Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion backend/internal/service/image_generation_intent.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ func cloneRequestMapForImageIntent(body []byte) map[string]any {
func resolveOpenAIResponsesImageBillingConfig(reqBody map[string]any, fallbackModel string) (string, string, error) {
imageModel := ""
imageSize := ""
imageQuality := ""
hasImageTool := false
if reqBody != nil {
rawTools, _ := reqBody["tools"].([]any)
Expand All @@ -184,11 +185,15 @@ func resolveOpenAIResponsesImageBillingConfig(reqBody map[string]any, fallbackMo
hasImageTool = true
imageModel = strings.TrimSpace(firstNonEmptyString(toolMap["model"]))
imageSize = strings.TrimSpace(firstNonEmptyString(toolMap["size"]))
imageQuality = strings.TrimSpace(firstNonEmptyString(toolMap["quality"]))
break
}
if imageSize == "" {
imageSize = strings.TrimSpace(firstNonEmptyString(reqBody["size"]))
}
if imageQuality == "" {
imageQuality = strings.TrimSpace(firstNonEmptyString(reqBody["quality"]))
}
}
if imageModel == "" && reqBody != nil {
bodyModel := strings.TrimSpace(firstNonEmptyString(reqBody["model"]))
Expand All @@ -202,7 +207,7 @@ func resolveOpenAIResponsesImageBillingConfig(reqBody map[string]any, fallbackMo
if imageModel == "" {
imageModel = strings.TrimSpace(fallbackModel)
}
sizeTier := normalizeOpenAIImageSizeTier(imageSize)
sizeTier := normalizeOpenAIImageSizeTier(imageSize, imageQuality)
return imageModel, sizeTier, nil
}

Expand Down
21 changes: 19 additions & 2 deletions backend/internal/service/openai_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ func (s *OpenAIGatewayService) ParseOpenAIImagesRequest(c *gin.Context, body []b
if err := validateOpenAIImagesModel(req.Model); err != nil {
return nil, err
}
req.SizeTier = normalizeOpenAIImageSizeTier(req.Size)
req.SizeTier = normalizeOpenAIImageSizeTier(req.Size, req.Quality)
req.RequiredCapability = classifyOpenAIImagesCapability(req)
return req, nil
}
Expand Down Expand Up @@ -531,7 +531,24 @@ func isOpenAINativeImageOption(name string) bool {
}
}

func normalizeOpenAIImageSizeTier(size string) string {
// normalizeOpenAIImageSizeTier maps an image request to a billing tier
// ("1K"/"2K"/"4K") consumed by BillingService.CalculateImageCost and the
// Group.ImagePrice1K/2K/4K subscription pricing fields.
//
// gpt-image-2's image_tokens scale primarily with `quality`, not pixel size
// (low ≈ 1k tokens, medium ≈ 2k, high ≈ 4k), so an explicit quality value
// takes precedence. When quality is empty / "auto" / unrecognized we fall
// back to size-dimension classification for legacy callers (older
// gpt-image-1, dall-e flows).
func normalizeOpenAIImageSizeTier(size, quality string) string {
switch strings.ToLower(strings.TrimSpace(quality)) {
case "low":
return "1K"
case "medium":
return "2K"
case "high":
return "4K"
}
trimmed := strings.TrimSpace(size)
normalized := strings.ToLower(trimmed)
switch normalized {
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/service/openai_images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func TestOpenAIGatewayServiceParseOpenAIImagesRequest_JSON(t *testing.T) {
require.Equal(t, "draw a cat", parsed.Prompt)
require.True(t, parsed.Stream)
require.Equal(t, "1024x1024", parsed.Size)
require.Equal(t, "1K", parsed.SizeTier)
require.Equal(t, "4K", parsed.SizeTier)
require.Equal(t, OpenAIImagesCapabilityNative, parsed.RequiredCapability)
require.False(t, parsed.Multipart)
}
Expand Down
Loading