feat: AutoZero 通用 Agent 框架(从 AutoGo 五阶段拆分完成)
llm(三协议 Provider)/ session(JSONL 持久化)/ tools(Registry、围栏、 builtin、computer)/ cdp(browser)/ skills / mcp / agent(循环、两级压缩、 编排、审批门控)+ 底座 jsonl/textx/execx/diag/scratch/spill/imagecache。 产品装配代码(config 桥接、schedule 工具、memory/plan/retro/prompt) 留在 AutoGo,宿主接入方式见 MIGRATION.md。
This commit is contained in:
commit
d5dac5a6fc
140 files changed
+26497
No files matched your search
@@ -0,0 +1,107 @@
|
||||
package llm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestSystemCacheSplit:稳定 system 与动态 system 必须分开下发,且收尾回合
|
||||
// 只禁用工具调用、不撤掉工具定义——两者都是为了保住 prompt cache 前缀。
|
||||
func TestSystemCacheSplit(t *testing.T) {
|
||||
tools := []ToolDef{{Name: "read", Description: "d", Parameters: map[string]any{"type": "object"}}}
|
||||
opts := StreamOptions{SystemDynamic: "DYNAMIC", DisableTools: true}
|
||||
|
||||
t.Run("anthropic:动态段是第二个 system 块且不带 cache_control", func(t *testing.T) {
|
||||
var body map[string]any
|
||||
srv := captureBodyServer(t, &body,
|
||||
"data: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"ok\"}}\n\n"+
|
||||
"data: {\"type\":\"message_stop\"}\n\n")
|
||||
defer srv.Close()
|
||||
p := &AnthropicProvider{BaseURL: srv.URL, APIKey: "k", Model: "m", MaxTokens: 128, HTTP: srv.Client()}
|
||||
if _, err := p.Stream(context.Background(), "STABLE",
|
||||
[]Message{{Role: RoleUser, Content: "hi"}}, tools, opts, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
blocks := body["system"].([]any)
|
||||
if len(blocks) != 2 {
|
||||
t.Fatalf("want 2 system blocks, got %d: %v", len(blocks), blocks)
|
||||
}
|
||||
first := blocks[0].(map[string]any)
|
||||
second := blocks[1].(map[string]any)
|
||||
if first["text"] != "STABLE" || first["cache_control"].(map[string]any)["type"] != "ephemeral" {
|
||||
t.Fatalf("stable block must be cached: %v", first)
|
||||
}
|
||||
if second["text"] != "DYNAMIC" {
|
||||
t.Fatalf("dynamic block text: %v", second)
|
||||
}
|
||||
if _, ok := second["cache_control"]; ok {
|
||||
t.Fatalf("dynamic block must not carry cache_control: %v", second)
|
||||
}
|
||||
if body["tool_choice"].(map[string]any)["type"] != "none" {
|
||||
t.Fatalf("tool_choice: %v", body["tool_choice"])
|
||||
}
|
||||
if len(body["tools"].([]any)) != 1 {
|
||||
t.Fatalf("tools must stay in the request: %v", body["tools"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("chat_completions:动态段是独立的第二条 system 消息", func(t *testing.T) {
|
||||
var body map[string]any
|
||||
srv := captureBodyServer(t, &body,
|
||||
"data: {\"choices\":[{\"delta\":{\"content\":\"ok\"},\"finish_reason\":\"stop\"}]}\n\ndata: [DONE]\n\n")
|
||||
defer srv.Close()
|
||||
p := &OpenAIProvider{BaseURL: srv.URL, APIKey: "k", Model: "m", HTTP: srv.Client()}
|
||||
if _, err := p.Stream(context.Background(), "STABLE",
|
||||
[]Message{{Role: RoleUser, Content: "hi"}}, tools, opts, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
msgs := body["messages"].([]any)
|
||||
if len(msgs) != 2 {
|
||||
t.Fatalf("want one system message + user, got %v", msgs)
|
||||
}
|
||||
if msgs[0].(map[string]any)["content"] != "STABLE\n\nDYNAMIC" {
|
||||
t.Fatalf("system must keep the stable prefix and append the dynamic tail: %v", msgs[0])
|
||||
}
|
||||
if body["tool_choice"] != "none" {
|
||||
t.Fatalf("tool_choice: %v", body["tool_choice"])
|
||||
}
|
||||
if len(body["tools"].([]any)) != 1 {
|
||||
t.Fatalf("tools must stay in the request: %v", body["tools"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("responses:动态段是独立的第二条 system 消息", func(t *testing.T) {
|
||||
var body map[string]any
|
||||
srv := captureBodyServer(t, &body,
|
||||
"data: {\"type\":\"response.output_text.delta\",\"delta\":\"ok\"}\n\n"+
|
||||
"data: {\"type\":\"response.completed\",\"response\":{\"output\":[],\"usage\":{\"input_tokens\":1,\"output_tokens\":1}}}\n\n")
|
||||
defer srv.Close()
|
||||
p := &ResponsesProvider{BaseURL: srv.URL, APIKey: "k", Model: "m", HTTP: srv.Client()}
|
||||
if _, err := p.Stream(context.Background(), "STABLE",
|
||||
[]Message{{Role: RoleUser, Content: "hi"}}, tools, opts, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
input := body["input"].([]any)
|
||||
if len(input) != 2 {
|
||||
t.Fatalf("want one system message + user, got %v", input)
|
||||
}
|
||||
if input[0].(map[string]any)["content"] != "STABLE\n\nDYNAMIC" {
|
||||
t.Fatalf("system must keep the stable prefix and append the dynamic tail: %v", input[0])
|
||||
}
|
||||
if body["tool_choice"] != "none" {
|
||||
t.Fatalf("tool_choice: %v", body["tool_choice"])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func captureBodyServer(t *testing.T, body *map[string]any, sse string) *httptest.Server {
|
||||
t.Helper()
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewDecoder(r.Body).Decode(body)
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
_, _ = w.Write([]byte(sse))
|
||||
}))
|
||||
}
|
||||
Reference in new issue
Block a user