From c628b3eea70d3a8813074fdacc252d8c3af16bba Mon Sep 17 00:00:00 2001 From: IanShaw <131567472+IanShaw027@users.noreply.github.com> Date: Thu, 20 Aug 2026 07:07:55 -0700 Subject: [PATCH] =?UTF-8?q?=E8=AE=A9=20Grok=20stream=20idle=20=E9=87=8D?= =?UTF-8?q?=E8=AF=95=E4=B8=8A=E9=99=90=E4=BD=9C=E7=94=A8=E4=BA=8E=E4=B8=BB?= =?UTF-8?q?=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/handler/failover_loop.go | 15 ++++++++++++++- backend/internal/handler/failover_loop_test.go | 7 +++++++ backend/internal/handler/grok_media.go | 2 +- .../internal/handler/openai_chat_completions.go | 2 +- .../internal/handler/openai_gateway_handler.go | 4 ++-- backend/internal/handler/openai_images.go | 2 +- .../service/openai_gateway_grok_chat_bridge.go | 8 ++++---- 7 files changed, 30 insertions(+), 10 deletions(-) diff --git a/backend/internal/handler/failover_loop.go b/backend/internal/handler/failover_loop.go index c51779c047..e626d66e05 100644 --- a/backend/internal/handler/failover_loop.go +++ b/backend/internal/handler/failover_loop.go @@ -82,6 +82,19 @@ func sameAccountRetryDeadlineAllows(failoverErr *service.UpstreamFailoverError) return failoverErr == nil || failoverErr.SameAccountRetryDeadline.IsZero() || time.Now().Before(failoverErr.SameAccountRetryDeadline) } +// effectiveSameAccountRetryLimit applies an error-specific cap without +// overriding an explicit account setting of zero (which disables retries). +func effectiveSameAccountRetryLimit(failoverErr *service.UpstreamFailoverError, account *service.Account) int { + if account == nil { + return 0 + } + limit := account.GetPoolModeRetryCount() + if limit > 0 && failoverErr != nil && failoverErr.SameAccountRetryMax > 0 && failoverErr.SameAccountRetryMax < limit { + return failoverErr.SameAccountRetryMax + } + return limit +} + // FailoverState 跨循环迭代共享的 failover 状态 type FailoverState struct { SwitchCount int @@ -171,7 +184,7 @@ func (s *FailoverState) HandleFailoverError( // 同账号重试不算切换账号,粘性会话仅在实际切换时强制缓存计费。 retryCount := s.SameAccountRetryCount[accountID] - if failoverErr.SameAccountRetryMax > 0 && (retryLimit <= 0 || failoverErr.SameAccountRetryMax < retryLimit) { + if failoverErr.SameAccountRetryMax > 0 && failoverErr.SameAccountRetryMax < retryLimit { retryLimit = failoverErr.SameAccountRetryMax } sameAccountRetryAllowed := failoverErr.RetryableOnSameAccount && retryLimit > 0 && retryCount < retryLimit diff --git a/backend/internal/handler/failover_loop_test.go b/backend/internal/handler/failover_loop_test.go index 40602f9df9..bcf8110850 100644 --- a/backend/internal/handler/failover_loop_test.go +++ b/backend/internal/handler/failover_loop_test.go @@ -70,6 +70,13 @@ func TestSameAccountRetryDeadlineAllows(t *testing.T) { })) } +func TestEffectiveSameAccountRetryLimitHonorsErrorCapAndDisabledAccount(t *testing.T) { + account := &service.Account{Type: service.AccountTypeAPIKey, Credentials: map[string]any{"pool_mode": true, "pool_mode_retry_count": float64(3)}} + require.Equal(t, 1, effectiveSameAccountRetryLimit(&service.UpstreamFailoverError{SameAccountRetryMax: 1}, account)) + account.Credentials["pool_mode_retry_count"] = float64(0) + require.Equal(t, 0, effectiveSameAccountRetryLimit(&service.UpstreamFailoverError{SameAccountRetryMax: 1}, account)) +} + // --------------------------------------------------------------------------- // Helper // --------------------------------------------------------------------------- diff --git a/backend/internal/handler/grok_media.go b/backend/internal/handler/grok_media.go index 6e5619b6e5..94f267a970 100644 --- a/backend/internal/handler/grok_media.go +++ b/backend/internal/handler/grok_media.go @@ -361,7 +361,7 @@ func (h *OpenAIGatewayHandler) handleGrokMedia(c *gin.Context, endpoint service. return } if failoverErr.RetryableOnSameAccount { - retryLimit := account.GetPoolModeRetryCount() + retryLimit := effectiveSameAccountRetryLimit(failoverErr, account) if sameAccountRetryCount[account.ID] < retryLimit && sameAccountRetryDeadlineAllows(failoverErr) { sameAccountRetryCount[account.ID]++ retryDelay := sameAccountRetryDelayFor(failoverErr, sameAccountRetryCount[account.ID]) diff --git a/backend/internal/handler/openai_chat_completions.go b/backend/internal/handler/openai_chat_completions.go index 1dd8f63ceb..78dd7eb098 100644 --- a/backend/internal/handler/openai_chat_completions.go +++ b/backend/internal/handler/openai_chat_completions.go @@ -327,7 +327,7 @@ func (h *OpenAIGatewayHandler) ChatCompletions(c *gin.Context) { } // Pool mode: retry on the same account if failoverErr.RetryableOnSameAccount { - retryLimit := account.GetPoolModeRetryCount() + retryLimit := effectiveSameAccountRetryLimit(failoverErr, account) if sameAccountRetryCount[account.ID] < retryLimit && sameAccountRetryDeadlineAllows(failoverErr) { sameAccountRetryCount[account.ID]++ retryDelay := sameAccountRetryDelayFor(failoverErr, sameAccountRetryCount[account.ID]) diff --git a/backend/internal/handler/openai_gateway_handler.go b/backend/internal/handler/openai_gateway_handler.go index 1f7c849a6a..bbece97730 100644 --- a/backend/internal/handler/openai_gateway_handler.go +++ b/backend/internal/handler/openai_gateway_handler.go @@ -717,7 +717,7 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) { } // 池模式:同账号重试 if failoverErr.RetryableOnSameAccount { - retryLimit := account.GetPoolModeRetryCount() + retryLimit := effectiveSameAccountRetryLimit(failoverErr, account) if sameAccountRetryCount[account.ID] < retryLimit && sameAccountRetryDeadlineAllows(failoverErr) { sameAccountRetryCount[account.ID]++ retryDelay := sameAccountRetryDelayFor(failoverErr, sameAccountRetryCount[account.ID]) @@ -1266,7 +1266,7 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) { } // 池模式:同账号重试 if failoverErr.RetryableOnSameAccount { - retryLimit := account.GetPoolModeRetryCount() + retryLimit := effectiveSameAccountRetryLimit(failoverErr, account) if sameAccountRetryCount[account.ID] < retryLimit && sameAccountRetryDeadlineAllows(failoverErr) { sameAccountRetryCount[account.ID]++ retryDelay := sameAccountRetryDelayFor(failoverErr, sameAccountRetryCount[account.ID]) diff --git a/backend/internal/handler/openai_images.go b/backend/internal/handler/openai_images.go index a82c7b7193..6ff30eccfe 100644 --- a/backend/internal/handler/openai_images.go +++ b/backend/internal/handler/openai_images.go @@ -304,7 +304,7 @@ func (h *OpenAIGatewayHandler) Images(c *gin.Context) { return } if failoverErr.RetryableOnSameAccount { - retryLimit := account.GetPoolModeRetryCount() + retryLimit := effectiveSameAccountRetryLimit(failoverErr, account) if sameAccountRetryCount[account.ID] < retryLimit && sameAccountRetryDeadlineAllows(failoverErr) { sameAccountRetryCount[account.ID]++ retryDelay := sameAccountRetryDelayFor(failoverErr, sameAccountRetryCount[account.ID]) diff --git a/backend/internal/service/openai_gateway_grok_chat_bridge.go b/backend/internal/service/openai_gateway_grok_chat_bridge.go index 1c9f6c4b7d..84e2c95e72 100644 --- a/backend/internal/service/openai_gateway_grok_chat_bridge.go +++ b/backend/internal/service/openai_gateway_grok_chat_bridge.go @@ -635,10 +635,10 @@ func (s *OpenAIGatewayService) forwardGrokChatCompletionsViaResponses( if s.shouldFailoverGrokUpstreamError(resp.StatusCode, respBody) { retryable, retryDelay, retryDeadline := grokSameAccountRetryMetadata(account, resp.StatusCode, respBody) return nil, &UpstreamFailoverError{ - StatusCode: resp.StatusCode, - ResponseBody: respBody, - ResponseHeaders: resp.Header.Clone(), - RetryableOnSameAccount: retryable, + StatusCode: resp.StatusCode, + ResponseBody: respBody, + ResponseHeaders: resp.Header.Clone(), + RetryableOnSameAccount: retryable, RequestScopedTransient: retryable && resp.StatusCode == http.StatusTooManyRequests, SameAccountRetryDelay: retryDelay, SameAccountRetryDeadline: retryDeadline,