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) } }