diff --git a/backend/internal/service/billing_service.go b/backend/internal/service/billing_service.go index ae68c93ef6..3c8b52af97 100644 --- a/backend/internal/service/billing_service.go +++ b/backend/internal/service/billing_service.go @@ -97,6 +97,7 @@ type ModelPricing struct { OutputPricePerTokenPriority float64 // priority service tier 下每token输出价格 (USD) CacheCreationPricePerToken float64 // 缓存创建每token价格 (USD) CacheCreationPricePerTokenPriority float64 // priority service tier 下缓存创建每token价格 (USD) + CacheCreationPriceExplicit bool // 是否由渠道/区间定价显式设定(为 true 时即使 == 0 也不回退) CacheReadPricePerToken float64 // 缓存读取每token价格 (USD) CacheReadPricePerTokenPriority float64 // priority service tier 下缓存读取每token价格 (USD) CacheCreation5mPrice float64 // 5分钟缓存创建每token价格 (USD) @@ -825,6 +826,7 @@ func (s *BillingService) GetModelPricingWithChannel(model string, channelPricing if channelPricing.CacheWritePrice != nil { pricing.CacheCreationPricePerToken = *channelPricing.CacheWritePrice pricing.CacheCreationPricePerTokenPriority = *channelPricing.CacheWritePrice + pricing.CacheCreationPriceExplicit = true pricing.CacheCreation5mPrice = *channelPricing.CacheWritePrice pricing.CacheCreation1hPrice = *channelPricing.CacheWritePrice } @@ -1100,7 +1102,7 @@ func (s *BillingService) applyModelSpecificPricingPolicy(model string, pricing * } needsLongContextPolicy := usesLegacyLongContextPricing && (pricing.LongContextInputThreshold <= 0 || pricing.LongContextInputMultiplier <= 0 || pricing.LongContextOutputMultiplier <= 0) - needsCacheCreationPolicy := isGPT56 && (pricing.CacheCreationPricePerToken <= 0 || + needsCacheCreationPolicy := isGPT56 && !pricing.CacheCreationPriceExplicit && (pricing.CacheCreationPricePerToken <= 0 || (pricing.InputPricePerTokenPriority > 0 && pricing.CacheCreationPricePerTokenPriority <= 0)) if !needsLongContextPolicy && !needsCacheCreationPolicy { return pricing diff --git a/backend/internal/service/model_pricing_resolver.go b/backend/internal/service/model_pricing_resolver.go index 7ddf6ea475..a9f603113b 100644 --- a/backend/internal/service/model_pricing_resolver.go +++ b/backend/internal/service/model_pricing_resolver.go @@ -184,6 +184,7 @@ func (r *ModelPricingResolver) applyTokenOverrides(chPricing *ChannelModelPricin if chPricing.CacheWritePrice != nil { resolved.BasePricing.CacheCreationPricePerToken = *chPricing.CacheWritePrice resolved.BasePricing.CacheCreationPricePerTokenPriority = *chPricing.CacheWritePrice + resolved.BasePricing.CacheCreationPriceExplicit = true resolved.BasePricing.CacheCreation5mPrice = *chPricing.CacheWritePrice resolved.BasePricing.CacheCreation1hPrice = *chPricing.CacheWritePrice } @@ -253,6 +254,7 @@ func intervalToModelPricing(iv *PricingInterval, supportsCacheBreakdown bool, ch if iv.CacheWritePrice != nil { pricing.CacheCreationPricePerToken = *iv.CacheWritePrice pricing.CacheCreationPricePerTokenPriority = *iv.CacheWritePrice + pricing.CacheCreationPriceExplicit = true pricing.CacheCreation5mPrice = *iv.CacheWritePrice pricing.CacheCreation1hPrice = *iv.CacheWritePrice } diff --git a/backend/internal/service/model_pricing_resolver_test.go b/backend/internal/service/model_pricing_resolver_test.go index d4169e5f0e..71ba9904c2 100644 --- a/backend/internal/service/model_pricing_resolver_test.go +++ b/backend/internal/service/model_pricing_resolver_test.go @@ -114,6 +114,52 @@ func TestGetIntervalPricing_NoMatch_FallsBackToBase(t *testing.T) { require.Equal(t, basePricing, result) } +func TestGPT56ExplicitZeroCacheWritePriceIsPreserved(t *testing.T) { + bs := &BillingService{} + resolver := NewModelPricingResolver(nil, bs) + zero := 0.0 + + t.Run("flat channel price", func(t *testing.T) { + resolved := &ResolvedPricing{ + Mode: BillingModeToken, + BasePricing: &ModelPricing{ + InputPricePerToken: 5e-6, + OutputPricePerToken: 30e-6, + }, + } + resolver.applyTokenOverrides(&ChannelModelPricing{CacheWritePrice: &zero}, resolved) + + require.True(t, resolved.BasePricing.CacheCreationPriceExplicit) + cost, err := bs.CalculateCostUnified(CostInput{ + Model: "gpt-5.6-sol", + Tokens: UsageTokens{CacheCreationTokens: 100}, + RateMultiplier: 1, + Resolver: resolver, + Resolved: resolved, + }) + require.NoError(t, err) + require.Zero(t, cost.CacheCreationCost) + }) + + t.Run("interval price", func(t *testing.T) { + pricing := intervalToModelPricing(&PricingInterval{CacheWritePrice: &zero}, false, nil) + require.True(t, pricing.CacheCreationPriceExplicit) + + cost, err := bs.CalculateCostUnified(CostInput{ + Model: "gpt-5.6-sol", + Tokens: UsageTokens{CacheCreationTokens: 100}, + RateMultiplier: 1, + Resolver: resolver, + Resolved: &ResolvedPricing{ + Mode: BillingModeToken, + BasePricing: pricing, + }, + }) + require.NoError(t, err) + require.Zero(t, cost.CacheCreationCost) + }) +} + func TestGetRequestTierPrice(t *testing.T) { bs := newTestBillingServiceForResolver() r := NewModelPricingResolver(&ChannelService{}, bs)