Commit 38da737e authored by erio's avatar erio
Browse files

feat: channel token pricing takes priority over per-image billing

When ImageCount > 0, check if channel has token pricing configured:
- YES (source=channel, mode=token) → use token billing with image_output_tokens
- NO → fall back to CalculateImageCost (original per-image billing)

This allows channels to configure $/MTok pricing for image generation
models while maintaining backward compatibility for setups without
channel pricing.
parent 1b2ea7a1
...@@ -7762,16 +7762,49 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu ...@@ -7762,16 +7762,49 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu
} else if result.MediaType == "prompt" { } else if result.MediaType == "prompt" {
cost = &CostBreakdown{} cost = &CostBreakdown{}
} else if result.ImageCount > 0 { } else if result.ImageCount > 0 {
// 图片生成计费 // 图片生成计费:渠道 token 定价优先,否则走按次计费(兼容旧版本)
var groupConfig *ImagePriceConfig useImageTokenBilling := false
if apiKey.Group != nil { if s.resolver != nil && apiKey.Group != nil {
groupConfig = &ImagePriceConfig{ gid := apiKey.Group.ID
Price1K: apiKey.Group.ImagePrice1K, resolved := s.resolver.Resolve(ctx, PricingInput{Model: billingModel, GroupID: &gid})
Price2K: apiKey.Group.ImagePrice2K, if resolved.Source == "channel" && resolved.Mode == BillingModeToken {
Price4K: apiKey.Group.ImagePrice4K, useImageTokenBilling = true
}
}
if useImageTokenBilling {
// 渠道配置了 token 定价 → 用 token 计费(image_output_tokens 独立计价)
tokens := UsageTokens{
InputTokens: result.Usage.InputTokens,
OutputTokens: result.Usage.OutputTokens,
ImageOutputTokens: result.Usage.ImageOutputTokens,
}
gid := apiKey.Group.ID
var err error
cost, err = s.billingService.CalculateCostUnified(CostInput{
Ctx: ctx,
Model: billingModel,
GroupID: &gid,
Tokens: tokens,
RequestCount: 1,
RateMultiplier: multiplier,
Resolver: s.resolver,
})
if err != nil {
logger.LegacyPrintf("service.gateway", "Calculate image token cost failed: %v", err)
cost = &CostBreakdown{ActualCost: 0}
}
} else {
// 无渠道定价 → 走按次计费(默认,兼容旧版本)
var groupConfig *ImagePriceConfig
if apiKey.Group != nil {
groupConfig = &ImagePriceConfig{
Price1K: apiKey.Group.ImagePrice1K,
Price2K: apiKey.Group.ImagePrice2K,
Price4K: apiKey.Group.ImagePrice4K,
}
} }
cost = s.billingService.CalculateImageCost(billingModel, result.ImageSize, result.ImageCount, groupConfig, multiplier)
} }
cost = s.billingService.CalculateImageCost(billingModel, result.ImageSize, result.ImageCount, groupConfig, multiplier)
} else { } else {
// Token 计费 // Token 计费
tokens := UsageTokens{ tokens := UsageTokens{
...@@ -8000,16 +8033,47 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input * ...@@ -8000,16 +8033,47 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input *
// 根据请求类型选择计费方式 // 根据请求类型选择计费方式
if result.ImageCount > 0 { if result.ImageCount > 0 {
// 图片生成计费 // 图片生成计费:渠道 token 定价优先,否则走按次计费(兼容旧版本)
var groupConfig *ImagePriceConfig useImageTokenBilling := false
if apiKey.Group != nil { if s.resolver != nil && apiKey.Group != nil {
groupConfig = &ImagePriceConfig{ gid := apiKey.Group.ID
Price1K: apiKey.Group.ImagePrice1K, resolved := s.resolver.Resolve(ctx, PricingInput{Model: billingModel, GroupID: &gid})
Price2K: apiKey.Group.ImagePrice2K, if resolved.Source == "channel" && resolved.Mode == BillingModeToken {
Price4K: apiKey.Group.ImagePrice4K, useImageTokenBilling = true
}
}
if useImageTokenBilling {
tokens := UsageTokens{
InputTokens: result.Usage.InputTokens,
OutputTokens: result.Usage.OutputTokens,
ImageOutputTokens: result.Usage.ImageOutputTokens,
}
gid := apiKey.Group.ID
var err error
cost, err = s.billingService.CalculateCostUnified(CostInput{
Ctx: ctx,
Model: billingModel,
GroupID: &gid,
Tokens: tokens,
RequestCount: 1,
RateMultiplier: multiplier,
Resolver: s.resolver,
})
if err != nil {
logger.LegacyPrintf("service.gateway", "Calculate image token cost failed: %v", err)
cost = &CostBreakdown{ActualCost: 0}
}
} else {
var groupConfig *ImagePriceConfig
if apiKey.Group != nil {
groupConfig = &ImagePriceConfig{
Price1K: apiKey.Group.ImagePrice1K,
Price2K: apiKey.Group.ImagePrice2K,
Price4K: apiKey.Group.ImagePrice4K,
}
} }
cost = s.billingService.CalculateImageCost(billingModel, result.ImageSize, result.ImageCount, groupConfig, multiplier)
} }
cost = s.billingService.CalculateImageCost(billingModel, result.ImageSize, result.ImageCount, groupConfig, multiplier)
} else { } else {
// Token 计费(使用长上下文计费方法) // Token 计费(使用长上下文计费方法)
tokens := UsageTokens{ tokens := UsageTokens{
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment