From a8fa8dd212d3029f8fa9db17302526aaa647dc1e Mon Sep 17 00:00:00 2001 From: wangbo Date: Mon, 8 Jun 2026 23:21:14 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=85=BC=E5=AE=B9=E5=83=8F=E7=B4=A0?= =?UTF-8?q?=E5=88=86=E8=BE=A8=E7=8E=87=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/runner/param_processor_media.go | 37 +++++++++++- .../internal/runner/param_processor_test.go | 60 +++++++++++++++++++ .../internal/runner/param_processor_utils.go | 29 ++++++++- 3 files changed, 124 insertions(+), 2 deletions(-) diff --git a/apps/api/internal/runner/param_processor_media.go b/apps/api/internal/runner/param_processor_media.go index 8c576d3..e2e2a76 100644 --- a/apps/api/internal/runner/param_processor_media.go +++ b/apps/api/internal/runner/param_processor_media.go @@ -207,6 +207,14 @@ func (imageSizeProcessor) Process(params map[string]any, modelType string, conte width, height = constrainImageDimensions(width, height, capability) params["width"] = width params["height"] = height + if stringFromAny(params["aspect_ratio"]) == "" { + aspectRatio := aspectRatioFromDimensions(width, height) + allowed := aspectRatioAllowed(capability["aspect_ratio_allowed"], firstNonEmptyString(stringFromAny(params["resolution"]), context.resolution)) + if processed, ok := validateAndAdjustAspectRatio(aspectRatio, capability, allowed); ok && processed != "" { + params["aspect_ratio"] = processed + context.aspectRatio = processed + } + } resolution := normalizeImageResolutionForCapability(firstNonEmptyString(stringFromAny(params["resolution"]), context.resolution), width, height, capability) if resolution != "" { params["resolution"] = resolution @@ -248,7 +256,10 @@ func imageDimensionsFromParams(params map[string]any) (int, int, bool) { if width > 0 && height > 0 { return width, height, true } - return parsePixelSizeString(stringFromAny(params["size"])) + if width, height, ok := parsePixelSizeString(stringFromAny(params["size"])); ok { + return width, height, true + } + return parsePixelSizeString(stringFromAny(params["resolution"])) } func imageSizeCapabilityConfigured(capability map[string]any) bool { @@ -431,6 +442,30 @@ func imageResolutionFromDimensions(width int, height int) string { } } +func aspectRatioFromDimensions(width int, height int) string { + if width <= 0 || height <= 0 { + return "" + } + divisor := gcd(width, height) + return fmt.Sprintf("%d:%d", width/divisor, height/divisor) +} + +func gcd(a int, b int) int { + if a < 0 { + a = -a + } + if b < 0 { + b = -b + } + for b != 0 { + a, b = b, a%b + } + if a == 0 { + return 1 + } + return a +} + func closestImageResolution(target string, allowed []string) string { order := []string{"1K", "2K", "3K", "4K", "8K"} targetIndex := indexOfString(order, target) diff --git a/apps/api/internal/runner/param_processor_test.go b/apps/api/internal/runner/param_processor_test.go index af105d1..47e0c82 100644 --- a/apps/api/internal/runner/param_processor_test.go +++ b/apps/api/internal/runner/param_processor_test.go @@ -1,6 +1,7 @@ package runner import ( + "fmt" "testing" "github.com/easyai/easyai-ai-gateway/apps/api/internal/store" @@ -699,6 +700,65 @@ func TestParamProcessorImageSizeConstraintsNormalizeExplicitDimensions(t *testin t.Fatalf("expected image size preprocessing log against output_size_range, got %+v", result.Log.Changes) } +func TestParamProcessorImageSizeConstraintsAcceptPixelResolutionStrings(t *testing.T) { + candidate := store.RuntimeModelCandidate{ + ModelType: "image_generate", + Capabilities: map[string]any{ + "image_generate": map[string]any{ + "output_resolutions": []any{"1K", "2K", "4K"}, + "aspect_ratio_allowed": []any{ + "1:1", + "16:9", + "9:16", + }, + "output_size_range": []any{655360, 8294400}, + "width_height_range": []any{1, 3840}, + }, + }, + } + + for name, body := range map[string]map[string]any{ + "resolution": { + "model": "gpt-image-2", + "prompt": "draw", + "resolution": "3840x2160", + }, + "size": { + "model": "gpt-image-2", + "prompt": "draw", + "size": "3840x2160", + }, + "non-standard-resolution": { + "model": "gpt-image-2", + "prompt": "draw", + "resolution": "3600x1900", + }, + } { + t.Run(name, func(t *testing.T) { + processed := preprocessRequest("images.generations", body, candidate) + expectedWidth := 3840 + expectedHeight := 2160 + if name == "non-standard-resolution" { + expectedWidth = 3600 + expectedHeight = 1900 + } + if processed["width"] != expectedWidth || processed["height"] != expectedHeight { + t.Fatalf("pixel resolution string should populate width/height, got %+v", processed) + } + if processed["aspect_ratio"] != "16:9" { + t.Fatalf("pixel resolution string should infer aspect_ratio, got %+v", processed) + } + if processed["resolution"] != "4K" { + t.Fatalf("pixel resolution string should normalize resolution to allowed bucket, got %+v", processed) + } + expectedSize := fmt.Sprintf("%dx%d", expectedWidth, expectedHeight) + if processed["size"] != expectedSize { + t.Fatalf("size should stay synchronized with normalized width/height, got %+v", processed) + } + }) + } +} + func TestParamProcessorImageSizeConstraintsNormalizeEditDimensions(t *testing.T) { body := map[string]any{ "model": "gpt-image-2", diff --git a/apps/api/internal/runner/param_processor_utils.go b/apps/api/internal/runner/param_processor_utils.go index ebfec31..fe5f951 100644 --- a/apps/api/internal/runner/param_processor_utils.go +++ b/apps/api/internal/runner/param_processor_utils.go @@ -29,7 +29,7 @@ func validateAndAdjustAspectRatio(aspectRatio string, capability map[string]any, if containsString(allowed, aspectRatio) { return aspectRatio, true } - return allowed[0], true + return closestAspectRatio(aspectRatio, allowed), true } func isMediaModelTypeWithAspectRatio(capability map[string]any) bool { @@ -445,6 +445,33 @@ func adjustAspectRatioToRange(value string, minValue float64, maxValue float64, return ratioString(maxValue) } +func closestAspectRatio(value string, allowed []string) string { + if len(allowed) == 0 { + return value + } + current, ok := aspectRatioNumber(value) + if !ok { + return allowed[0] + } + closest := "" + minDiff := math.Inf(1) + for _, candidate := range allowed { + ratio, ok := aspectRatioNumber(candidate) + if !ok { + continue + } + diff := math.Abs(ratio - current) + if diff < minDiff { + minDiff = diff + closest = candidate + } + } + if closest != "" { + return closest + } + return allowed[0] +} + func ratioString(value float64) string { if value <= 0 { return "1:1"