feat(gemini): 补齐 Gemini 4 Argon 计费

- 内置兜底价:gemini-4-argon 为 $2 输入 / $10 输出 / $0.10 缓存读取(每百万
  token,优惠价有效期至 2026-11-30,2026-12-01 起为 $4 / $20 / $0.20);
  Cyber 变体(gemini-4-argon-cyber-permissive)不参与优惠,为 $4 / $20 / $0.20,
  匹配时先于基础型号判断
- Antigravity 的 -low / -medium / -high 档位名归一到基础价
- 本地价格快照新增两个不带前缀的条目,含 Priority 档价格;所有提示词长度同价,
  不带长上下文阶梯;只支持隐式缓存,不带存储价
This commit is contained in:
feeeei
2026-10-14 14:22:17 +08:00
parent 9c2cca9e62
commit c6c9935ae3
5 changed files with 159 additions and 7 deletions
@@ -524,6 +524,27 @@ func (s *BillingService) initFallbackPricing() {
SupportsCacheBreakdown: false,
}
// Gemini 4 Argon (Google AI pricing: $2 input / $10 output / $0.10 cached
// input per MTok, promotional through 2026-11-30; official rates double to
// $4/$20/$0.20 from 2026-12-01). One rate covers every prompt length.
// Antigravity's -high/-low/-medium aliases are matched below so unavailable
// remote pricing never records token-bearing requests at $0.
s.fallbackPrices["gemini-4-argon"] = &ModelPricing{
InputPricePerToken: 2e-6,
OutputPricePerToken: 10e-6,
CacheReadPricePerToken: 0.1e-6,
SupportsCacheBreakdown: false,
}
// Gemini 4 Argon Cyber Permissive (Vertex AI pricing: $4 input / $20 output /
// $0.20 cached input per MTok). The Gemini 4 Argon promotion does not apply.
s.fallbackPrices["gemini-4-argon-cyber-permissive"] = &ModelPricing{
InputPricePerToken: 4e-6,
OutputPricePerToken: 20e-6,
CacheReadPricePerToken: 0.2e-6,
SupportsCacheBreakdown: false,
}
// OpenAI GPT-5.4(业务指定价格)
s.fallbackPrices["gpt-5.4"] = &ModelPricing{
InputPricePerToken: 2.5e-6, // $2.5 per MTok
@@ -1082,6 +1103,13 @@ func (s *BillingService) getFallbackPricing(model string) *ModelPricing {
if strings.Contains(modelLower, "gemini-3.8-flash") || strings.Contains(modelLower, "gemini-3-8-flash") {
return s.fallbackPrices["gemini-3.8-flash"]
}
// Cyber 变体不享受 Gemini 4 Argon 的优惠价,必须先于 "gemini-4-argon" 判。
if strings.Contains(modelLower, "gemini-4-argon-cyber") {
return s.fallbackPrices["gemini-4-argon-cyber-permissive"]
}
if strings.Contains(modelLower, "gemini-4-argon") {
return s.fallbackPrices["gemini-4-argon"]
}
// DeepSeek 系列:官方模型 V4 Pro/Flash(含 vision-exp)按各自价卡;
// 其余 deepseek-*(含已停服的 deepseek-chat / deepseek-reasoner 与未知型号)
@@ -43,7 +43,10 @@ func TestBuiltinGeminiCacheStoragePrice(t *testing.T) {
{"gemini-flash-latest", 1e-6},
{"gemini-flash-lite-latest", 1e-6},
{"gemini-pro-latest", 4.5e-6},
// 无存储价
// 无存储价(Gemini 4 Argon 仅支持隐式缓存)
{"gemini-4-argon", 0},
{"gemini-4-argon-high", 0},
{"gemini-4-argon-cyber-permissive", 0},
{"gemini-embedding-001", 0},
{"gemini-unknown-model", 0},
{"claude-sonnet-5-5", 0},
+6 -6
View File
@@ -1306,7 +1306,7 @@ func (s *PricingService) buildModelLookupCandidates(modelLower string) []string
normalized := normalizeModelNameForPricing(modelLower)
// A tier-specific entry should take precedence when the pricing catalog gains
// one later. Antigravity's Gemini Flash thinking tiers share the base rate,
// one later. Antigravity's Gemini thinking tiers share the base rate,
// so the normalized base remains the fallback after the exact aliases.
candidates := rawCandidates
if normalizeGeminiThinkingTierAlias(lastSegment(modelLower)) != lastSegment(modelLower) {
@@ -1368,12 +1368,12 @@ func normalizeModelNameForPricing(model string) string {
return normalizeGeminiThinkingTierAlias(model)
}
// normalizeGeminiThinkingTierAlias maps Antigravity's Gemini Flash
// thinking-tier model IDs to the public base model. The tier controls reasoning
// behavior, not the published token rate, so this keeps -high/-low/-medium and
// -tiered requests on the corresponding base model's price card.
// normalizeGeminiThinkingTierAlias maps Antigravity's Gemini thinking-tier
// model IDs to the public base model. The tier controls reasoning behavior, not
// the published token rate, so this keeps -high/-low/-medium and -tiered
// requests on the corresponding base model's price card.
func normalizeGeminiThinkingTierAlias(model string) string {
for _, baseModel := range []string{"gemini-3.6-flash", "gemini-3.7-flash", "gemini-3.8-flash"} {
for _, baseModel := range []string{"gemini-3.6-flash", "gemini-3.7-flash", "gemini-3.8-flash", "gemini-4-argon"} {
for _, tier := range []string{"-high", "-low", "-medium", "-tiered"} {
if model == baseModel+tier {
return baseModel
@@ -732,6 +732,93 @@ func TestDefaultPricingIncludesGemini36FlashRates(t *testing.T) {
}
}
func TestPricingService_Gemini4ArgonThinkingTiersUseBasePricing(t *testing.T) {
basePricing := &LiteLLMModelPricing{InputCostPerToken: 2e-6, OutputCostPerToken: 10e-6, CacheReadInputTokenCost: 0.1e-6}
cyberPricing := &LiteLLMModelPricing{InputCostPerToken: 4e-6, OutputCostPerToken: 20e-6, CacheReadInputTokenCost: 0.2e-6}
svc := &PricingService{pricingData: map[string]*LiteLLMModelPricing{
"gemini-4-argon": basePricing,
"gemini-4-argon-cyber-permissive": cyberPricing,
}}
for _, model := range []string{
"gemini-4-argon",
"gemini-4-argon-high",
"gemini-4-argon-low",
"gemini-4-argon-medium",
"models/gemini-4-argon-high",
"projects/p/locations/global/publishers/google/models/gemini-4-argon",
} {
t.Run(model, func(t *testing.T) {
require.Same(t, basePricing, svc.GetModelPricing(model))
})
}
require.Same(t, cyberPricing, svc.GetModelPricing("gemini-4-argon-cyber-permissive"))
}
func TestBillingService_Gemini4ArgonFallbacksAreBillable(t *testing.T) {
svc := NewBillingService(&config.Config{}, nil)
tokens := UsageTokens{InputTokens: 1_000_000, OutputTokens: 1_000_000, CacheReadTokens: 1_000_000}
for _, tc := range []struct {
model string
input, output, read float64
}{
{"gemini-4-argon", 2, 10, 0.1},
{"gemini-4-argon-high", 2, 10, 0.1},
{"gemini-4-argon-low", 2, 10, 0.1},
{"gemini-4-argon-medium", 2, 10, 0.1},
{"models/gemini-4-argon", 2, 10, 0.1},
{"gemini-4-argon-cyber-permissive", 4, 20, 0.2},
{"gemini-4-argon-cyber", 4, 20, 0.2},
{"publishers/google/models/gemini-4-argon-cyber-permissive", 4, 20, 0.2},
} {
t.Run(tc.model, func(t *testing.T) {
cost, err := svc.CalculateCost(tc.model, tokens, 1)
require.NoError(t, err)
require.InDelta(t, tc.input, cost.InputCost, 1e-12)
require.InDelta(t, tc.output, cost.OutputCost, 1e-12)
require.InDelta(t, tc.read, cost.CacheReadCost, 1e-12)
require.InDelta(t, tc.input+tc.output+tc.read, cost.TotalCost, 1e-12)
})
}
}
func TestDefaultPricingIncludesGemini4ArgonRates(t *testing.T) {
data, err := os.ReadFile(filepath.Join("..", "..", "resources", "model-pricing", "model_prices_and_context_window.json"))
require.NoError(t, err)
pricingSvc := &PricingService{}
pricingData, err := pricingSvc.parsePricingData(data)
require.NoError(t, err)
pricingSvc.pricingData = pricingData
for _, tc := range []struct {
model string
provider string
input, output, read float64
}{
{"gemini-4-argon", "gemini", 2e-6, 10e-6, 0.1e-6},
{"gemini-4-argon-high", "gemini", 2e-6, 10e-6, 0.1e-6},
{"gemini-4-argon-cyber-permissive", "vertex_ai-language-models", 4e-6, 20e-6, 0.2e-6},
} {
t.Run(tc.model, func(t *testing.T) {
pricing := pricingSvc.GetModelPricing(tc.model)
require.NotNil(t, pricing)
require.Equal(t, tc.provider, pricing.LiteLLMProvider)
require.InDelta(t, tc.input, pricing.InputCostPerToken, 1e-15)
require.InDelta(t, tc.output, pricing.OutputCostPerToken, 1e-15)
require.InDelta(t, tc.read, pricing.CacheReadInputTokenCost, 1e-15)
// Priority 档为标准价的 1.8 倍;单一价档不分提示词长度,显式缓存不可用故无存储价。
require.InDelta(t, tc.input*1.8, pricing.InputCostPerTokenPriority, 1e-15)
require.InDelta(t, tc.output*1.8, pricing.OutputCostPerTokenPriority, 1e-15)
require.InDelta(t, tc.read*1.8, pricing.CacheReadInputTokenCostPriority, 1e-15)
require.Zero(t, pricing.LongContextInputTokenThreshold)
require.Zero(t, pricing.CacheStorageCostPerTokenPerHour)
})
}
require.Contains(t, pricingSvc.ListModelNamesByProvider("gemini"), "gemini-4-argon")
}
func TestDefaultPricingUsesCurrentCodexAutoReviewBaseRates(t *testing.T) {
data, err := os.ReadFile(filepath.Join("..", "..", "resources", "model-pricing", "model_prices_and_context_window.json"))
require.NoError(t, err)
@@ -2666,6 +2666,40 @@
"supports_reasoning": true,
"supports_service_tier": true
},
"gemini-4-argon": {
"cache_read_input_token_cost": 1e-07,
"cache_read_input_token_cost_priority": 1.8e-07,
"input_cost_per_token": 2e-06,
"input_cost_per_token_priority": 3.6e-06,
"litellm_provider": "gemini",
"max_input_tokens": 1048576,
"max_output_tokens": 65536,
"max_tokens": 65536,
"mode": "chat",
"output_cost_per_token": 1e-05,
"output_cost_per_token_priority": 1.8e-05,
"source": "https://ai.google.dev/gemini-api/docs/pricing",
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_service_tier": true
},
"gemini-4-argon-cyber-permissive": {
"cache_read_input_token_cost": 2e-07,
"cache_read_input_token_cost_priority": 3.6e-07,
"input_cost_per_token": 4e-06,
"input_cost_per_token_priority": 7.2e-06,
"litellm_provider": "vertex_ai-language-models",
"max_input_tokens": 1048576,
"max_output_tokens": 65536,
"max_tokens": 65536,
"mode": "chat",
"output_cost_per_token": 2e-05,
"output_cost_per_token_priority": 3.6e-05,
"source": "https://cloud.google.com/gemini-enterprise-agent-platform/generative-ai/pricing",
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_service_tier": true
},
"gemini-embedding-001": {
"input_cost_per_token": 1.5e-07,
"litellm_provider": "vertex_ai-embedding-models",