From c7c933776db3847f60fa65945f389d82071ff5d9 Mon Sep 17 00:00:00 2001 From: feeeei Date: Mon, 13 Jul 2026 12:57:00 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=B1=A0=E6=A8=A1=E5=BC=8F=E5=90=8C?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E9=87=8D=E8=AF=95=E6=AC=A1=E6=95=B0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=AF=B9=20Anthropic/Gemini/=E9=80=9A=E7=94=A8?= =?UTF-8?q?=E8=BD=AC=E5=8F=91=E8=B7=AF=E5=BE=84=E7=94=9F=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit e643fc38 引入 pool_mode_retry_count 账号配置时,只覆盖了 OpenAI 族 handler 的内联重试循环;走 HandleFailoverError 的 Anthropic/ Antigravity/Gemini/通用转发路径仍硬编码同账号重试 3 次,配置不生效。 为 HandleFailoverError 增加 retryLimit 参数,由调用方传入 account.GetPoolModeRetryCount();未配置账号默认仍为 3 次,行为不变。 --- backend/internal/handler/failover_loop.go | 11 +- .../internal/handler/failover_loop_test.go | 137 +++++++++++------- backend/internal/handler/gateway_handler.go | 4 +- .../gateway_handler_chat_completions.go | 2 +- .../handler/gateway_handler_responses.go | 2 +- .../internal/handler/gemini_v1beta_handler.go | 2 +- 6 files changed, 99 insertions(+), 59 deletions(-) diff --git a/backend/internal/handler/failover_loop.go b/backend/internal/handler/failover_loop.go index 6d8ddc7236..5838e58f48 100644 --- a/backend/internal/handler/failover_loop.go +++ b/backend/internal/handler/failover_loop.go @@ -29,7 +29,8 @@ const ( ) const ( - // maxSameAccountRetries 同账号重试次数上限(针对 RetryableOnSameAccount 错误) + // maxSameAccountRetries 同账号重试次数默认上限(针对 RetryableOnSameAccount 错误)。 + // 生产调用方通常传入账号级配置 account.GetPoolModeRetryCount(),该常量仅作兜底/测试默认值。 maxSameAccountRetries = 3 // sameAccountRetryDelay 同账号重试间隔 sameAccountRetryDelay = 500 * time.Millisecond @@ -67,6 +68,7 @@ func (s *FailoverState) HandleFailoverError( gatewayService TempUnscheduler, accountID int64, platform string, + retryLimit int, failoverErr *service.UpstreamFailoverError, ) FailoverAction { s.LastFailoverErr = failoverErr @@ -76,14 +78,15 @@ func (s *FailoverState) HandleFailoverError( s.ForceCacheBilling = true } - // 同账号重试:对 RetryableOnSameAccount 的临时性错误,先在同一账号上重试 - if failoverErr.RetryableOnSameAccount && s.SameAccountRetryCount[accountID] < maxSameAccountRetries { + // 同账号重试:对 RetryableOnSameAccount 的临时性错误,先在同一账号上重试。 + // 重试次数上限 retryLimit 由调用方传入(账号级 pool_mode_retry_count 配置)。 + if failoverErr.RetryableOnSameAccount && s.SameAccountRetryCount[accountID] < retryLimit { s.SameAccountRetryCount[accountID]++ logger.FromContext(ctx).Warn("gateway.failover_same_account_retry", zap.Int64("account_id", accountID), zap.Int("upstream_status", failoverErr.StatusCode), zap.Int("same_account_retry_count", s.SameAccountRetryCount[accountID]), - zap.Int("same_account_retry_max", maxSameAccountRetries), + zap.Int("same_account_retry_max", retryLimit), ) if !sleepWithContext(ctx, sameAccountRetryDelay) { return FailoverCanceled diff --git a/backend/internal/handler/failover_loop_test.go b/backend/internal/handler/failover_loop_test.go index 2c65ebc2c8..9fabe75f25 100644 --- a/backend/internal/handler/failover_loop_test.go +++ b/backend/internal/handler/failover_loop_test.go @@ -133,7 +133,7 @@ func TestHandleFailoverError_BasicSwitch(t *testing.T) { fs := NewFailoverState(3, false) err := newTestFailoverErr(500, false, false) - action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", err) + action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err) require.Equal(t, FailoverContinue, action) require.Equal(t, 1, fs.SwitchCount) @@ -150,7 +150,7 @@ func TestHandleFailoverError_BasicSwitch(t *testing.T) { err := newTestFailoverErr(500, false, false) start := time.Now() - action := fs.HandleFailoverError(context.Background(), mock, 100, service.PlatformAntigravity, err) + action := fs.HandleFailoverError(context.Background(), mock, 100, service.PlatformAntigravity, maxSameAccountRetries, err) elapsed := time.Since(start) require.Equal(t, FailoverContinue, action) @@ -166,7 +166,7 @@ func TestHandleFailoverError_BasicSwitch(t *testing.T) { err := newTestFailoverErr(500, false, false) start := time.Now() - action := fs.HandleFailoverError(context.Background(), mock, 200, service.PlatformAntigravity, err) + action := fs.HandleFailoverError(context.Background(), mock, 200, service.PlatformAntigravity, maxSameAccountRetries, err) elapsed := time.Since(start) require.Equal(t, FailoverContinue, action) @@ -181,19 +181,19 @@ func TestHandleFailoverError_BasicSwitch(t *testing.T) { // 第一次切换:0→1 err1 := newTestFailoverErr(500, false, false) - action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", err1) + action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err1) require.Equal(t, FailoverContinue, action) require.Equal(t, 1, fs.SwitchCount) // 第二次切换:1→2 err2 := newTestFailoverErr(502, false, false) - action = fs.HandleFailoverError(context.Background(), mock, 200, "openai", err2) + action = fs.HandleFailoverError(context.Background(), mock, 200, "openai", maxSameAccountRetries, err2) require.Equal(t, FailoverContinue, action) require.Equal(t, 2, fs.SwitchCount) // 第三次已耗尽:SwitchCount(2) >= MaxSwitches(2) err3 := newTestFailoverErr(503, false, false) - action = fs.HandleFailoverError(context.Background(), mock, 300, "openai", err3) + action = fs.HandleFailoverError(context.Background(), mock, 300, "openai", maxSameAccountRetries, err3) require.Equal(t, FailoverExhausted, action) require.Equal(t, 2, fs.SwitchCount, "耗尽时不应继续递增") @@ -212,7 +212,7 @@ func TestHandleFailoverError_BasicSwitch(t *testing.T) { fs := NewFailoverState(0, false) err := newTestFailoverErr(500, false, false) - action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", err) + action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err) require.Equal(t, FailoverExhausted, action) require.Equal(t, 0, fs.SwitchCount) require.Contains(t, fs.FailedAccountIDs, int64(100)) @@ -229,7 +229,7 @@ func TestHandleFailoverError_CacheBilling(t *testing.T) { fs := NewFailoverState(3, true) // hasBoundSession=true err := newTestFailoverErr(500, false, false) - fs.HandleFailoverError(context.Background(), mock, 100, "openai", err) + fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err) require.True(t, fs.ForceCacheBilling) }) @@ -238,7 +238,7 @@ func TestHandleFailoverError_CacheBilling(t *testing.T) { fs := NewFailoverState(3, false) err := newTestFailoverErr(500, false, true) // ForceCacheBilling=true - fs.HandleFailoverError(context.Background(), mock, 100, "openai", err) + fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err) require.True(t, fs.ForceCacheBilling) }) @@ -247,7 +247,7 @@ func TestHandleFailoverError_CacheBilling(t *testing.T) { fs := NewFailoverState(3, false) err := newTestFailoverErr(500, false, false) - fs.HandleFailoverError(context.Background(), mock, 100, "openai", err) + fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err) require.False(t, fs.ForceCacheBilling) }) @@ -257,12 +257,12 @@ func TestHandleFailoverError_CacheBilling(t *testing.T) { // 第一次:ForceCacheBilling=true → 设置 err1 := newTestFailoverErr(500, false, true) - fs.HandleFailoverError(context.Background(), mock, 100, "openai", err1) + fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err1) require.True(t, fs.ForceCacheBilling) // 第二次:ForceCacheBilling=false → 仍然保持 true err2 := newTestFailoverErr(502, false, false) - fs.HandleFailoverError(context.Background(), mock, 200, "openai", err2) + fs.HandleFailoverError(context.Background(), mock, 200, "openai", maxSameAccountRetries, err2) require.True(t, fs.ForceCacheBilling, "ForceCacheBilling 一旦设置不应被重置") }) } @@ -278,7 +278,7 @@ func TestHandleFailoverError_SameAccountRetry(t *testing.T) { err := newTestFailoverErr(400, true, false) start := time.Now() - action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", err) + action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err) elapsed := time.Since(start) require.Equal(t, FailoverContinue, action) @@ -297,7 +297,7 @@ func TestHandleFailoverError_SameAccountRetry(t *testing.T) { err := newTestFailoverErr(400, true, false) for i := 1; i <= maxSameAccountRetries; i++ { - action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", err) + action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err) require.Equal(t, FailoverContinue, action) require.Equal(t, i, fs.SameAccountRetryCount[100]) } @@ -311,12 +311,12 @@ func TestHandleFailoverError_SameAccountRetry(t *testing.T) { err := newTestFailoverErr(400, true, false) for i := 0; i < maxSameAccountRetries; i++ { - fs.HandleFailoverError(context.Background(), mock, 100, "openai", err) + fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err) } require.Equal(t, maxSameAccountRetries, fs.SameAccountRetryCount[100]) // 第 maxSameAccountRetries+1 次:重试耗尽,应切换账号 - action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", err) + action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err) require.Equal(t, FailoverContinue, action) require.Equal(t, 1, fs.SwitchCount) require.Contains(t, fs.FailedAccountIDs, int64(100)) @@ -333,12 +333,12 @@ func TestHandleFailoverError_SameAccountRetry(t *testing.T) { err := newTestFailoverErr(400, true, false) // 账号 100 第一次重试 - action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", err) + action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err) require.Equal(t, FailoverContinue, action) require.Equal(t, 1, fs.SameAccountRetryCount[100]) // 账号 200 第一次重试(独立计数) - action = fs.HandleFailoverError(context.Background(), mock, 200, "openai", err) + action = fs.HandleFailoverError(context.Background(), mock, 200, "openai", maxSameAccountRetries, err) require.Equal(t, FailoverContinue, action) require.Equal(t, 1, fs.SameAccountRetryCount[200]) require.Equal(t, 1, fs.SameAccountRetryCount[100], "账号 100 的计数不应受影响") @@ -351,17 +351,54 @@ func TestHandleFailoverError_SameAccountRetry(t *testing.T) { // 耗尽账号 100 的重试 for i := 0; i < maxSameAccountRetries; i++ { - fs.HandleFailoverError(context.Background(), mock, 100, "openai", err) + fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err) } // 第 maxSameAccountRetries+1 次: 重试耗尽 → 切换 - action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", err) + action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err) require.Equal(t, FailoverContinue, action) // 再次遇到账号 100,计数仍为 maxSameAccountRetries,条件不满足 → 直接切换 - action = fs.HandleFailoverError(context.Background(), mock, 100, "openai", err) + action = fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err) require.Equal(t, FailoverContinue, action) require.Len(t, mock.calls, 2, "第二次耗尽也应调用 TempUnschedule") }) + + t.Run("尊重账号级retryLimit_配置1次只重试1次", func(t *testing.T) { + // 回归测试:Anthropic 等路径此前硬编码同账号重试 3 次,忽略账号 + // pool_mode_retry_count 配置。此处验证传入 retryLimit=1 时只重试 1 次即切换。 + mock := &mockTempUnscheduler{} + fs := NewFailoverState(5, false) + err := newTestFailoverErr(403, true, false) + const retryLimit = 1 + + // 第 1 次:同账号重试 + action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", retryLimit, err) + require.Equal(t, FailoverContinue, action) + require.Equal(t, 1, fs.SameAccountRetryCount[100]) + require.Equal(t, 0, fs.SwitchCount, "首次重试不应切换账号") + require.Empty(t, mock.calls, "未耗尽前不应 TempUnschedule") + + // 第 2 次:已达上限 1 → 不再同账号重试,直接切换 + TempUnschedule + action = fs.HandleFailoverError(context.Background(), mock, 100, "openai", retryLimit, err) + require.Equal(t, FailoverContinue, action) + require.Equal(t, 1, fs.SameAccountRetryCount[100], "重试计数不应超过 retryLimit") + require.Equal(t, 1, fs.SwitchCount, "重试耗尽应切换账号") + require.Contains(t, fs.FailedAccountIDs, int64(100)) + require.Len(t, mock.calls, 1, "重试耗尽应触发 TempUnschedule") + }) + + t.Run("retryLimit为0时立即切换不重试", func(t *testing.T) { + // pool_mode_retry_count=0 表示关闭同账号重试(如 GPT Image 账号)。 + mock := &mockTempUnscheduler{} + fs := NewFailoverState(5, false) + err := newTestFailoverErr(403, true, false) + + action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", 0, err) + require.Equal(t, FailoverContinue, action) + require.Equal(t, 0, fs.SameAccountRetryCount[100], "retryLimit=0 不应发生同账号重试") + require.Equal(t, 1, fs.SwitchCount, "应立即切换账号") + require.Len(t, mock.calls, 1, "应立即 TempUnschedule") + }) } // --------------------------------------------------------------------------- @@ -374,7 +411,7 @@ func TestHandleFailoverError_TempUnschedule(t *testing.T) { fs := NewFailoverState(3, false) err := newTestFailoverErr(500, false, false) // RetryableOnSameAccount=false - fs.HandleFailoverError(context.Background(), mock, 100, "openai", err) + fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err) require.Empty(t, mock.calls) }) @@ -384,10 +421,10 @@ func TestHandleFailoverError_TempUnschedule(t *testing.T) { err := newTestFailoverErr(502, true, false) for i := 0; i < maxSameAccountRetries; i++ { - fs.HandleFailoverError(context.Background(), mock, 42, "openai", err) + fs.HandleFailoverError(context.Background(), mock, 42, "openai", maxSameAccountRetries, err) } // 再次触发时才会执行 TempUnschedule + 切换 - fs.HandleFailoverError(context.Background(), mock, 42, "openai", err) + fs.HandleFailoverError(context.Background(), mock, 42, "openai", maxSameAccountRetries, err) require.Len(t, mock.calls, 1) require.Equal(t, int64(42), mock.calls[0].accountID) @@ -410,7 +447,7 @@ func TestHandleFailoverError_ContextCanceled(t *testing.T) { cancel() // 立即取消 start := time.Now() - action := fs.HandleFailoverError(ctx, mock, 100, "openai", err) + action := fs.HandleFailoverError(ctx, mock, 100, "openai", maxSameAccountRetries, err) elapsed := time.Since(start) require.Equal(t, FailoverCanceled, action) @@ -429,7 +466,7 @@ func TestHandleFailoverError_ContextCanceled(t *testing.T) { cancel() // 立即取消 start := time.Now() - action := fs.HandleFailoverError(ctx, mock, 100, service.PlatformAntigravity, err) + action := fs.HandleFailoverError(ctx, mock, 100, service.PlatformAntigravity, maxSameAccountRetries, err) elapsed := time.Since(start) require.Equal(t, FailoverCanceled, action) @@ -446,10 +483,10 @@ func TestHandleFailoverError_FailedAccountIDs(t *testing.T) { mock := &mockTempUnscheduler{} fs := NewFailoverState(3, false) - fs.HandleFailoverError(context.Background(), mock, 100, "openai", newTestFailoverErr(500, false, false)) + fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, newTestFailoverErr(500, false, false)) require.Contains(t, fs.FailedAccountIDs, int64(100)) - fs.HandleFailoverError(context.Background(), mock, 200, "openai", newTestFailoverErr(502, false, false)) + fs.HandleFailoverError(context.Background(), mock, 200, "openai", maxSameAccountRetries, newTestFailoverErr(502, false, false)) require.Contains(t, fs.FailedAccountIDs, int64(200)) require.Len(t, fs.FailedAccountIDs, 2) }) @@ -458,7 +495,7 @@ func TestHandleFailoverError_FailedAccountIDs(t *testing.T) { mock := &mockTempUnscheduler{} fs := NewFailoverState(0, false) - action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", newTestFailoverErr(500, false, false)) + action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, newTestFailoverErr(500, false, false)) require.Equal(t, FailoverExhausted, action) require.Contains(t, fs.FailedAccountIDs, int64(100)) }) @@ -467,7 +504,7 @@ func TestHandleFailoverError_FailedAccountIDs(t *testing.T) { mock := &mockTempUnscheduler{} fs := NewFailoverState(3, false) - action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", newTestFailoverErr(400, true, false)) + action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, newTestFailoverErr(400, true, false)) require.Equal(t, FailoverContinue, action) require.NotContains(t, fs.FailedAccountIDs, int64(100)) }) @@ -476,8 +513,8 @@ func TestHandleFailoverError_FailedAccountIDs(t *testing.T) { mock := &mockTempUnscheduler{} fs := NewFailoverState(5, false) - fs.HandleFailoverError(context.Background(), mock, 100, "openai", newTestFailoverErr(500, false, false)) - fs.HandleFailoverError(context.Background(), mock, 100, "openai", newTestFailoverErr(500, false, false)) + fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, newTestFailoverErr(500, false, false)) + fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, newTestFailoverErr(500, false, false)) require.Len(t, fs.FailedAccountIDs, 1, "map 天然去重") }) } @@ -492,11 +529,11 @@ func TestHandleFailoverError_LastFailoverErr(t *testing.T) { fs := NewFailoverState(3, false) err1 := newTestFailoverErr(500, false, false) - fs.HandleFailoverError(context.Background(), mock, 100, "openai", err1) + fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err1) require.Equal(t, err1, fs.LastFailoverErr) err2 := newTestFailoverErr(502, false, false) - fs.HandleFailoverError(context.Background(), mock, 200, "openai", err2) + fs.HandleFailoverError(context.Background(), mock, 200, "openai", maxSameAccountRetries, err2) require.Equal(t, err2, fs.LastFailoverErr) }) @@ -505,7 +542,7 @@ func TestHandleFailoverError_LastFailoverErr(t *testing.T) { fs := NewFailoverState(3, false) err := newTestFailoverErr(400, true, false) - fs.HandleFailoverError(context.Background(), mock, 100, "openai", err) + fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err) require.Equal(t, err, fs.LastFailoverErr) }) } @@ -522,30 +559,30 @@ func TestHandleFailoverError_IntegrationScenario(t *testing.T) { // 1. 账号 100 遇到可重试错误,同账号重试 maxSameAccountRetries 次 retryErr := newTestFailoverErr(400, true, false) for i := 0; i < maxSameAccountRetries; i++ { - action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", retryErr) + action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, retryErr) require.Equal(t, FailoverContinue, action) } require.True(t, fs.ForceCacheBilling, "hasBoundSession=true 应设置 ForceCacheBilling") // 2. 账号 100 超过重试上限 → TempUnschedule + 切换 - action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", retryErr) + action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, retryErr) require.Equal(t, FailoverContinue, action) require.Equal(t, 1, fs.SwitchCount) require.Len(t, mock.calls, 1) // 3. 账号 200 遇到不可重试错误 → 直接切换 switchErr := newTestFailoverErr(500, false, false) - action = fs.HandleFailoverError(context.Background(), mock, 200, "openai", switchErr) + action = fs.HandleFailoverError(context.Background(), mock, 200, "openai", maxSameAccountRetries, switchErr) require.Equal(t, FailoverContinue, action) require.Equal(t, 2, fs.SwitchCount) // 4. 账号 300 遇到不可重试错误 → 再切换 - action = fs.HandleFailoverError(context.Background(), mock, 300, "openai", switchErr) + action = fs.HandleFailoverError(context.Background(), mock, 300, "openai", maxSameAccountRetries, switchErr) require.Equal(t, FailoverContinue, action) require.Equal(t, 3, fs.SwitchCount) // 5. 账号 400 → 已耗尽 (SwitchCount=3 >= MaxSwitches=3) - action = fs.HandleFailoverError(context.Background(), mock, 400, "openai", switchErr) + action = fs.HandleFailoverError(context.Background(), mock, 400, "openai", maxSameAccountRetries, switchErr) require.Equal(t, FailoverExhausted, action) // 最终状态验证 @@ -563,21 +600,21 @@ func TestHandleFailoverError_IntegrationScenario(t *testing.T) { // 第一次切换:delay = 0s start := time.Now() - action := fs.HandleFailoverError(context.Background(), mock, 100, service.PlatformAntigravity, err) + action := fs.HandleFailoverError(context.Background(), mock, 100, service.PlatformAntigravity, maxSameAccountRetries, err) elapsed := time.Since(start) require.Equal(t, FailoverContinue, action) require.Less(t, elapsed, 200*time.Millisecond, "第一次切换延迟为 0") // 第二次切换:delay = 1s start = time.Now() - action = fs.HandleFailoverError(context.Background(), mock, 200, service.PlatformAntigravity, err) + action = fs.HandleFailoverError(context.Background(), mock, 200, service.PlatformAntigravity, maxSameAccountRetries, err) elapsed = time.Since(start) require.Equal(t, FailoverContinue, action) require.GreaterOrEqual(t, elapsed, 800*time.Millisecond, "第二次切换延迟约 1s") // 第三次:耗尽(无延迟,因为在检查延迟之前就返回了) start = time.Now() - action = fs.HandleFailoverError(context.Background(), mock, 300, service.PlatformAntigravity, err) + action = fs.HandleFailoverError(context.Background(), mock, 300, service.PlatformAntigravity, maxSameAccountRetries, err) elapsed = time.Since(start) require.Equal(t, FailoverExhausted, action) require.Less(t, elapsed, 200*time.Millisecond, "耗尽时不应有延迟") @@ -589,17 +626,17 @@ func TestHandleFailoverError_IntegrationScenario(t *testing.T) { // 第一次:ForceCacheBilling=false err1 := newTestFailoverErr(500, false, false) - fs.HandleFailoverError(context.Background(), mock, 100, "openai", err1) + fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err1) require.False(t, fs.ForceCacheBilling) // 第二次:ForceCacheBilling=true(Antigravity 粘性会话切换) err2 := newTestFailoverErr(500, false, true) - fs.HandleFailoverError(context.Background(), mock, 200, "openai", err2) + fs.HandleFailoverError(context.Background(), mock, 200, "openai", maxSameAccountRetries, err2) require.True(t, fs.ForceCacheBilling, "错误标志应触发 ForceCacheBilling") // 第三次:ForceCacheBilling=false,但状态仍保持 true err3 := newTestFailoverErr(500, false, false) - fs.HandleFailoverError(context.Background(), mock, 300, "openai", err3) + fs.HandleFailoverError(context.Background(), mock, 300, "openai", maxSameAccountRetries, err3) require.True(t, fs.ForceCacheBilling, "不应重置") }) } @@ -614,7 +651,7 @@ func TestHandleFailoverError_EdgeCases(t *testing.T) { fs := NewFailoverState(3, false) err := newTestFailoverErr(0, false, false) - action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", err) + action := fs.HandleFailoverError(context.Background(), mock, 100, "openai", maxSameAccountRetries, err) require.Equal(t, FailoverContinue, action) }) @@ -623,7 +660,7 @@ func TestHandleFailoverError_EdgeCases(t *testing.T) { fs := NewFailoverState(3, false) err := newTestFailoverErr(500, true, false) - action := fs.HandleFailoverError(context.Background(), mock, 0, "openai", err) + action := fs.HandleFailoverError(context.Background(), mock, 0, "openai", maxSameAccountRetries, err) require.Equal(t, FailoverContinue, action) require.Equal(t, 1, fs.SameAccountRetryCount[0]) }) @@ -633,7 +670,7 @@ func TestHandleFailoverError_EdgeCases(t *testing.T) { fs := NewFailoverState(3, false) err := newTestFailoverErr(500, true, false) - action := fs.HandleFailoverError(context.Background(), mock, -1, "openai", err) + action := fs.HandleFailoverError(context.Background(), mock, -1, "openai", maxSameAccountRetries, err) require.Equal(t, FailoverContinue, action) require.Equal(t, 1, fs.SameAccountRetryCount[-1]) }) @@ -645,7 +682,7 @@ func TestHandleFailoverError_EdgeCases(t *testing.T) { err := newTestFailoverErr(500, false, false) start := time.Now() - action := fs.HandleFailoverError(context.Background(), mock, 100, "", err) + action := fs.HandleFailoverError(context.Background(), mock, 100, "", maxSameAccountRetries, err) elapsed := time.Since(start) require.Equal(t, FailoverContinue, action) diff --git a/backend/internal/handler/gateway_handler.go b/backend/internal/handler/gateway_handler.go index 116346b4a6..f9b0f7de8b 100644 --- a/backend/internal/handler/gateway_handler.go +++ b/backend/internal/handler/gateway_handler.go @@ -448,7 +448,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) { h.handleFailoverExhausted(c, failoverErr, service.PlatformGemini, true) return } - action := fs.HandleFailoverError(c.Request.Context(), h.gatewayService, account.ID, account.Platform, failoverErr) + action := fs.HandleFailoverError(c.Request.Context(), h.gatewayService, account.ID, account.Platform, account.GetPoolModeRetryCount(), failoverErr) switch action { case FailoverContinue: continue @@ -868,7 +868,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) { h.handleFailoverExhausted(c, failoverErr, account.Platform, true) return } - action := fs.HandleFailoverError(c.Request.Context(), h.gatewayService, account.ID, account.Platform, failoverErr) + action := fs.HandleFailoverError(c.Request.Context(), h.gatewayService, account.ID, account.Platform, account.GetPoolModeRetryCount(), failoverErr) switch action { case FailoverContinue: continue diff --git a/backend/internal/handler/gateway_handler_chat_completions.go b/backend/internal/handler/gateway_handler_chat_completions.go index f3805f3a53..af9bcdb344 100644 --- a/backend/internal/handler/gateway_handler_chat_completions.go +++ b/backend/internal/handler/gateway_handler_chat_completions.go @@ -254,7 +254,7 @@ func (h *GatewayHandler) ChatCompletions(c *gin.Context) { h.handleCCFailoverExhausted(c, failoverErr, true) return } - action := fs.HandleFailoverError(c.Request.Context(), h.gatewayService, account.ID, account.Platform, failoverErr) + action := fs.HandleFailoverError(c.Request.Context(), h.gatewayService, account.ID, account.Platform, account.GetPoolModeRetryCount(), failoverErr) switch action { case FailoverContinue: continue diff --git a/backend/internal/handler/gateway_handler_responses.go b/backend/internal/handler/gateway_handler_responses.go index 5b49ca69a2..8a88d5fa57 100644 --- a/backend/internal/handler/gateway_handler_responses.go +++ b/backend/internal/handler/gateway_handler_responses.go @@ -233,7 +233,7 @@ func (h *GatewayHandler) Responses(c *gin.Context) { h.handleResponsesFailoverExhausted(c, failoverErr, true) return } - action := fs.HandleFailoverError(requestCtx, h.gatewayService, account.ID, account.Platform, failoverErr) + action := fs.HandleFailoverError(requestCtx, h.gatewayService, account.ID, account.Platform, account.GetPoolModeRetryCount(), failoverErr) switch action { case FailoverContinue: continue diff --git a/backend/internal/handler/gemini_v1beta_handler.go b/backend/internal/handler/gemini_v1beta_handler.go index 86be1062e9..b1653c6b3f 100644 --- a/backend/internal/handler/gemini_v1beta_handler.go +++ b/backend/internal/handler/gemini_v1beta_handler.go @@ -482,7 +482,7 @@ func (h *GatewayHandler) GeminiV1BetaModels(c *gin.Context) { if err != nil { var failoverErr *service.UpstreamFailoverError if errors.As(err, &failoverErr) { - failoverAction := fs.HandleFailoverError(c.Request.Context(), h.gatewayService, account.ID, account.Platform, failoverErr) + failoverAction := fs.HandleFailoverError(c.Request.Context(), h.gatewayService, account.ID, account.Platform, account.GetPoolModeRetryCount(), failoverErr) switch failoverAction { case FailoverContinue: continue