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。
93 lines
2.8 KiB
Go
93 lines
2.8 KiB
Go
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)
|
||
}
|
||
}
|