mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-21 14:19:18 +08:00
fix(openai): align GPT-5.6 billing with official pricing
This commit is contained in:
@@ -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 使用配置中的默认倍率计算费用
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 定价
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user