fix(images): 规范 OpenAI 图像尺寸参数
This commit is contained in:
@@ -370,10 +370,14 @@ func TestOpenAIClientImageEditUsesMultipartWithMultipleImages(t *testing.T) {
|
|||||||
ModelType: "image_edit",
|
ModelType: "image_edit",
|
||||||
Model: "gpt-image-2",
|
Model: "gpt-image-2",
|
||||||
Body: map[string]any{
|
Body: map[string]any{
|
||||||
"model": "gpt-image-2",
|
"model": "gpt-image-2",
|
||||||
"prompt": "combine the references",
|
"prompt": "combine the references",
|
||||||
"size": "1024x1536",
|
"size": "1024x1536",
|
||||||
"quality": "medium",
|
"quality": "medium",
|
||||||
|
"aspect_ratio": "2:3",
|
||||||
|
"resolution": "2K",
|
||||||
|
"width": 1024,
|
||||||
|
"height": 1536,
|
||||||
"images": []any{
|
"images": []any{
|
||||||
"data:image/png;base64," + base64.StdEncoding.EncodeToString([]byte("image one")),
|
"data:image/png;base64," + base64.StdEncoding.EncodeToString([]byte("image one")),
|
||||||
"data:image/jpeg;base64," + base64.StdEncoding.EncodeToString([]byte("image two")),
|
"data:image/jpeg;base64," + base64.StdEncoding.EncodeToString([]byte("image two")),
|
||||||
@@ -406,6 +410,62 @@ func TestOpenAIClientImageEditUsesMultipartWithMultipleImages(t *testing.T) {
|
|||||||
if _, ok := receivedFields["_metadata"]; ok {
|
if _, ok := receivedFields["_metadata"]; ok {
|
||||||
t.Fatalf("internal metadata must not be forwarded: %+v", receivedFields)
|
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) {
|
func TestOpenAIClientChatReasoningParamsByProvider(t *testing.T) {
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
body["model"] = upstreamModelName(request.Candidate)
|
body["model"] = upstreamModelName(request.Candidate)
|
||||||
|
normalizeOpenAIImageRequestBody(endpointKind, body)
|
||||||
stream := openAIEndpointSupportsStream(endpointKind) && (request.Stream || boolValue(body, "stream"))
|
stream := openAIEndpointSupportsStream(endpointKind) && (request.Stream || boolValue(body, "stream"))
|
||||||
ensureOpenAIStreamUsage(body, endpointKind, stream)
|
ensureOpenAIStreamUsage(body, endpointKind, stream)
|
||||||
raw, contentType, err := openAIRequestPayload(endpointKind, body, request.Candidate)
|
raw, contentType, err := openAIRequestPayload(endpointKind, body, request.Candidate)
|
||||||
@@ -171,6 +172,24 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
|
|||||||
}, nil
|
}, 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) {
|
func openAIRequestPayload(endpointKind string, body map[string]any, candidate store.RuntimeModelCandidate) ([]byte, string, error) {
|
||||||
if endpointKind != "images.edits" {
|
if endpointKind != "images.edits" {
|
||||||
raw, err := json.Marshal(body)
|
raw, err := json.Marshal(body)
|
||||||
|
|||||||
Reference in New Issue
Block a user