fix: Grok 长上下文只跟分组开关,不受 OpenAI 账号开关否决

openai_long_context_billing_enabled 是 OpenAI 账号设置,Grok
账号无法打开。按账号硬传 false 会让分组开关失效,官方 200k
阶梯和渠道多档区间全部塌到第一档。非 OpenAI 不再传账号门闩。
This commit is contained in:
IanShaw027
2026-08-13 09:22:34 +08:00
parent 4853662f99
commit fd82dfd52d
4 changed files with 79 additions and 14 deletions
@@ -50,7 +50,7 @@ func TestCalculateOpenAIRecordUsageCostWebSearchPerCall(t *testing.T) {
// 即使 token 倍率(含高峰,3.0)更高也不采用。
apiKey := &APIKey{ID: 1, GroupID: &groupID, Group: &Group{ID: groupID, Platform: PlatformOpenAI}}
result := &OpenAIForwardResult{Model: "gpt-5.6-sol", UpstreamModel: "gpt-5.6-sol", WebSearchCalls: 1}
cost, err := svc.calculateOpenAIRecordUsageCost(context.Background(), result, apiKey, []string{"gpt-5.6-sol"}, 3.0, 1.0, 1.0, 2.0, UsageTokens{}, "", false)
cost, err := svc.calculateOpenAIRecordUsageCost(context.Background(), result, apiKey, []string{"gpt-5.6-sol"}, 3.0, 1.0, 1.0, 2.0, UsageTokens{}, "", boolPtr(false))
require.NoError(t, err)
require.Equal(t, string(BillingModePerRequest), cost.BillingMode)
require.InDelta(t, 0.01, cost.TotalCost, 1e-12)
@@ -58,7 +58,7 @@ func TestCalculateOpenAIRecordUsageCostWebSearchPerCall(t *testing.T) {
// 分组配置单价 0.005
apiKey.Group.WebSearchPricePerCall = float64Ptr(0.005)
cost, err = svc.calculateOpenAIRecordUsageCost(context.Background(), result, apiKey, []string{"gpt-5.6-sol"}, 1.0, 1.0, 1.0, 1.0, UsageTokens{}, "", false)
cost, err = svc.calculateOpenAIRecordUsageCost(context.Background(), result, apiKey, []string{"gpt-5.6-sol"}, 1.0, 1.0, 1.0, 1.0, UsageTokens{}, "", boolPtr(false))
require.NoError(t, err)
require.InDelta(t, 0.005, cost.TotalCost, 1e-12)
require.InDelta(t, 0.005, cost.ActualCost, 1e-12)
@@ -66,7 +66,7 @@ func TestCalculateOpenAIRecordUsageCostWebSearchPerCall(t *testing.T) {
// WebSearchCalls = 0 时不得走按次分支(无定价数据会返回 pricing 错误,
// 证明回落到了 token 路径而不是被按次分支吞掉)。
result.WebSearchCalls = 0
_, err = svc.calculateOpenAIRecordUsageCost(context.Background(), result, apiKey, []string{"gpt-5.6-sol"}, 1.0, 1.0, 1.0, 1.0, UsageTokens{InputTokens: 10}, "", false)
_, err = svc.calculateOpenAIRecordUsageCost(context.Background(), result, apiKey, []string{"gpt-5.6-sol"}, 1.0, 1.0, 1.0, 1.0, UsageTokens{InputTokens: 10}, "", boolPtr(false))
require.Error(t, err)
}
@@ -1210,6 +1210,58 @@ func TestOpenAIGatewayServiceRecordUsage_GroupAndAccountLongContextMustBothAllow
})
}
// openai_long_context_billing_enabled is an OpenAI-only account setting, so it
// must not veto the official Grok >=200k ladder: a Grok account has no way to
// ever set that flag, which would make the group toggle unreachable.
func TestOpenAIGatewayServiceRecordUsage_GrokLongContextFollowsGroupToggleOnly(t *testing.T) {
baseInput := 250000 * 2e-6
baseOutput := 1000 * 6e-6
grokAccount := func(id int64) *Account {
return &Account{ID: id, Platform: PlatformGrok, Type: AccountTypeOAuth}
}
t.Run("group on applies the official ladder", func(t *testing.T) {
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
svc := newOpenAIRecordUsageServiceForTest(usageRepo, &openAIRecordUsageUserRepoStub{}, &openAIRecordUsageSubRepoStub{}, nil)
err := svc.RecordUsage(context.Background(), &OpenAIRecordUsageInput{
Result: &OpenAIForwardResult{
RequestID: "resp_grok_longctx_on",
Usage: OpenAIUsage{InputTokens: 250000, OutputTokens: 1000},
Model: "grok-4.5",
Duration: time.Second,
},
APIKey: openAIRecordUsageAPIKeyWithGroup(svc, 1030, true),
User: &User{ID: 2030},
Account: grokAccount(3030),
})
require.NoError(t, err)
require.True(t, usageRepo.lastLog.LongContextBillingApplied)
require.InDelta(t, baseInput*2, usageRepo.lastLog.InputCost, 1e-10)
require.InDelta(t, baseOutput*2, usageRepo.lastLog.OutputCost, 1e-10)
})
t.Run("group off keeps the base card", func(t *testing.T) {
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
svc := newOpenAIRecordUsageServiceForTest(usageRepo, &openAIRecordUsageUserRepoStub{}, &openAIRecordUsageSubRepoStub{}, nil)
err := svc.RecordUsage(context.Background(), &OpenAIRecordUsageInput{
Result: &OpenAIForwardResult{
RequestID: "resp_grok_longctx_off",
Usage: OpenAIUsage{InputTokens: 250000, OutputTokens: 1000},
Model: "grok-4.5",
Duration: time.Second,
},
APIKey: openAIRecordUsageAPIKeyWithGroup(svc, 1031, false),
User: &User{ID: 2031},
Account: grokAccount(3031),
})
require.NoError(t, err)
require.False(t, usageRepo.lastLog.LongContextBillingApplied)
require.InDelta(t, baseInput, usageRepo.lastLog.InputCost, 1e-10)
require.InDelta(t, baseOutput, usageRepo.lastLog.OutputCost, 1e-10)
})
}
func TestOpenAIGatewayServiceRecordUsage_SparkShadowUsesCurrentParentBillingSetting(t *testing.T) {
tests := []struct {
name string
@@ -36,7 +36,7 @@ func TestCalculateOpenAIRecordUsageCost_SearchIsAdditiveToTokens(t *testing.T) {
1.0,
UsageTokens{InputTokens: 1000, OutputTokens: 500},
"",
false,
boolPtr(false),
)
require.NoError(t, err)
require.NotNil(t, cost)
@@ -66,7 +66,7 @@ func TestCalculateOpenAIRecordUsageCost_SearchOnlyWhenNoTokenPricing(t *testing.
1.0,
UsageTokens{},
"",
false,
boolPtr(false),
)
require.NoError(t, err)
require.NotNil(t, cost)
@@ -111,7 +111,7 @@ func TestCalculateOpenAIRecordUsageCost_TokenPricingErrorNotSwallowedBySearch(t
1.0,
UsageTokens{InputTokens: 1000, OutputTokens: 500},
"",
false,
boolPtr(false),
)
require.Error(t, err)
require.Nil(t, cost)
@@ -211,7 +211,7 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
return err
}
}
longContextBillingEnabled := billingAccount.IsOpenAILongContextBillingEnabled()
longContextBillingGate := openAILongContextBillingGate(billingAccount)
cost, err = s.calculateOpenAIRecordUsageCost(
ctx,
result,
@@ -223,7 +223,7 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
baseMultiplier,
tokens,
serviceTier,
longContextBillingEnabled,
longContextBillingGate,
)
if err != nil {
if !isUsagePricingUnavailableError(err) {
@@ -256,7 +256,7 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
responseModels := usageBillingModelCandidates(responseModel)
responseCost, responseErr := s.calculateOpenAIRecordUsageCost(
ctx, result, apiKey, responseModels, multiplier, imageMultiplier,
videoMultiplier, baseMultiplier, tokens, serviceTier, longContextBillingEnabled,
videoMultiplier, baseMultiplier, tokens, serviceTier, longContextBillingGate,
)
// 基线定价源以 baselineBillingModel 为准:它正是 calculateOpenAIRecordUsageCost
// 内部做渠道定价判断时使用的模型,且"首候选有渠道价"必然意味着首候选就是实际
@@ -480,6 +480,19 @@ func (s *OpenAIGatewayService) hasIdentifiedOpenAIResponsePricing(ctx context.Co
return s.billingService.HasIdentifiedTokenPricing(model), false
}
// openAILongContextBillingGate returns the per-account long-context opt-in.
// The flag is an OpenAI-only account setting, so other platforms (Grok) return
// nil — "no per-account gate" — and are governed by the group toggle alone.
// Returning a hardcoded false for them would veto the official model ladders
// (e.g. the Grok >=200k 2x card) that no account setting can ever re-enable.
func openAILongContextBillingGate(account *Account) *bool {
if account == nil || !account.IsOpenAI() {
return nil
}
enabled := account.IsOpenAILongContextBillingEnabled()
return &enabled
}
func (s *OpenAIGatewayService) calculateOpenAIRecordUsageCost(
ctx context.Context,
result *OpenAIForwardResult,
@@ -491,7 +504,7 @@ func (s *OpenAIGatewayService) calculateOpenAIRecordUsageCost(
webSearchMultiplier float64,
tokens UsageTokens,
serviceTier string,
longContextBillingEnabled bool,
longContextBillingGate *bool,
) (*CostBreakdown, error) {
billingModel := firstUsageBillingModel(billingModels)
if result != nil && result.WebSearchCalls > 0 {
@@ -543,7 +556,7 @@ func (s *OpenAIGatewayService) calculateOpenAIRecordUsageCost(
multiplier,
tokens,
serviceTier,
longContextBillingEnabled,
longContextBillingGate,
)
if err == nil {
tokenCost = cost
@@ -632,7 +645,7 @@ func (s *OpenAIGatewayService) calculateOpenAIRecordUsageTokenCost(
multiplier float64,
tokens UsageTokens,
serviceTier string,
longContextBillingEnabled bool,
longContextBillingGate *bool,
) (*CostBreakdown, error) {
if s.resolver != nil && apiKey.Group != nil {
gid := apiKey.Group.ID
@@ -640,7 +653,7 @@ func (s *OpenAIGatewayService) calculateOpenAIRecordUsageTokenCost(
Ctx: ctx, Model: billingModel, GroupID: &gid, Group: apiKey.Group,
Tokens: tokens, RequestCount: 1, RateMultiplier: multiplier,
ServiceTier: serviceTier, Resolver: s.resolver,
LongContextBillingEnabled: &longContextBillingEnabled,
LongContextBillingEnabled: longContextBillingGate,
})
}
return s.billingService.calculateCostWithServiceTierPolicy(
@@ -648,7 +661,7 @@ func (s *OpenAIGatewayService) calculateOpenAIRecordUsageTokenCost(
tokens,
multiplier,
serviceTier,
longContextBillingEnabled,
longContextBillingGate == nil || *longContextBillingGate,
)
}