From ab99c308848389cc6e25bc3fb3dc8f7e850bf799 Mon Sep 17 00:00:00 2001 From: CaIon Date: Tue, 31 Mar 2026 23:58:10 +0800 Subject: [PATCH] fix: move image count n to OtherRatio to prevent double-counting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit commented out AddOtherRatio("n") in the Ali adaptor to fix double-counting but this could cause billing evasion when n is specified via extra["parameters"] instead of request.N. Root cause: ImagePriceRatio in GetTokenCountMeta() already included n, AND channel adaptors added OtherRatio("n"), resulting in n² billing. Proper fix: - Remove n from ImagePriceRatio (keep sizeRatio * qualityRatio only) - In ImageHelper, add default OtherRatio("n") when adaptor hasn't set one; set fallback tokens to 1 (base unit) - Restore Ali adaptor's AddOtherRatio("n") — it uses actual upstream parameters/response count, preventing billing evasion --- dto/openai_image.go | 11 +++++------ relay/channel/ali/image.go | 19 ++++++++----------- relay/image_handler.go | 13 +++++++++++-- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/dto/openai_image.go b/dto/openai_image.go index fa09155d..52986fbf 100644 --- a/dto/openai_image.go +++ b/dto/openai_image.go @@ -148,15 +148,14 @@ func (i *ImageRequest) GetTokenCountMeta() *types.TokenCountMeta { } } - // not support token count for dalle - n := uint(1) - if i.N != nil { - n = *i.N - } + // n is NOT included here; it is handled via OtherRatio("n") in + // image_handler.go (default) or channel adaptors (actual count). + // Including n here caused double-counting for channels that also + // set OtherRatio("n") (e.g. Ali/Bailian). return &types.TokenCountMeta{ CombineText: i.Prompt, MaxTokens: 1584, - ImagePriceRatio: sizeRatio * qualityRatio * float64(n), + ImagePriceRatio: sizeRatio * qualityRatio, } } diff --git a/relay/channel/ali/image.go b/relay/channel/ali/image.go index 68be2de9..be0f0068 100644 --- a/relay/channel/ali/image.go +++ b/relay/channel/ali/image.go @@ -54,10 +54,9 @@ func oaiImage2AliImageRequest(info *relaycommon.RelayInfo, request dto.ImageRequ } } - // 检查n参数 - // if imageRequest.Parameters.N != 0 { - // info.PriceData.AddOtherRatio("n", float64(imageRequest.Parameters.N)) - // } + if imageRequest.Parameters.N != 0 { + info.PriceData.AddOtherRatio("n", float64(imageRequest.Parameters.N)) + } // 同步图片模型和异步图片模型请求格式不一样 if isSync { @@ -328,13 +327,11 @@ func aliImageHandler(a *Adaptor, c *gin.Context, resp *http.Response, info *rela } imageResponses := responseAli2OpenAIImage(c, aliResponse, originRespBody, info, responseFormat) - // 可能生成多张图片,修正计费数量n - // 注释掉,否则会导致多次扣费用 - // if aliResponse.Usage.ImageCount != 0 { - // info.PriceData.AddOtherRatio("n", float64(aliResponse.Usage.ImageCount)) - // } else if len(imageResponses.Data) != 0 { - // info.PriceData.AddOtherRatio("n", float64(len(imageResponses.Data))) - // } + if aliResponse.Usage.ImageCount != 0 { + info.PriceData.AddOtherRatio("n", float64(aliResponse.Usage.ImageCount)) + } else if len(imageResponses.Data) != 0 { + info.PriceData.AddOtherRatio("n", float64(len(imageResponses.Data))) + } jsonResponse, err := common.Marshal(imageResponses) if err != nil { return types.NewError(err, types.ErrorCodeBadResponseBody), nil diff --git a/relay/image_handler.go b/relay/image_handler.go index 481c1cd2..a4fee7d9 100644 --- a/relay/image_handler.go +++ b/relay/image_handler.go @@ -117,11 +117,20 @@ func ImageHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *type if request.N != nil { imageN = *request.N } + + // n is handled via OtherRatio so it is applied exactly once in quota + // calculation (both price-based and ratio-based paths). + // Adaptors may have already set a more accurate count from the + // upstream response; only set the default when they haven't. + if _, hasN := info.PriceData.OtherRatios["n"]; !hasN { + info.PriceData.AddOtherRatio("n", float64(imageN)) + } + if usage.(*dto.Usage).TotalTokens == 0 { - usage.(*dto.Usage).TotalTokens = int(imageN) + usage.(*dto.Usage).TotalTokens = 1 } if usage.(*dto.Usage).PromptTokens == 0 { - usage.(*dto.Usage).PromptTokens = int(imageN) + usage.(*dto.Usage).PromptTokens = 1 } quality := "standard"