fix(images): 规范 OpenAI 图像尺寸参数

This commit is contained in:
2026-07-24 09:28:18 +08:00
parent 8df87875de
commit 36d1b18e10
2 changed files with 83 additions and 4 deletions
+64 -4
View File
@@ -370,10 +370,14 @@ func TestOpenAIClientImageEditUsesMultipartWithMultipleImages(t *testing.T) {
ModelType: "image_edit",
Model: "gpt-image-2",
Body: map[string]any{
"model": "gpt-image-2",
"prompt": "combine the references",
"size": "1024x1536",
"quality": "medium",
"model": "gpt-image-2",
"prompt": "combine the references",
"size": "1024x1536",
"quality": "medium",
"aspect_ratio": "2:3",
"resolution": "2K",
"width": 1024,
"height": 1536,
"images": []any{
"data:image/png;base64," + base64.StdEncoding.EncodeToString([]byte("image one")),
"data:image/jpeg;base64," + base64.StdEncoding.EncodeToString([]byte("image two")),
@@ -406,6 +410,62 @@ func TestOpenAIClientImageEditUsesMultipartWithMultipleImages(t *testing.T) {
if _, ok := receivedFields["_metadata"]; ok {
t.Fatalf("internal metadata must not be forwarded: %+v", receivedFields)
}
for _, field := range []string{"aspect_ratio", "resolution", "width", "height"} {
if _, ok := receivedFields[field]; ok {
t.Fatalf("generic image geometry field %q must not be forwarded: %+v", field, receivedFields)
}
}
}
func TestOpenAIClientImageGenerationUsesSizeWithoutGenericGeometryFields(t *testing.T) {
var received map[string]any
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := json.NewDecoder(r.Body).Decode(&received); err != nil {
t.Fatalf("decode image generation body: %v", err)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"data": []any{map[string]any{"b64_json": "aW1hZ2U="}},
})
}))
defer server.Close()
_, err := (OpenAIClient{HTTPClient: server.Client()}).Run(context.Background(), Request{
Kind: "images.generations",
ModelType: "image_generate",
Model: "gpt-image-2",
Body: map[string]any{
"model": "gpt-image-2",
"prompt": "draw",
"size": "2048x1152",
"quality": "high",
"aspect_ratio": "16:9",
"aspectRatio": "16:9",
"resolution": "2K",
"width": 2048,
"height": 1152,
},
Candidate: store.RuntimeModelCandidate{
BaseURL: server.URL,
Provider: "openai",
ProviderModelName: "gpt-image-2-vip",
ModelType: "image_generate",
Credentials: map[string]any{"apiKey": "openai-key"},
},
})
if err != nil {
t.Fatalf("run OpenAI image generation: %v", err)
}
if received["model"] != "gpt-image-2-vip" ||
received["prompt"] != "draw" ||
received["size"] != "2048x1152" ||
received["quality"] != "high" {
t.Fatalf("unexpected image generation fields: %+v", received)
}
for _, field := range []string{"aspect_ratio", "aspectRatio", "resolution", "width", "height"} {
if _, ok := received[field]; ok {
t.Fatalf("generic image geometry field %q must not be forwarded: %+v", field, received)
}
}
}
func TestOpenAIClientChatReasoningParamsByProvider(t *testing.T) {
+19
View File
@@ -65,6 +65,7 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
}
}
body["model"] = upstreamModelName(request.Candidate)
normalizeOpenAIImageRequestBody(endpointKind, body)
stream := openAIEndpointSupportsStream(endpointKind) && (request.Stream || boolValue(body, "stream"))
ensureOpenAIStreamUsage(body, endpointKind, stream)
raw, contentType, err := openAIRequestPayload(endpointKind, body, request.Candidate)
@@ -171,6 +172,24 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
}, nil
}
func normalizeOpenAIImageRequestBody(endpointKind string, body map[string]any) {
if endpointKind != "images.generations" && endpointKind != "images.edits" {
return
}
// OpenAI image endpoints express output geometry through size. The Gateway
// keeps the generic fields for capability validation and billing, but they
// are not valid OpenAI wire parameters.
for _, key := range []string{
"aspect_ratio",
"aspectRatio",
"resolution",
"width",
"height",
} {
delete(body, key)
}
}
func openAIRequestPayload(endpointKind string, body map[string]any, candidate store.RuntimeModelCandidate) ([]byte, string, error) {
if endpointKind != "images.edits" {
raw, err := json.Marshal(body)