mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
fix(billing): address Copilot review feedback
应 PR #3243 review 修正三项问题: 1. 删除 DeepSeek V4 fallback 定价的重复块(cherry-pick 时 的旧位置 + 新国产 LLM 分段都写了一遍,后写覆盖前写, 当前值相同所以无 bug,但冗余,未来改一处易漏另一处)。 2. 删除 minimax-m3 匹配中的死代码——modelLower 已经 strings.ToLower(),'MiniMax-M3' 字面量永远不可能匹中。 3. 测试结构改 *float64 替代 0 sentinel,让 free-tier 模型(GLM-4.5-Flash / GLM-4.7-Flash)的 0 价能被 真正断言而非默默跳过;定义内联 floatPtr 辅助函数。
This commit is contained in:
@@ -294,24 +294,6 @@ func (s *BillingService) initFallbackPricing() {
|
||||
CacheReadPricePerTokenPriority: 0.35e-6,
|
||||
SupportsCacheBreakdown: false,
|
||||
}
|
||||
// DeepSeek V4 Pro
|
||||
// Source: https://api-docs.deepseek.com/quick_start/pricing
|
||||
s.fallbackPrices["deepseek-v4-pro"] = &ModelPricing{
|
||||
InputPricePerToken: 4.35e-7, // $0.435 per MTok
|
||||
OutputPricePerToken: 8.7e-7, // $0.87 per MTok
|
||||
CacheReadPricePerToken: 3.625e-9, // $0.003625 per MTok
|
||||
SupportsCacheBreakdown: false,
|
||||
}
|
||||
|
||||
// DeepSeek V4 Flash
|
||||
// Source: https://api-docs.deepseek.com/quick_start/pricing
|
||||
s.fallbackPrices["deepseek-v4-flash"] = &ModelPricing{
|
||||
InputPricePerToken: 1.4e-7, // $0.14 per MTok
|
||||
OutputPricePerToken: 2.8e-7, // $0.28 per MTok
|
||||
CacheReadPricePerToken: 2.8e-9, // $0.0028 per MTok
|
||||
SupportsCacheBreakdown: false,
|
||||
}
|
||||
|
||||
// Codex 族兜底统一按 GPT-5.3 Codex 价格计费
|
||||
s.fallbackPrices["gpt-5.3-codex"] = &ModelPricing{
|
||||
InputPricePerToken: 1.5e-6, // $1.5 per MTok
|
||||
@@ -610,7 +592,7 @@ func (s *BillingService) getFallbackPricing(model string) *ModelPricing {
|
||||
}
|
||||
|
||||
// MiniMax M 系列(M3 / M2.7 / M2.5 / M2.1 / M2;含 highspeed 变体)
|
||||
if strings.Contains(modelLower, "minimax-m3") || strings.Contains(modelLower, "MiniMax-M3") {
|
||||
if strings.Contains(modelLower, "minimax-m3") {
|
||||
return s.fallbackPrices["minimax-m3"]
|
||||
}
|
||||
if strings.Contains(modelLower, "minimax-m2.7-highspeed") || strings.Contains(modelLower, "minimax-m2-7-highspeed") {
|
||||
|
||||
@@ -332,13 +332,15 @@ func TestCalculateCost_LongContextAppliesMultiplierToCacheCreation5mAnd1h(t *tes
|
||||
func TestGetFallbackPricing_FamilyMatching(t *testing.T) {
|
||||
svc := newTestBillingService()
|
||||
|
||||
// expectedOutput / expectedCacheRead 为 0 时跳过该字段断言(保持与原有用例兼容)。
|
||||
floatPtr := func(v float64) *float64 { return &v }
|
||||
|
||||
// expectedOutput / expectedCacheRead 为 nil 时跳过该字段断言(保持与原有用例兼容)。
|
||||
tests := []struct {
|
||||
name string
|
||||
model string
|
||||
expectedInput float64
|
||||
expectedOutput float64
|
||||
expectedCacheRead float64
|
||||
expectedOutput *float64
|
||||
expectedCacheRead *float64
|
||||
expectNilPricing bool
|
||||
}{
|
||||
{name: "empty model", model: " ", expectNilPricing: true},
|
||||
@@ -359,29 +361,29 @@ func TestGetFallbackPricing_FamilyMatching(t *testing.T) {
|
||||
name: "deepseek v4 pro",
|
||||
model: "deepseek-v4-pro",
|
||||
expectedInput: 4.35e-7,
|
||||
expectedOutput: 8.7e-7,
|
||||
expectedCacheRead: 3.625e-9,
|
||||
expectedOutput: floatPtr(8.7e-7),
|
||||
expectedCacheRead: floatPtr(3.625e-9),
|
||||
},
|
||||
{
|
||||
name: "deepseek v4 flash",
|
||||
model: "deepseek-v4-flash",
|
||||
expectedInput: 1.4e-7,
|
||||
expectedOutput: 2.8e-7,
|
||||
expectedCacheRead: 2.8e-9,
|
||||
expectedOutput: floatPtr(2.8e-7),
|
||||
expectedCacheRead: floatPtr(2.8e-9),
|
||||
},
|
||||
{
|
||||
name: "deepseek chat alias → flash",
|
||||
model: "deepseek-chat",
|
||||
expectedInput: 1.4e-7,
|
||||
expectedOutput: 2.8e-7,
|
||||
expectedCacheRead: 2.8e-9,
|
||||
expectedOutput: floatPtr(2.8e-7),
|
||||
expectedCacheRead: floatPtr(2.8e-9),
|
||||
},
|
||||
{
|
||||
name: "deepseek reasoner alias → flash",
|
||||
model: "deepseek-reasoner",
|
||||
expectedInput: 1.4e-7,
|
||||
expectedOutput: 2.8e-7,
|
||||
expectedCacheRead: 2.8e-9,
|
||||
expectedOutput: floatPtr(2.8e-7),
|
||||
expectedCacheRead: floatPtr(2.8e-9),
|
||||
},
|
||||
|
||||
// ---- 智谱 GLM(z.ai USD 口径)----
|
||||
@@ -389,100 +391,99 @@ func TestGetFallbackPricing_FamilyMatching(t *testing.T) {
|
||||
name: "glm 5.1 flagship",
|
||||
model: "glm-5.1",
|
||||
expectedInput: 1.4e-6,
|
||||
expectedOutput: 4.4e-6,
|
||||
expectedCacheRead: 0.26e-6,
|
||||
expectedOutput: floatPtr(4.4e-6),
|
||||
expectedCacheRead: floatPtr(0.26e-6),
|
||||
},
|
||||
{
|
||||
name: "glm 5 base",
|
||||
model: "glm-5",
|
||||
expectedInput: 1e-6,
|
||||
expectedOutput: 3.2e-6,
|
||||
expectedCacheRead: 0.2e-6,
|
||||
expectedOutput: floatPtr(3.2e-6),
|
||||
expectedCacheRead: floatPtr(0.2e-6),
|
||||
},
|
||||
{
|
||||
name: "glm 5 turbo",
|
||||
model: "glm-5-turbo",
|
||||
expectedInput: 1.2e-6,
|
||||
expectedOutput: 4e-6,
|
||||
expectedCacheRead: 0.24e-6,
|
||||
expectedOutput: floatPtr(4e-6),
|
||||
expectedCacheRead: floatPtr(0.24e-6),
|
||||
},
|
||||
{
|
||||
name: "glm 4.7",
|
||||
model: "glm-4.7",
|
||||
expectedInput: 0.6e-6,
|
||||
expectedOutput: 2.2e-6,
|
||||
expectedCacheRead: 0.11e-6,
|
||||
expectedOutput: floatPtr(2.2e-6),
|
||||
expectedCacheRead: floatPtr(0.11e-6),
|
||||
},
|
||||
{
|
||||
name: "glm 4.6",
|
||||
model: "glm-4.6",
|
||||
expectedInput: 0.6e-6,
|
||||
expectedOutput: 2.2e-6,
|
||||
expectedCacheRead: 0.11e-6,
|
||||
expectedOutput: floatPtr(2.2e-6),
|
||||
expectedCacheRead: floatPtr(0.11e-6),
|
||||
},
|
||||
{
|
||||
name: "glm 4.5",
|
||||
model: "glm-4.5",
|
||||
expectedInput: 0.6e-6,
|
||||
expectedOutput: 2.2e-6,
|
||||
expectedCacheRead: 0.11e-6,
|
||||
expectedOutput: floatPtr(2.2e-6),
|
||||
expectedCacheRead: floatPtr(0.11e-6),
|
||||
},
|
||||
{
|
||||
name: "glm 4.5-x premium",
|
||||
model: "glm-4.5-x",
|
||||
expectedInput: 2.2e-6,
|
||||
expectedOutput: 8.9e-6,
|
||||
expectedCacheRead: 0.45e-6,
|
||||
expectedOutput: floatPtr(8.9e-6),
|
||||
expectedCacheRead: floatPtr(0.45e-6),
|
||||
},
|
||||
{
|
||||
name: "glm 4.5-air lightweight",
|
||||
model: "glm-4.5-air",
|
||||
expectedInput: 0.2e-6,
|
||||
expectedOutput: 1.1e-6,
|
||||
expectedCacheRead: 0.03e-6,
|
||||
expectedOutput: floatPtr(1.1e-6),
|
||||
expectedCacheRead: floatPtr(0.03e-6),
|
||||
},
|
||||
{
|
||||
name: "glm 4.7-flashx",
|
||||
model: "glm-4.7-flashx",
|
||||
expectedInput: 0.07e-6,
|
||||
expectedOutput: 0.4e-6,
|
||||
expectedCacheRead: 0.01e-6,
|
||||
expectedOutput: floatPtr(0.4e-6),
|
||||
expectedCacheRead: floatPtr(0.01e-6),
|
||||
},
|
||||
{
|
||||
name: "glm 4.5-flash free tier",
|
||||
model: "glm-4.5-flash",
|
||||
expectedInput: 0, // Free tier on z.ai
|
||||
expectedOutput: 0,
|
||||
expectedCacheRead: 0,
|
||||
expectedOutput: floatPtr(0),
|
||||
expectedCacheRead: floatPtr(0),
|
||||
},
|
||||
{
|
||||
name: "glm 4.7-flash free tier",
|
||||
model: "glm-4.7-flash",
|
||||
expectedInput: 0,
|
||||
expectedOutput: 0,
|
||||
expectedCacheRead: 0,
|
||||
expectedOutput: floatPtr(0),
|
||||
expectedCacheRead: floatPtr(0),
|
||||
},
|
||||
{
|
||||
name: "glm 4-32b legacy",
|
||||
model: "glm-4-32b-0414-128k",
|
||||
expectedInput: 0.1e-6,
|
||||
expectedOutput: 0.1e-6,
|
||||
expectedCacheRead: 0,
|
||||
name: "glm 4-32b legacy",
|
||||
model: "glm-4-32b-0414-128k",
|
||||
expectedInput: 0.1e-6,
|
||||
expectedOutput: floatPtr(0.1e-6),
|
||||
},
|
||||
// 关键:5.1 必须先于 5 匹配(避免被 glm-5 抢走)
|
||||
{
|
||||
name: "glm 5.1 vs glm 5 ordering (verbatim 5.1)",
|
||||
model: "glm-5.1",
|
||||
expectedInput: 1.4e-6, // = glm-5.1 价格
|
||||
expectedOutput: 4.4e-6,
|
||||
expectedCacheRead: 0.26e-6,
|
||||
expectedOutput: floatPtr(4.4e-6),
|
||||
expectedCacheRead: floatPtr(0.26e-6),
|
||||
},
|
||||
{
|
||||
name: "glm 4.5-air vs glm 4.5 ordering",
|
||||
model: "glm-4.5-air",
|
||||
expectedInput: 0.2e-6, // = glm-4.5-air 价格(不是 glm-4.5 的 0.6e-6)
|
||||
expectedOutput: 1.1e-6,
|
||||
expectedCacheRead: 0.03e-6,
|
||||
expectedOutput: floatPtr(1.1e-6),
|
||||
expectedCacheRead: floatPtr(0.03e-6),
|
||||
},
|
||||
|
||||
// ---- 月之暗面 Kimi ----
|
||||
@@ -490,44 +491,44 @@ func TestGetFallbackPricing_FamilyMatching(t *testing.T) {
|
||||
name: "kimi k2.6 flagship",
|
||||
model: "kimi-k2.6",
|
||||
expectedInput: 0.95e-6,
|
||||
expectedOutput: 4e-6,
|
||||
expectedCacheRead: 0.15e-6,
|
||||
expectedOutput: floatPtr(4e-6),
|
||||
expectedCacheRead: floatPtr(0.15e-6),
|
||||
},
|
||||
{
|
||||
name: "kimi k2.5",
|
||||
model: "kimi-k2.5",
|
||||
expectedInput: 0.60e-6,
|
||||
expectedOutput: 3e-6,
|
||||
expectedCacheRead: 0.098e-6,
|
||||
expectedOutput: floatPtr(3e-6),
|
||||
expectedCacheRead: floatPtr(0.098e-6),
|
||||
},
|
||||
{
|
||||
name: "kimi k2-thinking",
|
||||
model: "kimi-k2-thinking",
|
||||
expectedInput: 0.56e-6,
|
||||
expectedOutput: 2.24e-6,
|
||||
expectedCacheRead: 0.14e-6,
|
||||
expectedOutput: floatPtr(2.24e-6),
|
||||
expectedCacheRead: floatPtr(0.14e-6),
|
||||
},
|
||||
{
|
||||
name: "kimi k2 base",
|
||||
model: "kimi-k2",
|
||||
expectedInput: 0.56e-6,
|
||||
expectedOutput: 2.24e-6,
|
||||
expectedCacheRead: 0.14e-6,
|
||||
expectedOutput: floatPtr(2.24e-6),
|
||||
expectedCacheRead: floatPtr(0.14e-6),
|
||||
},
|
||||
// 关键:k2.6 / k2.5 / k2-thinking 必须先于 k2 匹配
|
||||
{
|
||||
name: "kimi k2.6 vs k2 ordering",
|
||||
model: "kimi-k2.6",
|
||||
expectedInput: 0.95e-6, // = k2.6 不是 k2 的 0.56e-6
|
||||
expectedOutput: 4e-6,
|
||||
expectedCacheRead: 0.15e-6,
|
||||
expectedOutput: floatPtr(4e-6),
|
||||
expectedCacheRead: floatPtr(0.15e-6),
|
||||
},
|
||||
{
|
||||
name: "kimi k2 thinking hyphenated variant",
|
||||
model: "kimi-k2-thinking-preview",
|
||||
expectedInput: 0.56e-6,
|
||||
expectedOutput: 2.24e-6,
|
||||
expectedCacheRead: 0.14e-6,
|
||||
expectedOutput: floatPtr(2.24e-6),
|
||||
expectedCacheRead: floatPtr(0.14e-6),
|
||||
},
|
||||
|
||||
// ---- MiniMax M 系列 ----
|
||||
@@ -535,46 +536,46 @@ func TestGetFallbackPricing_FamilyMatching(t *testing.T) {
|
||||
name: "minimax m3",
|
||||
model: "minimax-m3",
|
||||
expectedInput: 0.60e-6,
|
||||
expectedOutput: 2.40e-6,
|
||||
expectedCacheRead: 0.12e-6,
|
||||
expectedOutput: floatPtr(2.40e-6),
|
||||
expectedCacheRead: floatPtr(0.12e-6),
|
||||
},
|
||||
{
|
||||
name: "minimax m3 long ctx boundary keep standard tier",
|
||||
model: "minimax-m3-long", // 仍按 standard tier (≤512K)
|
||||
expectedInput: 0.60e-6,
|
||||
expectedOutput: 2.40e-6,
|
||||
expectedCacheRead: 0.12e-6,
|
||||
expectedOutput: floatPtr(2.40e-6),
|
||||
expectedCacheRead: floatPtr(0.12e-6),
|
||||
},
|
||||
{
|
||||
name: "minimax m2.7",
|
||||
model: "minimax-m2.7",
|
||||
expectedInput: 0.30e-6,
|
||||
expectedOutput: 1.20e-6,
|
||||
expectedCacheRead: 0.06e-6,
|
||||
expectedOutput: floatPtr(1.20e-6),
|
||||
expectedCacheRead: floatPtr(0.06e-6),
|
||||
},
|
||||
{
|
||||
name: "minimax m2.7 highspeed",
|
||||
model: "minimax-m2.7-highspeed",
|
||||
expectedInput: 0.60e-6,
|
||||
expectedOutput: 2.40e-6,
|
||||
expectedCacheRead: 0.06e-6,
|
||||
expectedOutput: floatPtr(2.40e-6),
|
||||
expectedCacheRead: floatPtr(0.06e-6),
|
||||
},
|
||||
{
|
||||
name: "minimax m2.5",
|
||||
model: "minimax-m2.5",
|
||||
expectedInput: 0.30e-6,
|
||||
expectedOutput: 1.20e-6,
|
||||
expectedCacheRead: 0.03e-6,
|
||||
expectedOutput: floatPtr(1.20e-6),
|
||||
expectedCacheRead: floatPtr(0.03e-6),
|
||||
},
|
||||
{
|
||||
name: "minimax m2 legacy",
|
||||
model: "minimax-m2",
|
||||
expectedInput: 0.30e-6,
|
||||
expectedOutput: 1.20e-6,
|
||||
expectedCacheRead: 0.03e-6,
|
||||
expectedOutput: floatPtr(1.20e-6),
|
||||
expectedCacheRead: floatPtr(0.03e-6),
|
||||
},
|
||||
|
||||
// ---- 负向用例:未覆盖的国产厂商 / alias 不应误计价 ----
|
||||
// ---- 负向用例 ----
|
||||
{name: "qwen unknown no fallback", model: "qwen-max", expectNilPricing: true},
|
||||
{name: "doubao unknown no fallback", model: "doubao-pro", expectNilPricing: true},
|
||||
{name: "hunyuan unknown no fallback", model: "hunyuan-t1", expectNilPricing: true},
|
||||
@@ -585,8 +586,8 @@ func TestGetFallbackPricing_FamilyMatching(t *testing.T) {
|
||||
name: "kimi k2-0905-preview implicit fallback to k2",
|
||||
model: "kimi-k2-0905-preview",
|
||||
expectedInput: 0.56e-6,
|
||||
expectedOutput: 2.24e-6,
|
||||
expectedCacheRead: 0.14e-6,
|
||||
expectedOutput: floatPtr(2.24e-6),
|
||||
expectedCacheRead: floatPtr(0.14e-6),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -599,12 +600,12 @@ func TestGetFallbackPricing_FamilyMatching(t *testing.T) {
|
||||
}
|
||||
require.NotNil(t, pricing)
|
||||
require.InDelta(t, tt.expectedInput, pricing.InputPricePerToken, 1e-12)
|
||||
if tt.expectedOutput != 0 {
|
||||
require.InDelta(t, tt.expectedOutput, pricing.OutputPricePerToken, 1e-12,
|
||||
if tt.expectedOutput != nil {
|
||||
require.InDelta(t, *tt.expectedOutput, pricing.OutputPricePerToken, 1e-12,
|
||||
"OutputPricePerToken mismatch for %s", tt.model)
|
||||
}
|
||||
if tt.expectedCacheRead != 0 {
|
||||
require.InDelta(t, tt.expectedCacheRead, pricing.CacheReadPricePerToken, 1e-14,
|
||||
if tt.expectedCacheRead != nil {
|
||||
require.InDelta(t, *tt.expectedCacheRead, pricing.CacheReadPricePerToken, 1e-14,
|
||||
"CacheReadPricePerToken mismatch for %s", tt.model)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user