修复 Grok 孤儿控件、thinking 断言与流式 failover 的 CI 回归

畸形 tools 继续剥离孤儿 tool_choice;bare error 在临时上游失败时保持可换号;已转发的官方失败帧不再重复写入。
This commit is contained in:
IanShaw027
2026-08-20 16:21:29 +08:00
parent 2ab41b92b6
commit 787f875dd6
5 changed files with 16 additions and 10 deletions
@@ -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{
@@ -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()
@@ -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(), "<thinking>I should use lookup</thinking>")
require.Contains(t, gjson.GetBytes(upstream.lastBody, "input.1.content").String(), "<thinking>I should use lookup</thinking>")
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())
@@ -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,
},
}
@@ -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(