"frontend/git@web.lueluesay.top:chenxi/sub2api.git" did not exist on "9742796ee727e678fabf1e4028640a764cbac895"
Commit 11c46068 authored by erio's avatar erio
Browse files

fix(channel): use upstream model for account stats pricing and remove channel pricing fallback

- 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: "启用后将支持自定义账号统计的模型价格"
parent 95f9b27e
...@@ -8,23 +8,18 @@ import ( ...@@ -8,23 +8,18 @@ import (
// resolveAccountStatsCost 计算账号统计定价费用。 // resolveAccountStatsCost 计算账号统计定价费用。
// 返回 nil 表示不覆盖,使用默认公式(total_cost × account_rate_multiplier)。 // 返回 nil 表示不覆盖,使用默认公式(total_cost × account_rate_multiplier)。
// // 仅匹配自定义规则(AccountStatsPricingRules),按数组顺序先命中为准。
// 匹配优先级(先命中为准): // upstreamModel 是最终发往上游的模型 ID,用于匹配自定义规则中的模型定价。
// 1. 自定义规则(AccountStatsPricingRules,按数组顺序遍历)
// 2. 渠道已有的模型定价(ApplyPricingToAccountStats 开启时)
// 3. nil → 走默认公式
func resolveAccountStatsCost( func resolveAccountStatsCost(
ctx context.Context, ctx context.Context,
channelService *ChannelService, channelService *ChannelService,
billingService *BillingService,
accountID int64, accountID int64,
groupID int64, groupID int64,
billingModel string, upstreamModel string,
tokens UsageTokens, tokens UsageTokens,
requestCount int, requestCount int,
serviceTier string,
) *float64 { ) *float64 {
if channelService == nil || billingService == nil { if channelService == nil || upstreamModel == "" {
return nil return nil
} }
channel, err := channelService.GetChannelForGroup(ctx, groupID) channel, err := channelService.GetChannelForGroup(ctx, groupID)
...@@ -33,22 +28,15 @@ func resolveAccountStatsCost( ...@@ -33,22 +28,15 @@ func resolveAccountStatsCost(
} }
platform := channelService.GetGroupPlatform(ctx, groupID) platform := channelService.GetGroupPlatform(ctx, groupID)
modelLower := strings.ToLower(billingModel) return tryCustomRules(channel, accountID, groupID, platform, upstreamModel, tokens, requestCount)
// 优先级 1:自定义规则
if cost := tryCustomRules(channel, accountID, groupID, platform, modelLower, tokens, requestCount); cost != nil {
return cost
}
// 优先级 2:渠道已有模型定价
return tryChannelPricing(ctx, channelService, groupID, billingModel, tokens, requestCount)
} }
// tryCustomRules 遍历自定义规则,按数组顺序先命中为准。 // tryCustomRules 遍历自定义规则,按数组顺序先命中为准。
func tryCustomRules( func tryCustomRules(
channel *Channel, accountID, groupID int64, channel *Channel, accountID, groupID int64,
platform, modelLower string, tokens UsageTokens, requestCount int, platform, model string, tokens UsageTokens, requestCount int,
) *float64 { ) *float64 {
modelLower := strings.ToLower(model)
for _, rule := range channel.AccountStatsPricingRules { for _, rule := range channel.AccountStatsPricingRules {
if !matchAccountStatsRule(&rule, accountID, groupID) { if !matchAccountStatsRule(&rule, accountID, groupID) {
continue continue
...@@ -62,18 +50,6 @@ func tryCustomRules( ...@@ -62,18 +50,6 @@ func tryCustomRules(
return nil 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。 // matchAccountStatsRule 检查规则是否匹配指定的 accountID 和 groupID。
// 匹配条件:accountID ∈ rule.AccountIDs 或 groupID ∈ rule.GroupIDs。 // 匹配条件:accountID ∈ rule.AccountIDs 或 groupID ∈ rule.GroupIDs。
// 如果规则的 AccountIDs 和 GroupIDs 都为空,视为不匹配。 // 如果规则的 AccountIDs 和 GroupIDs 都为空,视为不匹配。
......
...@@ -7581,11 +7581,15 @@ func (s *GatewayService) recordUsageCore(ctx context.Context, input *recordUsage ...@@ -7581,11 +7581,15 @@ func (s *GatewayService) recordUsageCore(ctx context.Context, input *recordUsage
usageLog := s.buildRecordUsageLog(ctx, input, result, apiKey, user, account, subscription, usageLog := s.buildRecordUsageLog(ctx, input, result, apiKey, user, account, subscription,
requestedModel, multiplier, accountRateMultiplier, billingType, cacheTTLOverridden, cost, opts) requestedModel, multiplier, accountRateMultiplier, billingType, cacheTTLOverridden, cost, opts)
// 计算账号统计定价费用 // 计算账号统计定价费用(使用最终上游模型匹配自定义规则)
if apiKey.GroupID != nil { if apiKey.GroupID != nil {
upstreamModel := result.UpstreamModel
if upstreamModel == "" {
upstreamModel = result.Model
}
usageLog.AccountStatsCost = resolveAccountStatsCost( usageLog.AccountStatsCost = resolveAccountStatsCost(
ctx, s.channelService, s.billingService, ctx, s.channelService,
account.ID, *apiKey.GroupID, billingModel, account.ID, *apiKey.GroupID, upstreamModel,
UsageTokens{ UsageTokens{
InputTokens: result.Usage.InputTokens, InputTokens: result.Usage.InputTokens,
OutputTokens: result.Usage.OutputTokens, OutputTokens: result.Usage.OutputTokens,
...@@ -7594,7 +7598,6 @@ func (s *GatewayService) recordUsageCore(ctx context.Context, input *recordUsage ...@@ -7594,7 +7598,6 @@ func (s *GatewayService) recordUsageCore(ctx context.Context, input *recordUsage
ImageOutputTokens: result.Usage.ImageOutputTokens, ImageOutputTokens: result.Usage.ImageOutputTokens,
}, },
1, // requestCount 1, // requestCount
"", // serviceTier: Anthropic 平台不使用 service tier
) )
} }
......
...@@ -4573,12 +4573,16 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec ...@@ -4573,12 +4573,16 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
usageLog.SubscriptionID = &subscription.ID usageLog.SubscriptionID = &subscription.ID
} }
// 计算账号统计定价费用 // 计算账号统计定价费用(使用最终上游模型匹配自定义规则)
if apiKey.GroupID != nil { if apiKey.GroupID != nil {
statsModel := result.UpstreamModel
if statsModel == "" {
statsModel = result.Model
}
usageLog.AccountStatsCost = resolveAccountStatsCost( usageLog.AccountStatsCost = resolveAccountStatsCost(
ctx, s.channelService, s.billingService, ctx, s.channelService,
account.ID, *apiKey.GroupID, billingModel, account.ID, *apiKey.GroupID, statsModel,
tokens, 1, serviceTier, tokens, 1,
) )
} }
......
...@@ -1877,7 +1877,7 @@ export default { ...@@ -1877,7 +1877,7 @@ export default {
pricingEntry: 'Pricing Entry', pricingEntry: 'Pricing Entry',
noModels: 'No models added', noModels: 'No models added',
applyPricingToAccountStats: 'Apply Pricing to Account Stats', 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', accountStatsPricingRules: 'Custom Account Stats Pricing Rules',
addRule: 'Add Rule', addRule: 'Add Rule',
noRulesConfigured: 'No custom rules configured. Channel model pricing above will be used.', noRulesConfigured: 'No custom rules configured. Channel model pricing above will be used.',
......
...@@ -1956,7 +1956,7 @@ export default { ...@@ -1956,7 +1956,7 @@ export default {
pricingEntry: '定价配置', pricingEntry: '定价配置',
noModels: '未添加模型', noModels: '未添加模型',
applyPricingToAccountStats: '应用模型定价到账号统计', applyPricingToAccountStats: '应用模型定价到账号统计',
applyPricingToAccountStatsDesc: '启用后,账号统计费用将使用渠道模型定价计算。账号自身的统计倍率仍然生效。', applyPricingToAccountStatsDesc: '启用后将支持自定义账号统计的模型价格',
accountStatsPricingRules: '自定义账号统计定价规则', accountStatsPricingRules: '自定义账号统计定价规则',
addRule: '添加规则', addRule: '添加规则',
noRulesConfigured: '未配置自定义规则,将使用上方的模型定价。', noRulesConfigured: '未配置自定义规则,将使用上方的模型定价。',
......
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