163 lines
6.6 KiB
Go
163 lines
6.6 KiB
Go
package protocol
|
||
|
||
import (
|
||
"context"
|
||
"testing"
|
||
)
|
||
|
||
// Agnes 图像端点是同步的,文生图与图生图共用 POST /v1/images/generations。
|
||
// 官方参数表只承认 model、prompt、size(必填)、ratio、image、return_base64 与
|
||
// extra_body:顶层 response_format 是官方明确列出的错误写法,output_format 会被
|
||
// 直接拒绝,n 也不在参数表内。因此这里逐字段固定线协议,防止退回 OpenAI 兼容字段。
|
||
func TestAgnesImageSendsOnlyDocumentedFields(t *testing.T) {
|
||
adapter := officialPackageAdapter(t, "agnes-image.yingce-plugin", "agnes-image")
|
||
if adapter.Metadata().RequiresPublicMediaURLs {
|
||
t.Fatal("Agnes 图像支持 Data URI 参考图,不应强制要求公共媒体 URL")
|
||
}
|
||
create, err := adapter.BuildCreate(context.Background(), RequestContext{Request: GenerationRequest{
|
||
Model: "agnes-image-2.1-flash", Prompt: "海边写真的亚洲女模特", AspectRatio: "3:4", Quality: "2k", ImageCount: 4,
|
||
}})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if create.Method != "POST" || create.Path != "/v1/images/generations" {
|
||
t.Fatalf("create = %#v", create)
|
||
}
|
||
// 原生端点在域名根下,originPath 让请求路径替换 Base URL 的路径段,
|
||
// 渠道沿用 .../v1 也能命中正确地址。
|
||
if !create.OriginPath {
|
||
t.Fatal("create 必须声明 originPath")
|
||
}
|
||
|
||
body := manifestTestBody(t, create)
|
||
if body["model"] != "agnes-image-2.1-flash" || body["prompt"] != "海边写真的亚洲女模特" {
|
||
t.Fatalf("body = %#v", body)
|
||
}
|
||
if body["size"] != "2K" {
|
||
t.Fatalf("size = %#v, 期望按质量档位发送 2K", body["size"])
|
||
}
|
||
if body["ratio"] != "3:4" {
|
||
t.Fatalf("ratio = %#v, 期望独立的 ratio 字段", body["ratio"])
|
||
}
|
||
// 官方参数表之外的字段会被上游按“不支持该字段”拒绝,逐个钉死。
|
||
for _, forbidden := range []string{"n", "output_format", "quality", "background", "response_format", "image", "mask", "style", "user"} {
|
||
if _, present := body[forbidden]; present {
|
||
t.Fatalf("body = %#v, 官方参数表没有 %s 字段,不能发送", body, forbidden)
|
||
}
|
||
}
|
||
|
||
extra, ok := body["extra_body"].(map[string]any)
|
||
if !ok {
|
||
t.Fatalf("extra_body = %#v", body["extra_body"])
|
||
}
|
||
if extra["response_format"] != "url" {
|
||
t.Fatalf("extra_body.response_format = %#v, 输出格式只能声明在 extra_body 内", extra["response_format"])
|
||
}
|
||
if _, present := extra["image"]; present {
|
||
t.Fatalf("extra_body = %#v, 文生图不应发送 image", extra)
|
||
}
|
||
}
|
||
|
||
func TestAgnesImagePutsReferencesInExtraBody(t *testing.T) {
|
||
adapter := officialPackageAdapter(t, "agnes-image.yingce-plugin", "agnes-image")
|
||
create, err := adapter.BuildCreate(context.Background(), RequestContext{Request: GenerationRequest{
|
||
Model: "agnes-image-2.1-flash", Prompt: "换装并保留原始构图", AspectRatio: "3:4", Quality: "1k",
|
||
Images: []MediaReference{
|
||
{URL: "https://cdn.example/anchor.png", Role: "edit_source", Order: 0},
|
||
{DataURL: "data:image/png;base64,QUJD", Role: "edit_source", Order: 1},
|
||
},
|
||
}})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
body := manifestTestBody(t, create)
|
||
if _, present := body["image"]; present {
|
||
t.Fatalf("body = %#v, 参考图必须放在 extra_body.image,而不是顶层 image", body)
|
||
}
|
||
extra, _ := body["extra_body"].(map[string]any)
|
||
images, ok := extra["image"].([]any)
|
||
if !ok || len(images) != 2 {
|
||
t.Fatalf("extra_body.image = %#v, 期望两张参考图", extra["image"])
|
||
}
|
||
if images[0] != "https://cdn.example/anchor.png" {
|
||
t.Fatalf("第一张 = %#v, order 决定顺序", images[0])
|
||
}
|
||
// 有 Data URL 时优先 Base64:Agnes 明确支持 Data URI,本地开发无法提供公共图片地址。
|
||
if images[1] != "data:image/png;base64,QUJD" {
|
||
t.Fatalf("第二张 = %#v, 有 dataUrl 时必须优先 Base64", images[1])
|
||
}
|
||
if body["size"] != "1K" {
|
||
t.Fatalf("size = %#v, 图生图同样必须携带必填的 size", body["size"])
|
||
}
|
||
}
|
||
|
||
func TestAgnesImageSizeFollowsPixelOrTier(t *testing.T) {
|
||
adapter := officialPackageAdapter(t, "agnes-image.yingce-plugin", "agnes-image")
|
||
tests := []struct {
|
||
name string
|
||
aspectRatio string
|
||
quality string
|
||
wantSize string
|
||
wantRatio string
|
||
}{
|
||
{name: "比例落到分辨率档位", aspectRatio: "16:9", quality: "4k", wantSize: "4K", wantRatio: "16:9"},
|
||
{name: "像素尺寸原样透传", aspectRatio: "1024x768", quality: "2k", wantSize: "1024x768"},
|
||
{name: "auto 也必须落到档位", aspectRatio: "auto", quality: "1k", wantSize: "1K"},
|
||
{name: "空比例回落到默认档位", aspectRatio: "", quality: "", wantSize: "1K"},
|
||
}
|
||
for _, test := range tests {
|
||
t.Run(test.name, func(t *testing.T) {
|
||
create, err := adapter.BuildCreate(context.Background(), RequestContext{Request: GenerationRequest{
|
||
Model: "agnes-image-2.1-flash", Prompt: "circle", AspectRatio: test.aspectRatio, Quality: test.quality,
|
||
}})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
body := manifestTestBody(t, create)
|
||
if body["size"] != test.wantSize {
|
||
t.Fatalf("size = %#v, 期望 %q", body["size"], test.wantSize)
|
||
}
|
||
gotRatio, present := body["ratio"]
|
||
if test.wantRatio == "" {
|
||
if present {
|
||
t.Fatalf("ratio = %#v, 非比例输入不得发送 ratio", gotRatio)
|
||
}
|
||
return
|
||
}
|
||
if gotRatio != test.wantRatio {
|
||
t.Fatalf("ratio = %#v, 期望 %q", gotRatio, test.wantRatio)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestAgnesImageRejectsUnsupportedRatio(t *testing.T) {
|
||
adapter := officialPackageAdapter(t, "agnes-image.yingce-plugin", "agnes-image")
|
||
// 9:21 / 4:5 等比例不在官方比例表内,静默回落到 1:1 会让用户拿到与选择不符的画幅。
|
||
if _, err := adapter.BuildCreate(context.Background(), RequestContext{Request: GenerationRequest{
|
||
Model: "agnes-image-2.1-flash", Prompt: "x", AspectRatio: "9:21",
|
||
}}); err == nil {
|
||
t.Fatal("9:21 不在官方比例表内,必须在校验阶段拒绝")
|
||
}
|
||
}
|
||
|
||
func TestAgnesImageReadsUrlAndBase64Output(t *testing.T) {
|
||
adapter := officialPackageAdapter(t, "agnes-image.yingce-plugin", "agnes-image")
|
||
result, err := adapter.ParseCreate(context.Background(), []byte(`{"created":1780000000,"data":[{"url":"https://storage.googleapis.com/agnes-aigc/x.png","b64_json":null}]}`))
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if result.Result == nil || len(result.Result.Images) != 1 || result.Result.Images[0].URL != "https://storage.googleapis.com/agnes-aigc/x.png" {
|
||
t.Fatalf("images = %#v", result.Result)
|
||
}
|
||
|
||
inline, err := adapter.ParseCreate(context.Background(), []byte(`{"data":[{"url":null,"b64_json":"aW1hZ2U="}]}`))
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if inline.Result == nil || len(inline.Result.Images) != 1 || inline.Result.Images[0].DataURL != "data:image/png;base64,aW1hZ2U=" {
|
||
t.Fatalf("images = %#v", inline.Result)
|
||
}
|
||
}
|