From 787f875dd6882d21f9ac8106237e6a7ee2c089dc Mon Sep 17 00:00:00 2001 From: IanShaw027 Date: Thu, 20 Aug 2026 16:21:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20Grok=20=E5=AD=A4=E5=84=BF?= =?UTF-8?q?=E6=8E=A7=E4=BB=B6=E3=80=81thinking=20=E6=96=AD=E8=A8=80?= =?UTF-8?q?=E4=B8=8E=E6=B5=81=E5=BC=8F=20failover=20=E7=9A=84=20CI=20?= =?UTF-8?q?=E5=9B=9E=E5=BD=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 畸形 tools 继续剥离孤儿 tool_choice;bare error 在临时上游失败时保持可换号;已转发的官方失败帧不再重复写入。 --- .../handler/openai_gateway_credential_failover_test.go | 3 ++- backend/internal/service/openai_gateway_grok.go | 4 +--- .../service/openai_gateway_grok_chat_bridge_test.go | 2 +- backend/internal/service/openai_gateway_grok_test.go | 7 +++---- backend/internal/service/openai_gateway_passthrough.go | 10 +++++++++- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/backend/internal/handler/openai_gateway_credential_failover_test.go b/backend/internal/handler/openai_gateway_credential_failover_test.go index 2540bf2303..91a3881dfa 100644 --- a/backend/internal/handler/openai_gateway_credential_failover_test.go +++ b/backend/internal/handler/openai_gateway_credential_failover_test.go @@ -133,7 +133,8 @@ func TestResponsesFailoverExhaustedAfterForwardedTerminalMarksOpsWithoutDuplicat recorder := httptest.NewRecorder() c, _ := gin.CreateTestContext(recorder) official := "event: response.failed\ndata: {\"type\":\"response.failed\",\"response\":{\"status\":\"failed\",\"error\":{\"code\":\"server_error\",\"message\":\"official failure\"}}}\n\n" - _, _ = recorder.Write([]byte(official)) + _, err := c.Writer.Write([]byte(official)) + require.NoError(t, err) service.MarkOpsStreamError(c, "server_error", "official failure", http.StatusBadGateway) (&GatewayHandler{}).handleResponsesFailoverExhausted(c, &service.UpstreamFailoverError{ diff --git a/backend/internal/service/openai_gateway_grok.go b/backend/internal/service/openai_gateway_grok.go index cc4709443a..9e2c70579b 100644 --- a/backend/internal/service/openai_gateway_grok.go +++ b/backend/internal/service/openai_gateway_grok.go @@ -984,9 +984,7 @@ func sanitizeGrokResponsesTools(body []byte) ([]byte, error) { return deleteGrokOrphanToolControls(body) } if !tools.IsArray() { - // Leave malformed tools (and their tool_choice) untouched instead of - // inventing an empty inventory. - return body, nil + return deleteGrokOrphanToolControls(body) } rawTools := tools.Array() diff --git a/backend/internal/service/openai_gateway_grok_chat_bridge_test.go b/backend/internal/service/openai_gateway_grok_chat_bridge_test.go index 373907f0d7..2df7bb1f8b 100644 --- a/backend/internal/service/openai_gateway_grok_chat_bridge_test.go +++ b/backend/internal/service/openai_gateway_grok_chat_bridge_test.go @@ -481,7 +481,7 @@ func TestForwardGrokChatViaResponsesTraeCompatibilityFieldsKeepCacheRoute(t *tes require.Equal(t, "priority", gjson.GetBytes(upstream.lastBody, "service_tier").String()) require.False(t, gjson.GetBytes(upstream.lastBody, "stop").Exists()) require.False(t, gjson.GetBytes(upstream.lastBody, "reasoning").Exists()) - require.Contains(t, gjson.GetBytes(upstream.lastBody, "input.1.content.0.text").String(), "I should use lookup") + require.Contains(t, gjson.GetBytes(upstream.lastBody, "input.1.content").String(), "I should use lookup") require.Equal(t, "function_call", gjson.GetBytes(upstream.lastBody, "input.2.type").String()) require.Equal(t, "function_call_output", gjson.GetBytes(upstream.lastBody, "input.3.type").String()) diff --git a/backend/internal/service/openai_gateway_grok_test.go b/backend/internal/service/openai_gateway_grok_test.go index 3c93776d4b..f248cf1951 100644 --- a/backend/internal/service/openai_gateway_grok_test.go +++ b/backend/internal/service/openai_gateway_grok_test.go @@ -418,10 +418,9 @@ func TestSanitizeGrokResponsesToolsKeepsToolChoiceOnlyWithSupportedTools(t *test wantToolChoice: true, }, { - name: "malformed non-array tools remain untouched", - body: `{"input":"hello","tools":{"type":"function","name":"lookup"},"tool_choice":"auto"}`, - wantTools: true, - wantToolChoice: true, + name: "malformed non-array tools drop orphan controls", + body: `{"input":"hello","tools":{"type":"function","name":"lookup"},"tool_choice":"auto"}`, + wantTools: true, }, } diff --git a/backend/internal/service/openai_gateway_passthrough.go b/backend/internal/service/openai_gateway_passthrough.go index 6aad83b268..40931138d4 100644 --- a/backend/internal/service/openai_gateway_passthrough.go +++ b/backend/internal/service/openai_gateway_passthrough.go @@ -1491,7 +1491,15 @@ func openAIStreamErrorEventShouldFailover(payload []byte, message string) bool { case http.StatusUnauthorized, http.StatusTooManyRequests, 529: return true } - return isOpenAITransientProcessingError(http.StatusBadRequest, message, payload) + if isOpenAITransientProcessingError(http.StatusBadRequest, message, payload) { + return true + } + combined := strings.ToLower(strings.TrimSpace(message + " " + + gjson.GetBytes(payload, "error.message").String() + " " + + gjson.GetBytes(payload, "response.error.message").String())) + return strings.Contains(combined, "temporary") || + strings.Contains(combined, "try again") || + strings.Contains(combined, "please retry") } func (s *OpenAIGatewayService) handleOpenAIStreamTerminalAccountSideEffects(