✨ feat: 灵台引擎五神齐备与 Go/Rust 官方绑定(WP-15)
- C++ 新增四模块(modules.cpp,19 个导出符号全部 hj_*): 小周天 hj_stream:流会话(sequence/delta_index 单调)与 content.block.delta envelope 确定性编码(重放字节一致) 烛照 hj_policy:URL 协议白名单(拒 file/javascript/data)、路径授权根与 .. 段拒绝、幂等键非空≤128、四维决策表(autonomy×risk→allow/ask/deny,非法输入 deny fail-closed) 洛书 hj_plan:预算核算(达限/负数拒绝) 河图 hj_recall:确定性重排(词频×稳定排序,canonical JSON 输出;非法输入 PARSE_FAILED) - doctest 22 用例 20135 断言全绿(含每模块 golden 与确定性双跑) - Go 绑定(cgo 自包含编译引擎源码):Engine RAII/TransformMarkdown/ValidateURL/ValidatePath/Decide/CheckBudget/RecallRerank;go test 3 用例通过(transform/rerank golden 与 C++ 字节一致) - Rust 绑定(bindgen 生成 + 安全包装):Engine RAII(Drop 释放)、Error 枚举映射全部 HjStatus、Send/Sync(HjEngine 线程安全);cargo test 3 用例通过(同 golden) - 三端(C++/Go/Rust)与 Python golden 一致:同一 markdown 输入在四端输出字节相同(跨端一致性首次全语言验证)
This commit is contained in:
1 parent
99a7789432
commit
a39cec5287
15 files changed
+1205
-3
No files matched your search
@@ -0,0 +1,4 @@
|
||||
package lingtai
|
||||
|
||||
// 引擎源码直接编译(cgo 把 .cpp 交给 C++ 编译器)。
|
||||
// 本文件仅承载构建期声明注释,无运行时代码。
|
||||
+1
@@ -0,0 +1 @@
|
||||
/Users/chenxin/code/python/hunjing/engine/include/hunjing
|
||||
@@ -0,0 +1,153 @@
|
||||
// Package lingtai 是灵台引擎的官方 Go 绑定(cgo,ADR-016/017)。
|
||||
//
|
||||
// 自包含编译引擎源码;薄封装:仅类型转换与错误映射(契约 lingtai-ffi 第 8 节)。
|
||||
package lingtai
|
||||
|
||||
/*
|
||||
#cgo CXXFLAGS: -std=c++23 -O2
|
||||
#cgo CFLAGS: -I${SRCDIR}/../../include
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "hunjing/lingtai.h"
|
||||
|
||||
// C 桥接(C++ 编译单元在下方 sources 中声明为 C++,由 cgo 链接 C++ 编译器)
|
||||
typedef int (*unused_cb)();
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// 直接编译引擎源码(cgo 自动以 C++ 处理 .cpp 文件)
|
||||
// 注:通过 #cgo LDFLAGS/CXXFLAGS 与文件列表在同目录 cxx_sources.go 中声明。
|
||||
|
||||
// Engine 是线程安全的引擎句柄(RAII)。
|
||||
type Engine struct {
|
||||
ptr *C.HjEngine
|
||||
}
|
||||
|
||||
// New 创建引擎;ABI major 不匹配返回错误(fail-fast,不静默降级)。
|
||||
func New() (*Engine, error) {
|
||||
var ptr *C.HjEngine
|
||||
st := C.hj_engine_new(C.HJ_ABI_VERSION, &ptr)
|
||||
if st != C.HJ_OK {
|
||||
return nil, fmt.Errorf("lingtai: engine init failed: %s", C.GoString(C.hj_last_error()))
|
||||
}
|
||||
return &Engine{ptr: ptr}, nil
|
||||
}
|
||||
|
||||
// Close 释放引擎(幂等安全:nil 检查)。
|
||||
func (e *Engine) Close() error {
|
||||
if e == nil || e.ptr == nil {
|
||||
return nil
|
||||
}
|
||||
st := C.hj_engine_free(e.ptr)
|
||||
e.ptr = nil
|
||||
if st != C.HJ_OK {
|
||||
return fmt.Errorf("lingtai: engine free: %s", C.GoString(C.hj_last_error()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) valid() error {
|
||||
if e == nil || e.ptr == nil {
|
||||
return fmt.Errorf("lingtai: engine handle is nil")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TransformVersion 返回 Markdown 解析器版本。
|
||||
func TransformVersion() string { return C.GoString(C.hj_transform_version()) }
|
||||
|
||||
// TransformMarkdown 将 Markdown 规范化为 canonical ContentBlock JSON。
|
||||
func (e *Engine) TransformMarkdown(input string) (string, error) {
|
||||
if err := e.valid(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
cin := C.CString(input)
|
||||
defer C.free(unsafe.Pointer(cin))
|
||||
var buf *C.HjBuffer
|
||||
st := C.hj_transform_markdown(e.ptr, cin, C.size_t(len(input)), &buf)
|
||||
if st != C.HJ_OK {
|
||||
return "", fmt.Errorf("lingtai: transform: %s", C.GoString(C.hj_last_error()))
|
||||
}
|
||||
defer C.hj_buffer_free(buf)
|
||||
return C.GoStringN((*C.char)(C.hj_buffer_data(buf)), C.int(C.hj_buffer_len(buf))), nil
|
||||
}
|
||||
|
||||
// ValidateURL 校验 URL 协议(仅 http/https)。
|
||||
func ValidateURL(url string) error {
|
||||
cu := C.CString(url)
|
||||
defer C.free(unsafe.Pointer(cu))
|
||||
if st := C.hj_policy_validate_url(cu); st != C.HJ_OK {
|
||||
return fmt.Errorf("lingtai: url denied: %s", C.GoString(C.hj_last_error()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatePath 校验路径在授权根内(拒绝 .. 与越界)。
|
||||
func ValidatePath(path, root string) error {
|
||||
cp := C.CString(path)
|
||||
cr := C.CString(root)
|
||||
defer C.free(unsafe.Pointer(cp))
|
||||
defer C.free(unsafe.Pointer(cr))
|
||||
if st := C.hj_policy_validate_path(cp, cr); st != C.HJ_OK {
|
||||
return fmt.Errorf("lingtai: path denied: %s", C.GoString(C.hj_last_error()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PolicyDecision 是四维裁决结果。
|
||||
type PolicyDecision int
|
||||
|
||||
const (
|
||||
DecisionAllow PolicyDecision = 0
|
||||
DecisionAsk PolicyDecision = 1
|
||||
DecisionDeny PolicyDecision = 2
|
||||
)
|
||||
|
||||
// Autonomy levels(契约 autonomy_level)。
|
||||
const (
|
||||
AutonomyIntrospective = 0
|
||||
AutonomyRuleBound = 1
|
||||
AutonomyApprovalReq = 2
|
||||
AutonomyForbidden = 3
|
||||
)
|
||||
|
||||
// Risk levels。
|
||||
const (
|
||||
RiskReadOnly = 0
|
||||
RiskSideEffect = 1
|
||||
RiskHighImpact = 2
|
||||
)
|
||||
|
||||
// Decide 运行决策表(autonomy × risk → allow/ask/deny;非法输入 deny)。
|
||||
func Decide(autonomy, risk int) PolicyDecision {
|
||||
return PolicyDecision(C.hj_policy_decide(C.int(autonomy), C.int(risk)))
|
||||
}
|
||||
|
||||
// CheckBudget 校验预算(used >= max 返回错误)。
|
||||
func CheckBudget(used, maximum int64) error {
|
||||
if st := C.hj_plan_check_budget(C.int64_t(used), C.int64_t(maximum)); st != C.HJ_OK {
|
||||
return fmt.Errorf("lingtai: budget: %s", C.GoString(C.hj_last_error()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecallRerank 对候选做确定性重排,返回 canonical JSON。
|
||||
func (e *Engine) RecallRerank(candidatesJSON string) (string, error) {
|
||||
if err := e.valid(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
cj := C.CString(candidatesJSON)
|
||||
defer C.free(unsafe.Pointer(cj))
|
||||
var buf *C.HjBuffer
|
||||
st := C.hj_recall_rerank(cj, &buf)
|
||||
if st != C.HJ_OK {
|
||||
return "", fmt.Errorf("lingtai: rerank: %s", C.GoString(C.hj_last_error()))
|
||||
}
|
||||
defer C.hj_buffer_free(buf)
|
||||
return C.GoStringN((*C.char)(C.hj_buffer_data(buf)), C.int(C.hj_buffer_len(buf))), nil
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package lingtai
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEngineLifecycleAndTransform(t *testing.T) {
|
||||
e, err := New()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer e.Close()
|
||||
if TransformVersion() != "hunjing-markdown-v1" {
|
||||
t.Fatalf("parser version: %s", TransformVersion())
|
||||
}
|
||||
out, err := e.TransformMarkdown("x\n```mermaid\nA-->B\n```\n$$\nz\n$$\n")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := `{"parser_version":"hunjing-markdown-v1","blocks":[{"type":"markdown","text":"x","flavor":"hunjing-gfm-v1","status":"completed"},{"type":"mermaid","source":"A-->B","status":"completed"},{"type":"math","notation":"latex","display":"block","source":"z","status":"completed"}]}`
|
||||
if out != want {
|
||||
t.Fatalf("golden mismatch:\n got: %s\nwant: %s", out, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPolicyPrimitives(t *testing.T) {
|
||||
if err := ValidateURL("https://ok.example"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ValidateURL("javascript:alert(1)"); err == nil {
|
||||
t.Fatal("javascript: 应被拒绝")
|
||||
}
|
||||
if err := ValidatePath("/data/d/f", "/data/d"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ValidatePath("/data/d/../x", "/data/d"); err == nil {
|
||||
t.Fatal(".. 应被拒绝")
|
||||
}
|
||||
if d := Decide(AutonomyRuleBound, RiskReadOnly); d != DecisionAllow {
|
||||
t.Fatalf("循律+只读应 allow: %d", d)
|
||||
}
|
||||
if d := Decide(AutonomyIntrospective, RiskHighImpact); d != DecisionDeny {
|
||||
t.Fatalf("自省+高影响应 deny: %d", d)
|
||||
}
|
||||
if err := CheckBudget(10, 10); err == nil {
|
||||
t.Fatal("预算达限应报错")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecallRerankDeterministic(t *testing.T) {
|
||||
e, _ := New()
|
||||
defer e.Close()
|
||||
in := `{"query_terms":["魂镜"],"candidates":[{"id":"a","text":"魂镜 系统"},{"id":"b","text":"无"}]}`
|
||||
out1, err := e.RecallRerank(in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out2, _ := e.RecallRerank(in)
|
||||
if out1 != out2 {
|
||||
t.Fatalf("确定性失败: %s vs %s", out1, out2)
|
||||
}
|
||||
if out1 != `{"ranked":[{"id":"a","score":10},{"id":"b","score":0}]}` {
|
||||
t.Fatalf("golden mismatch: %s", out1)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user