- 只翻注释/文档字符串,代码零变化:go/scanner 词法流比对 21 文件 token+字面量全等,增删行数 1:1(467/467) - 刻意保留非散文英文:JSON Schema description、工具 Description() 返回值、adapter prompt、API 路径与命令示例、环境变量名 - 逐行确认代码前缀未变,未触碰其他模块
95 lines
3.0 KiB
Go
95 lines
3.0 KiB
Go
package vision
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
|
)
|
|
|
|
func TestMessageResolver_FoldsImageToDescription(t *testing.T) {
|
|
t.Parallel()
|
|
rt := NewRouter(WithVisionTool("vision"), WithOCRTool("ocr"))
|
|
invoked := ""
|
|
res := NewMessageResolver(rt, func() ModelInfo {
|
|
return ModelInfo{Name: "text-model", SupportsVision: false}
|
|
}, func(_ context.Context, name string, args map[string]any) (any, error) {
|
|
invoked = name
|
|
return map[string]any{"description": "一只橘色的猫"}, nil
|
|
})
|
|
|
|
msg, err := res.Resolve(context.Background(), model.ImageMessage(model.RoleUser, "看到了吗", "data:image/png;base64,AAAA"))
|
|
if err != nil {
|
|
t.Fatalf("Resolve: %v", err)
|
|
}
|
|
if invoked != "vision" {
|
|
t.Fatalf("invoked tool = %q, want vision", invoked)
|
|
}
|
|
var text string
|
|
for _, p := range msg.Content {
|
|
if p.Type == model.ContentTypeText {
|
|
text += p.Text
|
|
}
|
|
if p.Type == model.ContentTypeImage {
|
|
t.Fatalf("image part still present after tool route: %+v", p)
|
|
}
|
|
}
|
|
if text == "" || text == "看到了吗" {
|
|
t.Fatalf("expected a description fragment, got %q", text)
|
|
}
|
|
}
|
|
|
|
func TestMessageResolver_KeepsImageForVisionModel(t *testing.T) {
|
|
t.Parallel()
|
|
rt := NewRouter(WithVisionTool("vision"), WithOCRTool("ocr"))
|
|
res := NewMessageResolver(rt, func() ModelInfo {
|
|
return ModelInfo{Name: "vision-model", SupportsVision: true}
|
|
}, func(_ context.Context, _ string, _ map[string]any) (any, error) {
|
|
t.Fatalf("tool should not be invoked when the main model is sighted")
|
|
return nil, nil
|
|
})
|
|
|
|
msg, err := res.Resolve(context.Background(), model.ImageMessage(model.RoleUser, "x", "data:image/png;base64,AAAA"))
|
|
if err != nil {
|
|
t.Fatalf("Resolve: %v", err)
|
|
}
|
|
if !messageHasImagePart(msg) {
|
|
t.Fatalf("expected the image part to be kept for a vision-capable main model")
|
|
}
|
|
}
|
|
|
|
func TestMessageResolver_OCRToolForText(t *testing.T) {
|
|
t.Parallel()
|
|
rt := NewRouter(WithVisionTool("vision"), WithOCRTool("ocr"))
|
|
invoked := ""
|
|
res := NewMessageResolver(rt, func() ModelInfo {
|
|
return ModelInfo{Name: "text-model", SupportsVision: false}
|
|
}, func(_ context.Context, name string, _ map[string]any) (any, error) {
|
|
invoked = name
|
|
return map[string]any{"text": "HELLO"}, nil
|
|
})
|
|
|
|
// 通过 router 的路由请求路径强制给出文本优先提示:这里直接经 Resolve
|
|
// 走通,而 Resolve 不传任何提示,因此优先使用视觉工具;
|
|
// 断言至少有一个文本部件替换了图像。
|
|
msg, err := res.Resolve(context.Background(), model.ImageMessage(model.RoleUser, "x", "data:image/png;base64,AAAA"))
|
|
if err != nil {
|
|
t.Fatalf("Resolve: %v", err)
|
|
}
|
|
if invoked != "vision" {
|
|
t.Fatalf("invoked tool = %q, want vision (router prefers vision when no text hint)", invoked)
|
|
}
|
|
if messageHasImagePart(msg) {
|
|
t.Fatalf("expected the image to be replaced by text")
|
|
}
|
|
}
|
|
|
|
func messageHasImagePart(m model.Message) bool {
|
|
for _, p := range m.Content {
|
|
if p.Type == model.ContentTypeImage {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|