From e29b93a1fbc6753590b0bbda010c3ab1ab3361c6 Mon Sep 17 00:00:00 2001 From: IanShaw027 Date: Thu, 13 Aug 2026 09:22:38 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=9C=AA=E7=9F=A5=20Grok=20=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E5=85=9C=E5=BA=95=E6=8E=92=E9=99=A4=20image/video/aud?= =?UTF-8?q?io=20=E7=AD=89=E5=AA=92=E4=BD=93=E6=97=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 带版本号的媒体 ID(grok-2-image-1212、grok-5-video)会被 grok-N* 白名单当成文本,错吃 4.5 token 价。vision 多模态 仍按 token 计;HasIdentifiedTokenPricing 也不放行这些 ID。 --- backend/internal/service/billing_service.go | 16 ++++++++++++++++ backend/internal/service/billing_service_test.go | 8 ++++++++ .../service/response_model_billing_test.go | 6 ++++++ 3 files changed, 30 insertions(+) diff --git a/backend/internal/service/billing_service.go b/backend/internal/service/billing_service.go index c13908443e..0356f698a0 100644 --- a/backend/internal/service/billing_service.go +++ b/backend/internal/service/billing_service.go @@ -862,6 +862,9 @@ func (s *BillingService) grokUnknownTextFamilyFallback(model string) *ModelPrici func isGrokUnknownTextFamilyModel(model string) bool { native := strings.ToLower(strings.TrimSpace(xai.StripGrokProviderPrefix(model))) + if isGrokMediaFamilyModel(native) { + return false + } switch { case native == "grok", native == "grok-latest": return true @@ -877,6 +880,19 @@ func isGrokUnknownTextFamilyModel(model string) bool { } } +// isGrokMediaFamilyModel matches ids that are billed per image/video/audio unit +// rather than per token, so version-numbered media ids (grok-2-image-1212, +// grok-5-video) cannot slip into the unknown-text fallback and pick up a token +// card. "vision" is deliberately absent: multimodal chat models are token billed. +func isGrokMediaFamilyModel(native string) bool { + for _, marker := range []string{"imagine", "image", "video", "audio", "speech", "tts", "transcribe", "realtime"} { + if strings.Contains(native, marker) { + return true + } + } + return false +} + // HasIdentifiedTokenPricing 判断模型能否在价格表中被"确定性识别"出 token 价格。 // // 与 GetModelPricing 的关键区别:本函数拒绝按子串猜系列的兜底。GetModelPricing 会 diff --git a/backend/internal/service/billing_service_test.go b/backend/internal/service/billing_service_test.go index d1ea67645c..80d07af44b 100644 --- a/backend/internal/service/billing_service_test.go +++ b/backend/internal/service/billing_service_test.go @@ -1221,6 +1221,14 @@ func TestGetModelPricing_UnknownGrokTextFallsBackToGrok45(t *testing.T) { require.InDelta(t, baseline.CacheReadPricePerToken, pricing.CacheReadPricePerToken, 1e-12, model) } + // Per-unit media ids must not inherit the text card just because they carry + // a version number; they are billed by the image/video/audio paths instead. + for _, model := range []string{"grok-2-image-1212", "grok-2-audio", "grok-5-video", "x-ai/grok-6-image"} { + require.False(t, isGrokUnknownTextFamilyModel(model), "model %s", model) + } + // Multimodal chat models stay token billed. + require.True(t, isGrokUnknownTextFamilyModel("grok-2-vision-1212")) + for _, model := range []string{ "grok-imagine-image-3.0", "grok-imagine-video-2", diff --git a/backend/internal/service/response_model_billing_test.go b/backend/internal/service/response_model_billing_test.go index 9194a1684e..6b04fc424a 100644 --- a/backend/internal/service/response_model_billing_test.go +++ b/backend/internal/service/response_model_billing_test.go @@ -372,6 +372,12 @@ func TestBillingServiceHasIdentifiedTokenPricing_RejectsFamilyGuesses(t *testing } require.False(t, billing.HasIdentifiedTokenPricing("")) require.False(t, billing.HasIdentifiedTokenPricing("zz-unpriced-response-model")) + // Versioned media ids may inherit a text card via GetModelPricing; the + // identified-token gate must still reject them so response-model billing + // cannot adopt grok-4.5 rates for image/audio/video ids. + require.False(t, billing.HasIdentifiedTokenPricing("grok-2-image-1212")) + require.False(t, billing.HasIdentifiedTokenPricing("grok-2-audio")) + require.False(t, billing.HasIdentifiedTokenPricing("grok-5-video")) } func TestGatewayServiceRecordUsage_ResponseModelRejectsUnidentifiedFamilyName(t *testing.T) {