mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
fix: 未登记的 Grok 文本模型回退 grok-4.5 价卡
新文本 ID 在闭集价卡上 fail-closed,请求成功而用量记 0。 - grok / grok-N* / build / composer 未登记别名继承 4.5 价 - imagine/voice/web-search/speech 等非文本族不回退
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/xai"
|
||||
)
|
||||
|
||||
// APIKeyRateLimitCacheData holds rate limit usage data cached in Redis.
|
||||
@@ -843,9 +844,39 @@ func (s *BillingService) getFallbackPricing(model string) *ModelPricing {
|
||||
return s.fallbackPrices["grok-build-0.1"]
|
||||
}
|
||||
|
||||
// Unknown Grok text IDs (grok-5, dated snapshots, provider-prefixed) inherit
|
||||
// the current default text card so a new model cannot ship unbilled.
|
||||
if pricing := s.grokUnknownTextFamilyFallback(modelLower); pricing != nil {
|
||||
return pricing
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *BillingService) grokUnknownTextFamilyFallback(model string) *ModelPricing {
|
||||
if s == nil || !isGrokUnknownTextFamilyModel(model) {
|
||||
return nil
|
||||
}
|
||||
return s.fallbackPrices["grok-4.5"]
|
||||
}
|
||||
|
||||
func isGrokUnknownTextFamilyModel(model string) bool {
|
||||
native := strings.ToLower(strings.TrimSpace(xai.StripGrokProviderPrefix(model)))
|
||||
switch {
|
||||
case native == "grok", native == "grok-latest":
|
||||
return true
|
||||
case strings.HasPrefix(native, "grok-build"),
|
||||
strings.HasPrefix(native, "grok-composer"),
|
||||
strings.HasPrefix(native, "composer-"):
|
||||
return true
|
||||
case len(native) > 5 && strings.HasPrefix(native, "grok-"):
|
||||
rest := native[len("grok-"):]
|
||||
return rest[0] >= '0' && rest[0] <= '9'
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// HasIdentifiedTokenPricing 判断模型能否在价格表中被"确定性识别"出 token 价格。
|
||||
//
|
||||
// 与 GetModelPricing 的关键区别:本函数拒绝按子串猜系列的兜底。GetModelPricing 会
|
||||
|
||||
@@ -1185,6 +1185,61 @@ func TestGetModelPricing_Grok46OfficialFallback(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateCostUnified_GroupLongContextToggleUsesPresetLadder(t *testing.T) {
|
||||
svc := newTestBillingService()
|
||||
resolver := NewModelPricingResolver(nil, svc)
|
||||
tokens := UsageTokens{InputTokens: 250000, OutputTokens: 1000}
|
||||
|
||||
off := &Group{LongContextPricingEnabled: false}
|
||||
disabled, err := svc.CalculateCostUnified(CostInput{
|
||||
Model: "grok-4.5", Group: off, Tokens: tokens, RateMultiplier: 1, Resolver: resolver,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
on := &Group{LongContextPricingEnabled: true}
|
||||
enabled, err := svc.CalculateCostUnified(CostInput{
|
||||
Model: "grok-4.5", Group: on, Tokens: tokens, RateMultiplier: 1, Resolver: resolver,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.False(t, disabled.LongContextBillingApplied)
|
||||
require.True(t, enabled.LongContextBillingApplied)
|
||||
require.InDelta(t, disabled.InputCost*2, enabled.InputCost, 1e-12)
|
||||
require.InDelta(t, disabled.OutputCost*2, enabled.OutputCost, 1e-12)
|
||||
}
|
||||
|
||||
func TestGetModelPricing_UnknownGrokTextFallsBackToGrok45(t *testing.T) {
|
||||
svc := newTestBillingService()
|
||||
baseline, err := svc.GetModelPricing("grok-4.5")
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, model := range []string{"grok-5", "grok-5-latest", "x-ai/grok-7", "grok-4.7-beta"} {
|
||||
pricing, err := svc.GetModelPricing(model)
|
||||
require.NoError(t, err, "model %s", model)
|
||||
require.InDelta(t, baseline.InputPricePerToken, pricing.InputPricePerToken, 1e-12, model)
|
||||
require.InDelta(t, baseline.OutputPricePerToken, pricing.OutputPricePerToken, 1e-12, model)
|
||||
require.InDelta(t, baseline.CacheReadPricePerToken, pricing.CacheReadPricePerToken, 1e-12, model)
|
||||
}
|
||||
|
||||
for _, model := range []string{
|
||||
"grok-imagine-image-3.0",
|
||||
"grok-imagine-video-2",
|
||||
"grok-voice-latest",
|
||||
"grok-web-search",
|
||||
"grok-x-search",
|
||||
"grok-speech-1",
|
||||
} {
|
||||
_, err := svc.GetModelPricing(model)
|
||||
require.Error(t, err, "non-text grok family %s must not inherit grok-4.5 token rates", model)
|
||||
require.ErrorIs(t, err, ErrModelPricingUnavailable)
|
||||
}
|
||||
|
||||
// Known cards stay on their own rate, not the 4.5 family floor.
|
||||
build, err := svc.GetModelPricing("grok-build-0.1")
|
||||
require.NoError(t, err)
|
||||
require.InDelta(t, 1e-6, build.InputPricePerToken, 1e-12)
|
||||
}
|
||||
|
||||
func TestGetModelPricing_GrokCatalogFallbacks(t *testing.T) {
|
||||
svc := newTestBillingService()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user