fix: move image count n to OtherRatio to prevent double-counting

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
This commit is contained in:
CaIon
2026-03-31 23:58:10 +08:00
parent 53aeee4ff7
commit ab99c30884
3 changed files with 24 additions and 19 deletions
+5 -6
View File
@@ -148,15 +148,14 @@ func (i *ImageRequest) GetTokenCountMeta() *types.TokenCountMeta {
} }
} }
// not support token count for dalle // n is NOT included here; it is handled via OtherRatio("n") in
n := uint(1) // image_handler.go (default) or channel adaptors (actual count).
if i.N != nil { // Including n here caused double-counting for channels that also
n = *i.N // set OtherRatio("n") (e.g. Ali/Bailian).
}
return &types.TokenCountMeta{ return &types.TokenCountMeta{
CombineText: i.Prompt, CombineText: i.Prompt,
MaxTokens: 1584, MaxTokens: 1584,
ImagePriceRatio: sizeRatio * qualityRatio * float64(n), ImagePriceRatio: sizeRatio * qualityRatio,
} }
} }
+8 -11
View File
@@ -54,10 +54,9 @@ func oaiImage2AliImageRequest(info *relaycommon.RelayInfo, request dto.ImageRequ
} }
} }
// 检查n参数 if imageRequest.Parameters.N != 0 {
// if imageRequest.Parameters.N != 0 { info.PriceData.AddOtherRatio("n", float64(imageRequest.Parameters.N))
// info.PriceData.AddOtherRatio("n", float64(imageRequest.Parameters.N)) }
// }
// 同步图片模型和异步图片模型请求格式不一样 // 同步图片模型和异步图片模型请求格式不一样
if isSync { 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) imageResponses := responseAli2OpenAIImage(c, aliResponse, originRespBody, info, responseFormat)
// 可能生成多张图片,修正计费数量n if aliResponse.Usage.ImageCount != 0 {
// 注释掉,否则会导致多次扣费用 info.PriceData.AddOtherRatio("n", float64(aliResponse.Usage.ImageCount))
// if aliResponse.Usage.ImageCount != 0 { } else if len(imageResponses.Data) != 0 {
// info.PriceData.AddOtherRatio("n", float64(aliResponse.Usage.ImageCount)) info.PriceData.AddOtherRatio("n", float64(len(imageResponses.Data)))
// } else if len(imageResponses.Data) != 0 { }
// info.PriceData.AddOtherRatio("n", float64(len(imageResponses.Data)))
// }
jsonResponse, err := common.Marshal(imageResponses) jsonResponse, err := common.Marshal(imageResponses)
if err != nil { if err != nil {
return types.NewError(err, types.ErrorCodeBadResponseBody), nil return types.NewError(err, types.ErrorCodeBadResponseBody), nil
+11 -2
View File
@@ -117,11 +117,20 @@ func ImageHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *type
if request.N != nil { if request.N != nil {
imageN = *request.N 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 { if usage.(*dto.Usage).TotalTokens == 0 {
usage.(*dto.Usage).TotalTokens = int(imageN) usage.(*dto.Usage).TotalTokens = 1
} }
if usage.(*dto.Usage).PromptTokens == 0 { if usage.(*dto.Usage).PromptTokens == 0 {
usage.(*dto.Usage).PromptTokens = int(imageN) usage.(*dto.Usage).PromptTokens = 1
} }
quality := "standard" quality := "standard"