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,92 @@
|
||||
package llm
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 1x1 红色 PNG
|
||||
const tinyPNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
|
||||
|
||||
func writeTinyPNG(t *testing.T) string {
|
||||
t.Helper()
|
||||
data, _ := base64.StdEncoding.DecodeString(tinyPNG)
|
||||
path := filepath.Join(t.TempDir(), "shot.png")
|
||||
if err := os.WriteFile(path, data, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func TestLoadImage(t *testing.T) {
|
||||
path := writeTinyPNG(t)
|
||||
b64, mt, err := LoadImage(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if mt != "image/png" || b64 != tinyPNG {
|
||||
t.Fatalf("roundtrip failed: mt=%s len=%d", mt, len(b64))
|
||||
}
|
||||
if _, _, err := LoadImage("x.txt"); err == nil {
|
||||
t.Fatal("expected unsupported type error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenAISerializationWithImages(t *testing.T) {
|
||||
path := writeTinyPNG(t)
|
||||
msgs := []Message{
|
||||
{Role: RoleUser, Content: "look", Images: []Image{{Path: path}}},
|
||||
{Role: RoleTool, ToolCallID: "t1", Content: "done", Images: []Image{{Path: path}}},
|
||||
}
|
||||
out := oaBuildMessages("sys", msgs)
|
||||
// user 消息 → content parts 数组(text + image_url)
|
||||
u := out[1]
|
||||
if u["role"] != "user" {
|
||||
t.Fatalf("role: %v", u["role"])
|
||||
}
|
||||
parts, _ := json.Marshal(u["content"])
|
||||
if !strings.Contains(string(parts), `"image_url"`) || !strings.Contains(string(parts), "data:image/png;base64,") {
|
||||
t.Fatalf("user image parts: %s", parts)
|
||||
}
|
||||
// tool result → tool 消息 + 紧随的 user 附图消息
|
||||
if out[2]["role"] != "tool" || out[3]["role"] != "user" {
|
||||
t.Fatalf("tool+image layout wrong: %v %v", out[2]["role"], out[3]["role"])
|
||||
}
|
||||
parts, _ = json.Marshal(out[3]["content"])
|
||||
if !strings.Contains(string(parts), "data:image/png") {
|
||||
t.Fatalf("tool image parts: %s", parts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnthropicSerializationWithImages(t *testing.T) {
|
||||
path := writeTinyPNG(t)
|
||||
msgs := []Message{
|
||||
{Role: RoleTool, ToolCallID: "t1", Content: "shot", Images: []Image{{Path: path}}},
|
||||
{Role: RoleUser, Content: "look again", Images: []Image{{Path: path}}},
|
||||
}
|
||||
out := anBuildMessages(msgs, false)
|
||||
body, _ := json.Marshal(out)
|
||||
if !strings.Contains(string(body), `"type":"tool_result"`) ||
|
||||
!strings.Contains(string(body), `"type":"image"`) ||
|
||||
!strings.Contains(string(body), `"media_type":"image/png"`) {
|
||||
t.Fatalf("anthropic serialization: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageUnavailableDegradation(t *testing.T) {
|
||||
msgs := []Message{{Role: RoleUser, Content: "look", Images: []Image{{Path: "/nonexistent/shot.png"}}}}
|
||||
oa := oaBuildMessages("s", msgs)
|
||||
body, _ := json.Marshal(oa[1]["content"])
|
||||
if !strings.Contains(string(body), "[image unavailable") {
|
||||
t.Fatalf("openai degradation missing: %s", body)
|
||||
}
|
||||
an := anBuildMessages(msgs, false)
|
||||
body, _ = json.Marshal(an[0]["content"])
|
||||
if !strings.Contains(string(body), "[image unavailable") {
|
||||
t.Fatalf("anthropic degradation missing: %s", body)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user