From 11c4606874d1ade3219ce75bf405463118998172 Mon Sep 17 00:00:00 2001 From: erio Date: Mon, 13 Apr 2026 02:28:31 +0800 Subject: [PATCH] fix(channel): use upstream model for account stats pricing and remove channel pricing fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - resolveAccountStatsCost now uses the final upstream model (after account-level mapping) to match custom pricing rules, fixing the issue where requested model (e.g. claude-sonnet-4-5) didn't match rules configured for upstream model (e.g. claude-opus-4-6) - Remove tryChannelPricing fallback — only custom rules are applied, unmatched requests use default formula (total_cost × rate) - Remove unused billingService and serviceTier parameters - Update description: "启用后将支持自定义账号统计的模型价格" --- .../internal/service/account_stats_pricing.go | 38 ++++--------------- backend/internal/service/gateway_service.go | 13 ++++--- .../service/openai_gateway_service.go | 12 ++++-- frontend/src/i18n/locales/en.ts | 2 +- frontend/src/i18n/locales/zh.ts | 2 +- 5 files changed, 25 insertions(+), 42 deletions(-) diff --git a/backend/internal/service/account_stats_pricing.go b/backend/internal/service/account_stats_pricing.go index 86f98a12..4a896d9f 100644 --- a/backend/internal/service/account_stats_pricing.go +++ b/backend/internal/service/account_stats_pricing.go @@ -8,23 +8,18 @@ import ( // resolveAccountStatsCost 计算账号统计定价费用。 // 返回 nil 表示不覆盖,使用默认公式(total_cost × account_rate_multiplier)。 -// -// 匹配优先级(先命中为准): -// 1. 自定义规则(AccountStatsPricingRules,按数组顺序遍历) -// 2. 渠道已有的模型定价(ApplyPricingToAccountStats 开启时) -// 3. nil → 走默认公式 +// 仅匹配自定义规则(AccountStatsPricingRules),按数组顺序先命中为准。 +// upstreamModel 是最终发往上游的模型 ID,用于匹配自定义规则中的模型定价。 func resolveAccountStatsCost( ctx context.Context, channelService *ChannelService, - billingService *BillingService, accountID int64, groupID int64, - billingModel string, + upstreamModel string, tokens UsageTokens, requestCount int, - serviceTier string, ) *float64 { - if channelService == nil || billingService == nil { + if channelService == nil || upstreamModel == "" { return nil } channel, err := channelService.GetChannelForGroup(ctx, groupID) @@ -33,22 +28,15 @@ func resolveAccountStatsCost( } platform := channelService.GetGroupPlatform(ctx, groupID) - modelLower := strings.ToLower(billingModel) - - // 优先级 1:自定义规则 - if cost := tryCustomRules(channel, accountID, groupID, platform, modelLower, tokens, requestCount); cost != nil { - return cost - } - - // 优先级 2:渠道已有模型定价 - return tryChannelPricing(ctx, channelService, groupID, billingModel, tokens, requestCount) + return tryCustomRules(channel, accountID, groupID, platform, upstreamModel, tokens, requestCount) } // tryCustomRules 遍历自定义规则,按数组顺序先命中为准。 func tryCustomRules( channel *Channel, accountID, groupID int64, - platform, modelLower string, tokens UsageTokens, requestCount int, + platform, model string, tokens UsageTokens, requestCount int, ) *float64 { + modelLower := strings.ToLower(model) for _, rule := range channel.AccountStatsPricingRules { if !matchAccountStatsRule(&rule, accountID, groupID) { continue @@ -62,18 +50,6 @@ func tryCustomRules( return nil } -// tryChannelPricing 使用渠道已有的模型定价计算账号统计费用。 -func tryChannelPricing( - ctx context.Context, channelService *ChannelService, - groupID int64, billingModel string, tokens UsageTokens, requestCount int, -) *float64 { - pricing := channelService.GetChannelModelPricing(ctx, groupID, billingModel) - if pricing == nil { - return nil - } - return calculateStatsCost(pricing, tokens, requestCount) -} - // matchAccountStatsRule 检查规则是否匹配指定的 accountID 和 groupID。 // 匹配条件:accountID ∈ rule.AccountIDs 或 groupID ∈ rule.GroupIDs。 // 如果规则的 AccountIDs 和 GroupIDs 都为空,视为不匹配。 diff --git a/backend/internal/service/gateway_service.go b/backend/internal/service/gateway_service.go index d68a7771..70dd9b52 100644 --- a/backend/internal/service/gateway_service.go +++ b/backend/internal/service/gateway_service.go @@ -7581,11 +7581,15 @@ func (s *GatewayService) recordUsageCore(ctx context.Context, input *recordUsage usageLog := s.buildRecordUsageLog(ctx, input, result, apiKey, user, account, subscription, requestedModel, multiplier, accountRateMultiplier, billingType, cacheTTLOverridden, cost, opts) - // 计算账号统计定价费用 + // 计算账号统计定价费用(使用最终上游模型匹配自定义规则) if apiKey.GroupID != nil { + upstreamModel := result.UpstreamModel + if upstreamModel == "" { + upstreamModel = result.Model + } usageLog.AccountStatsCost = resolveAccountStatsCost( - ctx, s.channelService, s.billingService, - account.ID, *apiKey.GroupID, billingModel, + ctx, s.channelService, + account.ID, *apiKey.GroupID, upstreamModel, UsageTokens{ InputTokens: result.Usage.InputTokens, OutputTokens: result.Usage.OutputTokens, @@ -7593,8 +7597,7 @@ func (s *GatewayService) recordUsageCore(ctx context.Context, input *recordUsage CacheReadTokens: result.Usage.CacheReadInputTokens, ImageOutputTokens: result.Usage.ImageOutputTokens, }, - 1, // requestCount - "", // serviceTier: Anthropic 平台不使用 service tier + 1, // requestCount ) } diff --git a/backend/internal/service/openai_gateway_service.go b/backend/internal/service/openai_gateway_service.go index 70abd4ce..98258cd0 100644 --- a/backend/internal/service/openai_gateway_service.go +++ b/backend/internal/service/openai_gateway_service.go @@ -4573,12 +4573,16 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec usageLog.SubscriptionID = &subscription.ID } - // 计算账号统计定价费用 + // 计算账号统计定价费用(使用最终上游模型匹配自定义规则) if apiKey.GroupID != nil { + statsModel := result.UpstreamModel + if statsModel == "" { + statsModel = result.Model + } usageLog.AccountStatsCost = resolveAccountStatsCost( - ctx, s.channelService, s.billingService, - account.ID, *apiKey.GroupID, billingModel, - tokens, 1, serviceTier, + ctx, s.channelService, + account.ID, *apiKey.GroupID, statsModel, + tokens, 1, ) } diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 07ae0c3d..f45a02f6 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -1877,7 +1877,7 @@ export default { pricingEntry: 'Pricing Entry', noModels: 'No models added', applyPricingToAccountStats: 'Apply Pricing to Account Stats', - applyPricingToAccountStatsDesc: 'When enabled, account statistics cost will use channel model pricing. Account rate multiplier still applies.', + applyPricingToAccountStatsDesc: 'When enabled, custom account stats model pricing rules will be applied.', accountStatsPricingRules: 'Custom Account Stats Pricing Rules', addRule: 'Add Rule', noRulesConfigured: 'No custom rules configured. Channel model pricing above will be used.', diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index 64d42c12..61d43a37 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -1956,7 +1956,7 @@ export default { pricingEntry: '定价配置', noModels: '未添加模型', applyPricingToAccountStats: '应用模型定价到账号统计', - applyPricingToAccountStatsDesc: '启用后,账号统计费用将使用渠道模型定价计算。账号自身的统计倍率仍然生效。', + applyPricingToAccountStatsDesc: '启用后将支持自定义账号统计的模型价格', accountStatsPricingRules: '自定义账号统计定价规则', addRule: '添加规则', noRulesConfigured: '未配置自定义规则,将使用上方的模型定价。',