From 9eabd2a5b7b1f25e8bfaccd8716392819ddbf416 Mon Sep 17 00:00:00 2001 From: qiujun Date: Sun, 30 Aug 2026 22:56:28 +0800 Subject: [PATCH 1/2] fix(billing): apply model pricing policies to account stats cost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolveAccountStatsCost 的优先级 3 维护了第二份"单价 × token 数"实现,每加一个 定价特性都要手工镜像:service tier 与长上下文分别由 9261dd77、25e6688a 补齐, 而 b5827cfd 引入的 DeepSeek 官方峰谷一直没有生效,高峰时段账号成本最多低估一半。 改为复用 CalculateCostWithServiceTier。channelPricing 仍为 nil,优先级 3 的语义 不变:只取模型定价文件、不引入渠道自定义定价;低谷时段结果与原算式完全一致。 Co-Authored-By: Claude Opus 5 --- .../internal/service/account_stats_pricing.go | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/backend/internal/service/account_stats_pricing.go b/backend/internal/service/account_stats_pricing.go index 8d5bc144fcad..af82bc15d64d 100644 --- a/backend/internal/service/account_stats_pricing.go +++ b/backend/internal/service/account_stats_pricing.go @@ -62,29 +62,17 @@ func resolveAccountStatsCost( } // tryModelFilePricing 使用模型定价文件(LiteLLM/fallback)中的价格计算费用。 +// 与用户计费共用同一条定价管线,避免这里维护第二份"单价 × token 数"实现后, +// 每加一个定价特性都要手工镜像一次。channelPricing 为 nil,保持优先级 3 的 +// 语义:只取模型定价文件,不引入渠道自定义定价。 func tryModelFilePricing(billingService *BillingService, model string, tokens UsageTokens, serviceTier string) *float64 { - pricing, err := billingService.GetModelPricing(model) - if err != nil || pricing == nil { - return nil - } - normalizedTier := normalizeBillingServiceTier(serviceTier) - if normalizedTier == "priority" || normalizedTier == "fast" || normalizedTier == "flex" || - billingService.shouldApplySessionLongContextPricing(tokens, pricing) { - breakdown, err := billingService.CalculateCostWithServiceTier(model, tokens, 1, normalizedTier) - if err != nil || breakdown == nil || breakdown.TotalCost <= 0 { - return nil - } - return &breakdown.TotalCost - } - cost := float64(tokens.InputTokens)*pricing.InputPricePerToken + - float64(tokens.OutputTokens)*pricing.OutputPricePerToken + - float64(tokens.CacheCreationTokens)*pricing.CacheCreationPricePerToken + - float64(tokens.CacheReadTokens)*pricing.CacheReadPricePerToken + - float64(tokens.ImageOutputTokens)*pricing.ImageOutputPricePerToken - if cost <= 0 { + breakdown, err := billingService.CalculateCostWithServiceTier( + model, tokens, 1, normalizeBillingServiceTier(serviceTier), + ) + if err != nil || breakdown == nil || breakdown.TotalCost <= 0 { return nil } - return &cost + return &breakdown.TotalCost } // tryCustomRules 遍历自定义规则,按数组顺序先命中为准。 From e7c029875c2cbc7bb00b748651a5df9768e9bd24 Mon Sep 17 00:00:00 2001 From: qiujun Date: Mon, 31 Aug 2026 12:33:06 +0800 Subject: [PATCH 2/2] =?UTF-8?q?test(billing):=20=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E6=8C=89=20output=20=E5=AD=90=E9=9B=86?= =?UTF-8?q?=E8=AE=A1=E4=BB=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一条改用统一定价管线后,TestTryModelFilePricing_WithImageOutput 挂在 0.3 vs 0.28:原算式把 ImageOutputTokens 当 OutputTokens 之外的额外量叠加,等于按文本 output 价和图片价各收一次,测试固化了这个重复计费。 ImageOutputTokens 本就是 OutputTokens 的子集(Gemini 取自 candidatesTokensDetails 的 IMAGE 档,OpenAI 取自 output_tokens_details),统一管线先扣除再按图片单价计, 未配图片价时回退到 output 价、总额不变,正是这个语义。期望改为 0.28。 影响:涉及图片输出的模型,账号统计成本会降低"图片 token × 文本 output 单价"。 Co-Authored-By: Claude Opus 5 --- backend/internal/service/account_stats_pricing_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/internal/service/account_stats_pricing_test.go b/backend/internal/service/account_stats_pricing_test.go index 1bd28896fcba..84ca0897eccc 100644 --- a/backend/internal/service/account_stats_pricing_test.go +++ b/backend/internal/service/account_stats_pricing_test.go @@ -594,8 +594,9 @@ func TestTryModelFilePricing_WithImageOutput(t *testing.T) { } result := tryModelFilePricing(bs, "claude-sonnet-4", tokens, "") require.NotNil(t, result) - // 100*0.001 + 50*0.002 + 10*0.01 = 0.1 + 0.1 + 0.1 = 0.3 - require.InDelta(t, 0.3, *result, 1e-12) + // ImageOutputTokens 是 OutputTokens 的子集,先扣除再按图片单价计。 + // 100*0.001 + (50-10)*0.002 + 10*0.01 = 0.1 + 0.08 + 0.1 = 0.28 + require.InDelta(t, 0.28, *result, 1e-12) } func TestTryModelFilePricing_WithCacheTokens(t *testing.T) {