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,7 +7762,39 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu ...@@ -7762,7 +7762,39 @@ 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 定价优先,否则走按次计费(兼容旧版本)
useImageTokenBilling := false
if s.resolver != nil && apiKey.Group != nil {
gid := apiKey.Group.ID
resolved := s.resolver.Resolve(ctx, PricingInput{Model: billingModel, GroupID: &gid})
if resolved.Source == "channel" && resolved.Mode == BillingModeToken {
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 var groupConfig *ImagePriceConfig
if apiKey.Group != nil { if apiKey.Group != nil {
groupConfig = &ImagePriceConfig{ groupConfig = &ImagePriceConfig{
...@@ -7772,6 +7804,7 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu ...@@ -7772,6 +7804,7 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu
} }
} }
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,7 +8033,37 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input * ...@@ -8000,7 +8033,37 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input *
// 根据请求类型选择计费方式 // 根据请求类型选择计费方式
if result.ImageCount > 0 { if result.ImageCount > 0 {
// 图片生成计费 // 图片生成计费:渠道 token 定价优先,否则走按次计费(兼容旧版本)
useImageTokenBilling := false
if s.resolver != nil && apiKey.Group != nil {
gid := apiKey.Group.ID
resolved := s.resolver.Resolve(ctx, PricingInput{Model: billingModel, GroupID: &gid})
if resolved.Source == "channel" && resolved.Mode == BillingModeToken {
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 var groupConfig *ImagePriceConfig
if apiKey.Group != nil { if apiKey.Group != nil {
groupConfig = &ImagePriceConfig{ groupConfig = &ImagePriceConfig{
...@@ -8010,6 +8073,7 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input * ...@@ -8010,6 +8073,7 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input *
} }
} }
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