diff --git a/backend/internal/service/billing_service.go b/backend/internal/service/billing_service.go index e82c38a14f..ae68c93ef6 100644 --- a/backend/internal/service/billing_service.go +++ b/backend/internal/service/billing_service.go @@ -282,10 +282,37 @@ func (s *BillingService) initFallbackPricing() { s.fallbackPrices["gpt-5.5"] = s.fallbackPrices["gpt-5.4"] s.fallbackPrices["gpt-5.5-pro"] = s.fallbackPrices["gpt-5.4"] - // GPT-5.6(sol / terra / luna)暂无独立定价,回退到 GPT-5.4。 - s.fallbackPrices["gpt-5.6-sol"] = s.fallbackPrices["gpt-5.4"] - s.fallbackPrices["gpt-5.6-terra"] = s.fallbackPrices["gpt-5.4"] - s.fallbackPrices["gpt-5.6-luna"] = s.fallbackPrices["gpt-5.4"] + // OpenAI GPT-5.6 官方价格(USD/token)。缓存写入为输入价的 1.25 倍。 + s.fallbackPrices["gpt-5.6-sol"] = &ModelPricing{ + InputPricePerToken: 5e-6, + InputPricePerTokenPriority: 10e-6, + OutputPricePerToken: 30e-6, + OutputPricePerTokenPriority: 60e-6, + CacheCreationPricePerToken: 6.25e-6, + CacheCreationPricePerTokenPriority: 12.5e-6, + CacheReadPricePerToken: 0.5e-6, + CacheReadPricePerTokenPriority: 1e-6, + } + s.fallbackPrices["gpt-5.6-terra"] = &ModelPricing{ + InputPricePerToken: 2.5e-6, + InputPricePerTokenPriority: 5e-6, + OutputPricePerToken: 15e-6, + OutputPricePerTokenPriority: 30e-6, + CacheCreationPricePerToken: 3.125e-6, + CacheCreationPricePerTokenPriority: 6.25e-6, + CacheReadPricePerToken: 0.25e-6, + CacheReadPricePerTokenPriority: 0.5e-6, + } + s.fallbackPrices["gpt-5.6-luna"] = &ModelPricing{ + InputPricePerToken: 1e-6, + InputPricePerTokenPriority: 2e-6, + OutputPricePerToken: 6e-6, + OutputPricePerTokenPriority: 12e-6, + CacheCreationPricePerToken: 1.25e-6, + CacheCreationPricePerTokenPriority: 2.5e-6, + CacheReadPricePerToken: 0.1e-6, + CacheReadPricePerTokenPriority: 0.2e-6, + } s.fallbackPrices["gpt-5.4-mini"] = &ModelPricing{ InputPricePerToken: 7.5e-7, @@ -1066,11 +1093,13 @@ func (s *BillingService) applyModelSpecificPricingPolicy(model string, pricing * return nil } normalized := normalizeKnownOpenAICodexModel(model) - if !isOpenAIGPT54Model(model) { + isGPT56 := isOpenAIGPT56Model(normalized) + usesLegacyLongContextPricing := usesOpenAILegacyLongContextPricing(normalized) + if !isGPT56 && !usesLegacyLongContextPricing { return pricing } - isGPT56 := normalized == "gpt-5.6-sol" || normalized == "gpt-5.6-terra" || normalized == "gpt-5.6-luna" - needsLongContextPolicy := pricing.LongContextInputThreshold <= 0 || pricing.LongContextInputMultiplier <= 0 || pricing.LongContextOutputMultiplier <= 0 + needsLongContextPolicy := usesLegacyLongContextPricing && + (pricing.LongContextInputThreshold <= 0 || pricing.LongContextInputMultiplier <= 0 || pricing.LongContextOutputMultiplier <= 0) needsCacheCreationPolicy := isGPT56 && (pricing.CacheCreationPricePerToken <= 0 || (pricing.InputPricePerTokenPriority > 0 && pricing.CacheCreationPricePerTokenPriority <= 0)) if !needsLongContextPolicy && !needsCacheCreationPolicy { @@ -1079,20 +1108,22 @@ func (s *BillingService) applyModelSpecificPricingPolicy(model string, pricing * cloned := *pricing if isGPT56 { if cloned.CacheCreationPricePerToken <= 0 { - cloned.CacheCreationPricePerToken = cloned.InputPricePerToken + cloned.CacheCreationPricePerToken = cloned.InputPricePerToken * 1.25 } if cloned.CacheCreationPricePerTokenPriority <= 0 { - cloned.CacheCreationPricePerTokenPriority = cloned.InputPricePerTokenPriority + cloned.CacheCreationPricePerTokenPriority = cloned.InputPricePerTokenPriority * 1.25 } } - if cloned.LongContextInputThreshold <= 0 { - cloned.LongContextInputThreshold = openAIGPT54LongContextInputThreshold - } - if cloned.LongContextInputMultiplier <= 0 { - cloned.LongContextInputMultiplier = openAIGPT54LongContextInputMultiplier - } - if cloned.LongContextOutputMultiplier <= 0 { - cloned.LongContextOutputMultiplier = openAIGPT54LongContextOutputMultiplier + if usesLegacyLongContextPricing { + if cloned.LongContextInputThreshold <= 0 { + cloned.LongContextInputThreshold = openAIGPT54LongContextInputThreshold + } + if cloned.LongContextInputMultiplier <= 0 { + cloned.LongContextInputMultiplier = openAIGPT54LongContextInputMultiplier + } + if cloned.LongContextOutputMultiplier <= 0 { + cloned.LongContextOutputMultiplier = openAIGPT54LongContextOutputMultiplier + } } return &cloned } @@ -1108,13 +1139,12 @@ func (s *BillingService) shouldApplySessionLongContextPricing(tokens UsageTokens return totalInputTokens > pricing.LongContextInputThreshold } -func isOpenAIGPT54Model(model string) bool { - // 仅当模型字符串实际属于已知 GPT-5/Codex 族时才做归一判定,避免 - // normalizeCodexModel 的默认兜底把非 OpenAI 模型(claude-*、gemini-*、gpt-4o) - // 误识别为 gpt-5.4。 - normalized := normalizeKnownOpenAICodexModel(model) - return normalized == "gpt-5.4" || normalized == "gpt-5.5" || normalized == "gpt-5.5-pro" || - normalized == "gpt-5.6-sol" || normalized == "gpt-5.6-terra" || normalized == "gpt-5.6-luna" +func isOpenAIGPT56Model(normalized string) bool { + return normalized == "gpt-5.6-sol" || normalized == "gpt-5.6-terra" || normalized == "gpt-5.6-luna" +} + +func usesOpenAILegacyLongContextPricing(normalized string) bool { + return normalized == "gpt-5.4" || normalized == "gpt-5.5" || normalized == "gpt-5.5-pro" } // CalculateCostWithConfig 使用配置中的默认倍率计算费用 diff --git a/backend/internal/service/openai_gateway_record_usage_test.go b/backend/internal/service/openai_gateway_record_usage_test.go index f749c9c1f7..81578f630f 100644 --- a/backend/internal/service/openai_gateway_record_usage_test.go +++ b/backend/internal/service/openai_gateway_record_usage_test.go @@ -1039,7 +1039,7 @@ func TestOpenAIGatewayServiceRecordUsage_GPT56SeparatesCacheWriteForBillingAndSt require.Equal(t, 100, usageRepo.lastLog.CacheReadTokens) require.Equal(t, 1050, usageRepo.lastLog.TotalTokens()) require.InDelta(t, 700*5e-6, usageRepo.lastLog.InputCost, 1e-12) - require.InDelta(t, 200*5e-6, usageRepo.lastLog.CacheCreationCost, 1e-12) + require.InDelta(t, 200*6.25e-6, usageRepo.lastLog.CacheCreationCost, 1e-12) require.InDelta(t, 100*0.5e-6, usageRepo.lastLog.CacheReadCost, 1e-12) require.InDelta(t, 50*30e-6, usageRepo.lastLog.OutputCost, 1e-12) require.InDelta(t, usageRepo.lastLog.TotalCost*1.1, usageRepo.lastLog.ActualCost, 1e-12) diff --git a/backend/internal/service/openai_gateway_response_handling.go b/backend/internal/service/openai_gateway_response_handling.go index 77d212dbe9..d527bb2d32 100644 --- a/backend/internal/service/openai_gateway_response_handling.go +++ b/backend/internal/service/openai_gateway_response_handling.go @@ -783,14 +783,14 @@ func openAICacheReadTokensFromUsage(value gjson.Result) int { func openAICacheCreationTokensFromUsage(value gjson.Result) int { return firstPositiveGJSONInt( + value.Get("input_tokens_details.cache_write_tokens"), + value.Get("prompt_tokens_details.cache_write_tokens"), + value.Get("input_tokens_details.cache_creation_tokens"), + value.Get("prompt_tokens_details.cache_creation_tokens"), + value.Get("cache_write_tokens"), value.Get("cache_creation_input_tokens"), value.Get("cache_write_input_tokens"), value.Get("cache_creation_tokens"), - value.Get("cache_write_tokens"), - value.Get("input_tokens_details.cache_creation_tokens"), - value.Get("input_tokens_details.cache_write_tokens"), - value.Get("prompt_tokens_details.cache_creation_tokens"), - value.Get("prompt_tokens_details.cache_write_tokens"), ) } diff --git a/backend/internal/service/openai_gateway_service_test.go b/backend/internal/service/openai_gateway_service_test.go index 1628ca3d2b..3fdef698db 100644 --- a/backend/internal/service/openai_gateway_service_test.go +++ b/backend/internal/service/openai_gateway_service_test.go @@ -2817,6 +2817,10 @@ func TestExtractOpenAIUsageFromJSONBytes_AcceptsResponseAndChatUsageShapes(t *te usage, ok = extractOpenAIUsageFromJSONBytes([]byte(`{"usage":{"input_tokens":11,"output_tokens":2,"cache_write_input_tokens":6}}`)) require.True(t, ok) require.Equal(t, 6, usage.CacheCreationInputTokens) + + usage, ok = extractOpenAIUsageFromJSONBytes([]byte(`{"usage":{"input_tokens":20,"output_tokens":2,"cache_creation_input_tokens":19,"input_tokens_details":{"cache_write_tokens":7}}}`)) + require.True(t, ok) + require.Equal(t, 7, usage.CacheCreationInputTokens, "官方嵌套字段应优先于兼容顶层别名") } func TestExtractCodexFinalResponse_SampleReplay(t *testing.T) { diff --git a/backend/internal/service/openai_ws_v2/passthrough_relay.go b/backend/internal/service/openai_ws_v2/passthrough_relay.go index 874c618a29..be19438eaa 100644 --- a/backend/internal/service/openai_ws_v2/passthrough_relay.go +++ b/backend/internal/service/openai_ws_v2/passthrough_relay.go @@ -812,14 +812,14 @@ func parseUsageIntField(value gjson.Result, required bool) (int, bool) { func openAICacheCreationTokensFromUsage(value gjson.Result) int { for _, field := range []string{ + "input_tokens_details.cache_write_tokens", + "prompt_tokens_details.cache_write_tokens", + "input_tokens_details.cache_creation_tokens", + "prompt_tokens_details.cache_creation_tokens", + "cache_write_tokens", "cache_creation_input_tokens", "cache_write_input_tokens", "cache_creation_tokens", - "cache_write_tokens", - "input_tokens_details.cache_creation_tokens", - "input_tokens_details.cache_write_tokens", - "prompt_tokens_details.cache_creation_tokens", - "prompt_tokens_details.cache_write_tokens", } { if tokens := int(value.Get(field).Int()); tokens > 0 { return tokens diff --git a/backend/internal/service/pricing_service.go b/backend/internal/service/pricing_service.go index a21cb98783..4528a8886f 100644 --- a/backend/internal/service/pricing_service.go +++ b/backend/internal/service/pricing_service.go @@ -35,6 +35,48 @@ var ( Mode: "chat", SupportsPromptCaching: true, } + openAIGPT56SolFallbackPricing = &LiteLLMModelPricing{ + InputCostPerToken: 5e-06, + InputCostPerTokenPriority: 1e-05, + OutputCostPerToken: 3e-05, + OutputCostPerTokenPriority: 6e-05, + CacheCreationInputTokenCost: 6.25e-06, + CacheCreationInputTokenCostPriority: 1.25e-05, + CacheReadInputTokenCost: 5e-07, + CacheReadInputTokenCostPriority: 1e-06, + SupportsServiceTier: true, + LiteLLMProvider: "openai", + Mode: "chat", + SupportsPromptCaching: true, + } + openAIGPT56TerraFallbackPricing = &LiteLLMModelPricing{ + InputCostPerToken: 2.5e-06, + InputCostPerTokenPriority: 5e-06, + OutputCostPerToken: 1.5e-05, + OutputCostPerTokenPriority: 3e-05, + CacheCreationInputTokenCost: 3.125e-06, + CacheCreationInputTokenCostPriority: 6.25e-06, + CacheReadInputTokenCost: 2.5e-07, + CacheReadInputTokenCostPriority: 5e-07, + SupportsServiceTier: true, + LiteLLMProvider: "openai", + Mode: "chat", + SupportsPromptCaching: true, + } + openAIGPT56LunaFallbackPricing = &LiteLLMModelPricing{ + InputCostPerToken: 1e-06, + InputCostPerTokenPriority: 2e-06, + OutputCostPerToken: 6e-06, + OutputCostPerTokenPriority: 1.2e-05, + CacheCreationInputTokenCost: 1.25e-06, + CacheCreationInputTokenCostPriority: 2.5e-06, + CacheReadInputTokenCost: 1e-07, + CacheReadInputTokenCostPriority: 2e-07, + SupportsServiceTier: true, + LiteLLMProvider: "openai", + Mode: "chat", + SupportsPromptCaching: true, + } openAIGPT54MiniFallbackPricing = &LiteLLMModelPricing{ InputCostPerToken: 7.5e-07, OutputCostPerToken: 4.5e-06, @@ -842,11 +884,20 @@ func (s *PricingService) matchOpenAIModel(model string) *LiteLLMModelPricing { } } - // GPT-5.6(sol / terra / luna)回退到 GPT-5.4 定价 - if strings.HasPrefix(model, "gpt-5.6") { + if strings.HasPrefix(model, "gpt-5.6-sol") { logger.With(zap.String("component", "service.pricing")). - Info(fmt.Sprintf("[Pricing] OpenAI fallback matched %s -> %s", model, "gpt-5.4(static)")) - return openAIGPT54FallbackPricing + Info(fmt.Sprintf("[Pricing] OpenAI fallback matched %s -> %s", model, "gpt-5.6-sol(static)")) + return openAIGPT56SolFallbackPricing + } + if strings.HasPrefix(model, "gpt-5.6-terra") { + logger.With(zap.String("component", "service.pricing")). + Info(fmt.Sprintf("[Pricing] OpenAI fallback matched %s -> %s", model, "gpt-5.6-terra(static)")) + return openAIGPT56TerraFallbackPricing + } + if strings.HasPrefix(model, "gpt-5.6-luna") { + logger.With(zap.String("component", "service.pricing")). + Info(fmt.Sprintf("[Pricing] OpenAI fallback matched %s -> %s", model, "gpt-5.6-luna(static)")) + return openAIGPT56LunaFallbackPricing } // GPT-5.5 回退到 GPT-5.4 定价 diff --git a/backend/internal/service/pricing_service_test.go b/backend/internal/service/pricing_service_test.go index e8f00ae3b2..19098df917 100644 --- a/backend/internal/service/pricing_service_test.go +++ b/backend/internal/service/pricing_service_test.go @@ -40,43 +40,57 @@ func TestParsePricingData_ParsesPriorityAndServiceTierFields(t *testing.T) { require.True(t, pricing.SupportsServiceTier) } -func TestBillingService_GPT56CacheWritePricingUsesInputTier(t *testing.T) { - for _, model := range []string{"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"} { - t.Run(model, func(t *testing.T) { +func TestBillingService_GPT56CacheWritePricingUsesOfficialMultiplier(t *testing.T) { + tests := []struct { + model string + input float64 + inputPriority float64 + output float64 + outputPriority float64 + cacheRead float64 + cacheReadPriority float64 + }{ + {model: "gpt-5.6-sol", input: 5e-6, inputPriority: 10e-6, output: 30e-6, outputPriority: 60e-6, cacheRead: 0.5e-6, cacheReadPriority: 1e-6}, + {model: "gpt-5.6-terra", input: 2.5e-6, inputPriority: 5e-6, output: 15e-6, outputPriority: 30e-6, cacheRead: 0.25e-6, cacheReadPriority: 0.5e-6}, + {model: "gpt-5.6-luna", input: 1e-6, inputPriority: 2e-6, output: 6e-6, outputPriority: 12e-6, cacheRead: 0.1e-6, cacheReadPriority: 0.2e-6}, + } + for _, tt := range tests { + t.Run(tt.model, func(t *testing.T) { pricingSvc := &PricingService{pricingData: map[string]*LiteLLMModelPricing{ - model: { - InputCostPerToken: 5e-6, - InputCostPerTokenPriority: 10e-6, - OutputCostPerToken: 30e-6, - OutputCostPerTokenPriority: 60e-6, - CacheReadInputTokenCost: 0.5e-6, - CacheReadInputTokenCostPriority: 1e-6, + tt.model: { + InputCostPerToken: tt.input, + InputCostPerTokenPriority: tt.inputPriority, + OutputCostPerToken: tt.output, + OutputCostPerTokenPriority: tt.outputPriority, + CacheReadInputTokenCost: tt.cacheRead, + CacheReadInputTokenCostPriority: tt.cacheReadPriority, }, }} svc := NewBillingService(&config.Config{}, pricingSvc) - pricing, err := svc.GetModelPricing(model) + pricing, err := svc.GetModelPricing(tt.model) require.NoError(t, err) - require.InDelta(t, 5e-6, pricing.CacheCreationPricePerToken, 1e-12) - require.InDelta(t, 10e-6, pricing.CacheCreationPricePerTokenPriority, 1e-12) + require.InDelta(t, tt.input*1.25, pricing.CacheCreationPricePerToken, 1e-12) + require.InDelta(t, tt.inputPriority*1.25, pricing.CacheCreationPricePerTokenPriority, 1e-12) + require.Zero(t, pricing.LongContextInputThreshold) tokens := UsageTokens{InputTokens: 700, OutputTokens: 50, CacheCreationTokens: 200, CacheReadTokens: 100} - standard, err := svc.CalculateCostWithServiceTier(model, tokens, 1, "") + standard, err := svc.CalculateCostWithServiceTier(tt.model, tokens, 1, "") require.NoError(t, err) - require.InDelta(t, 200*5e-6, standard.CacheCreationCost, 1e-12) + require.InDelta(t, 200*tt.input*1.25, standard.CacheCreationCost, 1e-12) - priority, err := svc.CalculateCostWithServiceTier(model, tokens, 1, "priority") + priority, err := svc.CalculateCostWithServiceTier(tt.model, tokens, 1, "priority") require.NoError(t, err) - require.InDelta(t, 200*10e-6, priority.CacheCreationCost, 1e-12) + require.InDelta(t, 200*tt.inputPriority*1.25, priority.CacheCreationCost, 1e-12) - flex, err := svc.CalculateCostWithServiceTier(model, tokens, 1, "flex") + flex, err := svc.CalculateCostWithServiceTier(tt.model, tokens, 1, "flex") require.NoError(t, err) - require.InDelta(t, 200*2.5e-6, flex.CacheCreationCost, 1e-12) + require.InDelta(t, 200*tt.input*1.25*0.5, flex.CacheCreationCost, 1e-12) }) } } -func TestBillingService_GPT56CacheWriteContributesToLongContextThreshold(t *testing.T) { +func TestBillingService_GPT56DoesNotUseLegacyLongContextMultiplier(t *testing.T) { model := "gpt-5.6-sol" pricingSvc := &PricingService{pricingData: map[string]*LiteLLMModelPricing{ model: { @@ -90,9 +104,84 @@ func TestBillingService_GPT56CacheWriteContributesToLongContextThreshold(t *test cost, err := svc.CalculateCost(model, tokens, 1) require.NoError(t, err) - require.InDelta(t, 100000*10e-6, cost.InputCost, 1e-12) - require.InDelta(t, 173000*10e-6, cost.CacheCreationCost, 1e-12) - require.InDelta(t, 10*45e-6, cost.OutputCost, 1e-12) + require.InDelta(t, 100000*5e-6, cost.InputCost, 1e-12) + require.InDelta(t, 173000*6.25e-6, cost.CacheCreationCost, 1e-12) + require.InDelta(t, 10*30e-6, cost.OutputCost, 1e-12) +} + +func TestDefaultPricingIncludesOfficialGPT56Rates(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 + billingSvc := NewBillingService(&config.Config{}, pricingSvc) + + tests := []struct { + model string + input, cached, cacheWrite, output float64 + inputPriority, cachedPriority, cacheWritePriority, outputPriority float64 + }{ + {model: "gpt-5.6-sol", input: 5e-6, cached: 0.5e-6, cacheWrite: 6.25e-6, output: 30e-6, inputPriority: 10e-6, cachedPriority: 1e-6, cacheWritePriority: 12.5e-6, outputPriority: 60e-6}, + {model: "gpt-5.6-terra", input: 2.5e-6, cached: 0.25e-6, cacheWrite: 3.125e-6, output: 15e-6, inputPriority: 5e-6, cachedPriority: 0.5e-6, cacheWritePriority: 6.25e-6, outputPriority: 30e-6}, + {model: "gpt-5.6-luna", input: 1e-6, cached: 0.1e-6, cacheWrite: 1.25e-6, output: 6e-6, inputPriority: 2e-6, cachedPriority: 0.2e-6, cacheWritePriority: 2.5e-6, outputPriority: 12e-6}, + } + for _, tt := range tests { + t.Run(tt.model, func(t *testing.T) { + pricing, err := billingSvc.GetModelPricing(tt.model) + require.NoError(t, err) + require.InDelta(t, tt.input, pricing.InputPricePerToken, 1e-12) + require.InDelta(t, tt.cached, pricing.CacheReadPricePerToken, 1e-12) + require.InDelta(t, tt.cacheWrite, pricing.CacheCreationPricePerToken, 1e-12) + require.InDelta(t, tt.output, pricing.OutputPricePerToken, 1e-12) + require.InDelta(t, tt.inputPriority, pricing.InputPricePerTokenPriority, 1e-12) + require.InDelta(t, tt.cachedPriority, pricing.CacheReadPricePerTokenPriority, 1e-12) + require.InDelta(t, tt.cacheWritePriority, pricing.CacheCreationPricePerTokenPriority, 1e-12) + require.InDelta(t, tt.outputPriority, pricing.OutputPricePerTokenPriority, 1e-12) + require.Zero(t, pricing.LongContextInputThreshold) + }) + } +} + +func TestGPT56DedicatedFallbacksUseOfficialRates(t *testing.T) { + tests := []struct { + model string + input, cached, cacheWrite, output float64 + }{ + {model: "gpt-5.6-sol", input: 5e-6, cached: 0.5e-6, cacheWrite: 6.25e-6, output: 30e-6}, + {model: "gpt-5.6-terra", input: 2.5e-6, cached: 0.25e-6, cacheWrite: 3.125e-6, output: 15e-6}, + {model: "gpt-5.6-luna", input: 1e-6, cached: 0.1e-6, cacheWrite: 1.25e-6, output: 6e-6}, + } + + for _, tt := range tests { + t.Run(tt.model+"/pricing_service", func(t *testing.T) { + pricingSvc := &PricingService{pricingData: map[string]*LiteLLMModelPricing{ + "gpt-5.1-codex": {InputCostPerToken: 1.25e-6}, + }} + svc := NewBillingService(&config.Config{}, pricingSvc) + pricing, err := svc.GetModelPricing(tt.model + "-preview") + require.NoError(t, err) + assertGPT56FallbackPricing(t, pricing, tt.input, tt.cached, tt.cacheWrite, tt.output) + }) + + t.Run(tt.model+"/billing_service", func(t *testing.T) { + svc := NewBillingService(&config.Config{}, nil) + pricing, err := svc.GetModelPricing(tt.model) + require.NoError(t, err) + assertGPT56FallbackPricing(t, pricing, tt.input, tt.cached, tt.cacheWrite, tt.output) + }) + } +} + +func assertGPT56FallbackPricing(t *testing.T, pricing *ModelPricing, input, cached, cacheWrite, output float64) { + t.Helper() + require.InDelta(t, input, pricing.InputPricePerToken, 1e-12) + require.InDelta(t, cached, pricing.CacheReadPricePerToken, 1e-12) + require.InDelta(t, cacheWrite, pricing.CacheCreationPricePerToken, 1e-12) + require.InDelta(t, output, pricing.OutputPricePerToken, 1e-12) + require.Zero(t, pricing.LongContextInputThreshold) } func TestParsePricingData_KeepsImageOnlyPricing(t *testing.T) { diff --git a/backend/resources/model-pricing/model_prices_and_context_window.json b/backend/resources/model-pricing/model_prices_and_context_window.json index b9d18f8bf5..439988a53d 100644 --- a/backend/resources/model-pricing/model_prices_and_context_window.json +++ b/backend/resources/model-pricing/model_prices_and_context_window.json @@ -4961,12 +4961,14 @@ "supports_xhigh_reasoning_effort": true }, "gpt-5.6-sol": { + "cache_creation_input_token_cost": 6.25e-06, + "cache_creation_input_token_cost_batches": 3.125e-06, + "cache_creation_input_token_cost_flex": 3.125e-06, + "cache_creation_input_token_cost_priority": 1.25e-05, "cache_read_input_token_cost": 5e-07, - "cache_read_input_token_cost_above_272k_tokens": 1e-06, "cache_read_input_token_cost_flex": 2.5e-07, "cache_read_input_token_cost_priority": 1e-06, "input_cost_per_token": 5e-06, - "input_cost_per_token_above_272k_tokens": 1e-05, "input_cost_per_token_batches": 2.5e-06, "input_cost_per_token_flex": 2.5e-06, "input_cost_per_token_priority": 1e-05, @@ -4976,7 +4978,6 @@ "max_tokens": 128000, "mode": "chat", "output_cost_per_token": 3e-05, - "output_cost_per_token_above_272k_tokens": 4.5e-05, "output_cost_per_token_batches": 1.5e-05, "output_cost_per_token_flex": 1.5e-05, "output_cost_per_token_priority": 6e-05, @@ -5009,25 +5010,26 @@ "supports_xhigh_reasoning_effort": true }, "gpt-5.6-terra": { - "cache_read_input_token_cost": 5e-07, - "cache_read_input_token_cost_above_272k_tokens": 1e-06, - "cache_read_input_token_cost_flex": 2.5e-07, - "cache_read_input_token_cost_priority": 1e-06, - "input_cost_per_token": 5e-06, - "input_cost_per_token_above_272k_tokens": 1e-05, - "input_cost_per_token_batches": 2.5e-06, - "input_cost_per_token_flex": 2.5e-06, - "input_cost_per_token_priority": 1e-05, + "cache_creation_input_token_cost": 3.125e-06, + "cache_creation_input_token_cost_batches": 1.5625e-06, + "cache_creation_input_token_cost_flex": 1.5625e-06, + "cache_creation_input_token_cost_priority": 6.25e-06, + "cache_read_input_token_cost": 2.5e-07, + "cache_read_input_token_cost_flex": 1.25e-07, + "cache_read_input_token_cost_priority": 5e-07, + "input_cost_per_token": 2.5e-06, + "input_cost_per_token_batches": 1.25e-06, + "input_cost_per_token_flex": 1.25e-06, + "input_cost_per_token_priority": 5e-06, "litellm_provider": "openai", "max_input_tokens": 1050000, "max_output_tokens": 128000, "max_tokens": 128000, "mode": "chat", - "output_cost_per_token": 3e-05, - "output_cost_per_token_above_272k_tokens": 4.5e-05, - "output_cost_per_token_batches": 1.5e-05, - "output_cost_per_token_flex": 1.5e-05, - "output_cost_per_token_priority": 6e-05, + "output_cost_per_token": 1.5e-05, + "output_cost_per_token_batches": 7.5e-06, + "output_cost_per_token_flex": 7.5e-06, + "output_cost_per_token_priority": 3e-05, "supported_endpoints": [ "/v1/chat/completions", "/v1/batch", @@ -5057,25 +5059,26 @@ "supports_xhigh_reasoning_effort": true }, "gpt-5.6-luna": { - "cache_read_input_token_cost": 5e-07, - "cache_read_input_token_cost_above_272k_tokens": 1e-06, - "cache_read_input_token_cost_flex": 2.5e-07, - "cache_read_input_token_cost_priority": 1e-06, - "input_cost_per_token": 5e-06, - "input_cost_per_token_above_272k_tokens": 1e-05, - "input_cost_per_token_batches": 2.5e-06, - "input_cost_per_token_flex": 2.5e-06, - "input_cost_per_token_priority": 1e-05, + "cache_creation_input_token_cost": 1.25e-06, + "cache_creation_input_token_cost_batches": 6.25e-07, + "cache_creation_input_token_cost_flex": 6.25e-07, + "cache_creation_input_token_cost_priority": 2.5e-06, + "cache_read_input_token_cost": 1e-07, + "cache_read_input_token_cost_flex": 5e-08, + "cache_read_input_token_cost_priority": 2e-07, + "input_cost_per_token": 1e-06, + "input_cost_per_token_batches": 5e-07, + "input_cost_per_token_flex": 5e-07, + "input_cost_per_token_priority": 2e-06, "litellm_provider": "openai", "max_input_tokens": 1050000, "max_output_tokens": 128000, "max_tokens": 128000, "mode": "chat", - "output_cost_per_token": 3e-05, - "output_cost_per_token_above_272k_tokens": 4.5e-05, - "output_cost_per_token_batches": 1.5e-05, - "output_cost_per_token_flex": 1.5e-05, - "output_cost_per_token_priority": 6e-05, + "output_cost_per_token": 6e-06, + "output_cost_per_token_batches": 3e-06, + "output_cost_per_token_flex": 3e-06, + "output_cost_per_token_priority": 1.2e-05, "supported_endpoints": [ "/v1/chat/completions", "/v1/batch",