fix(images): 兼容 OpenAI 多图编辑 multipart
- 将 OpenAI images/edits 请求构造成 multipart/form-data - 在运行时安全转存 URL 图片为 data URL 后提交二进制文件 - 覆盖多图字段、真实上游模型名和内部元数据过滤测试
This commit is contained in:
@@ -160,6 +160,9 @@ func (s *Service) hydrateProviderRequestAssetString(ctx context.Context, value s
|
||||
return value, nil
|
||||
}
|
||||
style, ok := requestAssetCapabilityHydrationForMedia("image", candidate, raw, "")
|
||||
if !ok && openAIImageEditRequiresMultipartBytes(candidate) {
|
||||
style, ok = requestAssetHydrateDataURL, true
|
||||
}
|
||||
if !ok {
|
||||
return value, nil
|
||||
}
|
||||
@@ -320,6 +323,9 @@ func requestAssetHydrationForField(path []string, asset store.RequestAsset, cand
|
||||
if style, ok := requestAssetCapabilityHydrationForMedia("image", candidate, asset.URL, asset.StorageProvider); ok {
|
||||
return style
|
||||
}
|
||||
if openAIImageEditRequiresMultipartBytes(candidate) {
|
||||
return requestAssetHydrateDataURL
|
||||
}
|
||||
}
|
||||
if mediaURLFieldNeedsHydration(path) {
|
||||
if style := configuredRequestAssetMediaURLHydration(candidate, requestAssetMediaURLKind(path)); style != "" {
|
||||
@@ -332,6 +338,11 @@ func requestAssetHydrationForField(path []string, asset store.RequestAsset, cand
|
||||
return requestAssetHydrateURL
|
||||
}
|
||||
|
||||
func openAIImageEditRequiresMultipartBytes(candidate store.RuntimeModelCandidate) bool {
|
||||
return normalizeProviderKey(candidate.Provider) == "openai" &&
|
||||
strings.TrimSpace(candidate.ModelType) == "image_edit"
|
||||
}
|
||||
|
||||
func requestAssetMediaKindForHydration(path []string, asset store.RequestAsset) string {
|
||||
if mediaURLFieldNeedsHydration(path) {
|
||||
return requestAssetMediaURLKind(path)
|
||||
|
||||
@@ -205,6 +205,35 @@ func TestHydrateProviderRequestAssetsUsesImageCapabilityBase64ForTopLevelImageAs
|
||||
}
|
||||
}
|
||||
|
||||
func TestHydrateProviderRequestAssetsConvertsOpenAIEditImagesForMultipart(t *testing.T) {
|
||||
payload := []byte{0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n', 1, 2, 3, 4}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
_, _ = w.Write(payload)
|
||||
}))
|
||||
defer server.Close()
|
||||
service := &Service{}
|
||||
body := map[string]any{
|
||||
"images": []any{
|
||||
server.URL + "/first.png",
|
||||
server.URL + "/second.png",
|
||||
},
|
||||
}
|
||||
|
||||
hydrated, err := service.hydrateProviderRequestAssets(context.Background(), body, store.RuntimeModelCandidate{
|
||||
Provider: "openai",
|
||||
ModelType: "image_edit",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("hydrate OpenAI edit images: %v", err)
|
||||
}
|
||||
images := hydrated["images"].([]any)
|
||||
want := "data:image/png;base64," + base64.StdEncoding.EncodeToString(payload)
|
||||
if len(images) != 2 || stringFromAny(images[0]) != want || stringFromAny(images[1]) != want {
|
||||
t.Fatalf("OpenAI edit images should be hydrated for multipart: %+v", images)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHydrateProviderRequestAssetsImageCapabilityOverridesProviderDataURLDefault(t *testing.T) {
|
||||
service := &Service{}
|
||||
body := map[string]any{
|
||||
|
||||
Reference in New Issue
Block a user