package runtime import ( "context" "encoding/json" "strings" "testing" "agent-native/runtime/llm" "agent-native/runtime/tools" ) // ---- N2 WP1:前缀稳定契约(缓存友好的机器钉住:同一注册序列 → 逐字节确定)---- type contractTool struct { name, desc string deferred bool } func (t contractTool) Name() string { return t.name } func (t contractTool) Description() string { return t.desc } func (t contractTool) Parameters() json.RawMessage { return json.RawMessage(`{"type":"object","properties":{}}`) } func (t contractTool) Execute(_ context.Context, _ json.RawMessage) (tools.Result, error) { return tools.Result{Content: "ok"}, nil } func (t contractTool) Exposure() string { if t.deferred { return tools.ExposureDeferred } return tools.ExposureDirect } func buildContractRegistry() *tools.Registry { reg := tools.NewRegistry("") reg.Register(contractTool{name: "read_file", desc: "读文件"}) reg.Register(contractTool{name: "write_file", desc: "写文件"}) reg.Register(contractTool{name: "grep", desc: "搜索", deferred: true}) return reg } func TestSystemTextDeterministicAcrossIdenticalBuilds(t *testing.T) { a := systemText(Config{SystemPrompt: "sys", Tools: buildContractRegistry()}) b := systemText(Config{SystemPrompt: "sys", Tools: buildContractRegistry()}) if a != b { t.Fatalf("同一注册序列的系统文本必须逐字节一致:\n%q\nvs\n%q", a, b) } // 契约内容:直接工具全在清单内且按注册序;deferred 工具不进入清单。 idx := strings.Index(a, "read_file") if idx < 0 || strings.Index(a, "write_file") < idx { t.Fatalf("直接工具须按注册序出现:%q", a) } if strings.Contains(a, "grep") { t.Fatalf("deferred 工具不得出现在系统文本:%q", a) } // 元工具注入位置确定(tool_search/call_tool 存在且在 Decls 中)。 decls := buildContractRegistry().Decls() names := []string{} for _, d := range decls { names = append(names, d.Name) } if len(names) != 4 || names[0] != "read_file" || names[1] != "write_file" { t.Fatalf("Decls 顺序契约:%v", names) } if names[2] != "tool_search" && names[3] != "tool_search" { if names[2] != "call_tool" && names[3] != "call_tool" { t.Fatalf("元工具缺失:%v", names) } } var _ = llm.ToolDecl{} // 保持导入面稳定 }