mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-21 09:09:53 +00:00
fix(ratelimit): 图片模型被 Codex 文本端点拒绝时不再写模型冷却
gpt-image-* 被误发到 /v1/chat/completions 或 /v1/responses 时,Codex 会稳定 返回 400 codex_plan_gated_model。HandleUpstreamModelNotFound 把它当成账号能力 缺失,给每个账号写 30 分钟 model_rate_limits 冷却。failover 走完整个号池后, 池内每个账号都冷却了该图片模型,于是后续走对端点的 /v1/images/generations 请求也一并 503 no available accounts。 在 codex plan-gated 分支里对图片模型提前返回:仍然 failover 掉本次尝试, 但跳过 SetModelRateLimit 写入,不把号池对正确的生图请求整体下线。 Fixes #4828
This commit is contained in:
@@ -2047,6 +2047,14 @@ func (s *RateLimitService) HandleUpstreamModelNotFound(ctx context.Context, acco
|
||||
case isUpstreamModelNotFoundError(statusCode, responseBody):
|
||||
cooldown, reason = upstreamModelNotFoundCooldown, upstreamModelNotFoundReason
|
||||
case isOpenAIOAuthAccount(account) && isOpenAICodexPlanGatedModelError(statusCode, responseBody):
|
||||
// Image models rejected by the Codex text endpoints are a deterministic
|
||||
// endpoint mismatch, not an account capability gap: the same account still
|
||||
// serves them over /v1/images/*. Cooling down the model here would take the
|
||||
// whole pool offline for correct image requests, so fail the attempt over
|
||||
// without recording a per-model cooldown.
|
||||
if IsGPTImageGenerationModel(requestedModel) {
|
||||
return true
|
||||
}
|
||||
cooldown, reason = upstreamCodexPlanGatedModelCooldown, upstreamCodexPlanGatedModelReason
|
||||
default:
|
||||
return false
|
||||
|
||||
@@ -382,6 +382,49 @@ func TestRateLimitService_HandleUpstreamError_CodexPlanGatedModelIgnoresAPIKeyAc
|
||||
require.Empty(t, repo.modelRateLimitCalls)
|
||||
}
|
||||
|
||||
func TestRateLimitService_HandleUpstreamError_CodexPlanGatedImageModelSkipsCooldown(t *testing.T) {
|
||||
for _, model := range []string{"gpt-image-1", "gpt-image-1.5", "gpt-image-2"} {
|
||||
t.Run(model, func(t *testing.T) {
|
||||
repo := &modelNotFoundAccountRepoStub{}
|
||||
svc := &RateLimitService{accountRepo: repo}
|
||||
account := openAICodexPlanGatedOAuthAccount()
|
||||
|
||||
handled := svc.HandleUpstreamError(
|
||||
context.Background(),
|
||||
account,
|
||||
http.StatusBadRequest,
|
||||
http.Header{},
|
||||
[]byte(`{"detail":"The '`+model+`' model is not supported when using Codex with a ChatGPT account."}`),
|
||||
model,
|
||||
)
|
||||
|
||||
require.True(t, handled, "attempt should still fail over")
|
||||
require.Empty(t, repo.modelRateLimitCalls,
|
||||
"image models must not be cooled down: the account still serves them over /v1/images/*")
|
||||
require.Zero(t, repo.tempCalls)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRateLimitService_HandleUpstreamError_CodexPlanGatedTextModelStillCoolsDown(t *testing.T) {
|
||||
repo := &modelNotFoundAccountRepoStub{}
|
||||
svc := &RateLimitService{accountRepo: repo}
|
||||
account := openAICodexPlanGatedOAuthAccount()
|
||||
|
||||
handled := svc.HandleUpstreamError(
|
||||
context.Background(),
|
||||
account,
|
||||
http.StatusBadRequest,
|
||||
http.Header{},
|
||||
[]byte(`{"detail":"The 'gpt-5.6-sol' model is not supported when using Codex with a ChatGPT account."}`),
|
||||
"gpt-5.6-sol",
|
||||
)
|
||||
|
||||
require.True(t, handled)
|
||||
require.Len(t, repo.modelRateLimitCalls, 1, "non-image plan-gated models keep the existing cooldown")
|
||||
require.Equal(t, upstreamCodexPlanGatedModelReason, repo.modelRateLimitCalls[0].reason)
|
||||
}
|
||||
|
||||
func openAICodexPlanGatedOAuthAccount() *Account {
|
||||
return &Account{
|
||||
ID: 202,
|
||||
|
||||
Reference in New Issue
Block a user