From 2ab41b92b6a20f818824e38fc3c9d8fa7bdbfa89 Mon Sep 17 00:00:00 2001 From: IanShaw027 Date: Thu, 20 Aug 2026 15:46:53 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20Grok=20=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E4=B8=8E=20errcheck=20=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E7=9A=84=20CI=20=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 对齐 Grok ModelInput 对 function_call.status 的剥离,恢复畸形 tools 不改写 tool_choice 的行为,并检查测试中的类型断言。 --- .../service/openai_codex_transform_test.go | 66 ++++++++++++++----- .../internal/service/openai_gateway_grok.go | 4 +- .../openai_gateway_grok_tool_protocol_test.go | 2 +- .../openai_responses_input_compat_test.go | 24 +++++-- 4 files changed, 70 insertions(+), 26 deletions(-) diff --git a/backend/internal/service/openai_codex_transform_test.go b/backend/internal/service/openai_codex_transform_test.go index 7b1606f76e..56be9570b0 100644 --- a/backend/internal/service/openai_codex_transform_test.go +++ b/backend/internal/service/openai_codex_transform_test.go @@ -293,11 +293,17 @@ func TestApplyCodexOAuthTransform_NormalizesNativeToolCallPairsByType(t *testing applyCodexOAuthTransform(reqBody, false, false) - input := reqBody["input"].([]any) - custom := input[0].(map[string]any) - customOutput := input[1].(map[string]any) - search := input[2].(map[string]any) - searchOutput := input[3].(map[string]any) + input, ok := reqBody["input"].([]any) + require.True(t, ok) + require.Len(t, input, 4) + custom, ok := input[0].(map[string]any) + require.True(t, ok) + customOutput, ok := input[1].(map[string]any) + require.True(t, ok) + search, ok := input[2].(map[string]any) + require.True(t, ok) + searchOutput, ok := input[3].(map[string]any) + require.True(t, ok) require.NotContains(t, custom, "id", "the invalid replay item id must be removed, not fabricated") require.Equal(t, "ctc_custom", custom["call_id"]) require.Equal(t, custom["call_id"], customOutput["call_id"]) @@ -319,13 +325,23 @@ func TestApplyCodexOAuthTransform_PreservesNativeCallIDsWhenRequested(t *testing applyCodexOAuthTransformWithOptions(reqBody, codexOAuthTransformOptions{PreserveToolCallIDs: true}) - input := reqBody["input"].([]any) - require.Equal(t, "ctc_custom", input[0].(map[string]any)["id"]) - require.Equal(t, "call_custom", input[0].(map[string]any)["call_id"]) - require.Equal(t, "call_custom", input[1].(map[string]any)["call_id"]) - require.Equal(t, "tsc_search", input[2].(map[string]any)["id"]) - require.Equal(t, "call_search", input[2].(map[string]any)["call_id"]) - require.Equal(t, "call_search", input[3].(map[string]any)["call_id"]) + input, ok := reqBody["input"].([]any) + require.True(t, ok) + require.Len(t, input, 4) + first, ok := input[0].(map[string]any) + require.True(t, ok) + second, ok := input[1].(map[string]any) + require.True(t, ok) + third, ok := input[2].(map[string]any) + require.True(t, ok) + fourth, ok := input[3].(map[string]any) + require.True(t, ok) + require.Equal(t, "ctc_custom", first["id"]) + require.Equal(t, "call_custom", first["call_id"]) + require.Equal(t, "call_custom", second["call_id"]) + require.Equal(t, "tsc_search", third["id"]) + require.Equal(t, "call_search", third["call_id"]) + require.Equal(t, "call_search", fourth["call_id"]) } func TestApplyCodexOAuthTransform_BoundsEquivalentNativeToolCallIDsWithPairing(t *testing.T) { @@ -340,9 +356,17 @@ func TestApplyCodexOAuthTransform_BoundsEquivalentNativeToolCallIDsWithPairing(t applyCodexOAuthTransformWithOptions(reqBody, codexOAuthTransformOptions{PreserveToolCallIDs: true}) - input := reqBody["input"].([]any) - callID := input[0].(map[string]any)["call_id"].(string) - outputCallID := input[1].(map[string]any)["call_id"].(string) + input, ok := reqBody["input"].([]any) + require.True(t, ok) + require.Len(t, input, 2) + first, ok := input[0].(map[string]any) + require.True(t, ok) + second, ok := input[1].(map[string]any) + require.True(t, ok) + callID, ok := first["call_id"].(string) + require.True(t, ok) + outputCallID, ok := second["call_id"].(string) + require.True(t, ok) require.LessOrEqual(t, len(callID), codexCallIDMaxLength) require.True(t, strings.HasPrefix(callID, "ctc_")) require.Equal(t, callID, outputCallID) @@ -1763,9 +1787,15 @@ func TestNormalizeOpenAIResponsesImageGenerationTools_StripsGPTImage2InputFideli }} require.True(t, normalizeOpenAIResponsesImageGenerationTools(reqBody)) - tools := reqBody["tools"].([]any) - require.NotContains(t, tools[0].(map[string]any), "input_fidelity") - require.Equal(t, "high", tools[1].(map[string]any)["input_fidelity"]) + tools, ok := reqBody["tools"].([]any) + require.True(t, ok) + require.Len(t, tools, 2) + first, ok := tools[0].(map[string]any) + require.True(t, ok) + second, ok := tools[1].(map[string]any) + require.True(t, ok) + require.NotContains(t, first, "input_fidelity") + require.Equal(t, "high", second["input_fidelity"]) } func TestOpenAIRequestBodyImageGenerationToolNeedsNormalization_GPTImage2InputFidelity(t *testing.T) { diff --git a/backend/internal/service/openai_gateway_grok.go b/backend/internal/service/openai_gateway_grok.go index 9e2c70579b..cc4709443a 100644 --- a/backend/internal/service/openai_gateway_grok.go +++ b/backend/internal/service/openai_gateway_grok.go @@ -984,7 +984,9 @@ func sanitizeGrokResponsesTools(body []byte) ([]byte, error) { return deleteGrokOrphanToolControls(body) } if !tools.IsArray() { - return deleteGrokOrphanToolControls(body) + // Leave malformed tools (and their tool_choice) untouched instead of + // inventing an empty inventory. + return body, nil } rawTools := tools.Array() diff --git a/backend/internal/service/openai_gateway_grok_tool_protocol_test.go b/backend/internal/service/openai_gateway_grok_tool_protocol_test.go index cc25cb1924..dd2cd5bb66 100644 --- a/backend/internal/service/openai_gateway_grok_tool_protocol_test.go +++ b/backend/internal/service/openai_gateway_grok_tool_protocol_test.go @@ -106,7 +106,7 @@ func TestPatchGrokResponsesBodyWithClientToolsLowersDiscoveredToolsOutput(t *tes {"type":"function","name":"multi_agent_v1__wait_agent","parameters":{"type":"object","properties":{"timeout_ms":{"type":"integer"}},"additionalProperties":false}} ], "input":[ - {"type":"function_call","call_id":"call_fixture","name":"tool_search","arguments":"{\"query\":\"subagent\"}","status":"completed"}, + {"type":"function_call","call_id":"call_fixture","name":"tool_search","arguments":"{\"query\":\"subagent\"}"}, {"type":"function_call_output","call_id":"call_fixture","output":`+string(mustMarshalJSONForTest(t, output))+`} ] }`, string(patched)) diff --git a/backend/internal/service/openai_responses_input_compat_test.go b/backend/internal/service/openai_responses_input_compat_test.go index 063148ec13..fc9fa7418a 100644 --- a/backend/internal/service/openai_responses_input_compat_test.go +++ b/backend/internal/service/openai_responses_input_compat_test.go @@ -34,7 +34,8 @@ func TestSanitizeOpenAIResponsesOrphanToolOutputs(t *testing.T) { reqBody := map[string]any{"input": input} require.True(t, sanitizeOpenAIResponsesOrphanToolOutputs(reqBody, input, false)) - got := reqBody["input"].([]any) + got, ok := reqBody["input"].([]any) + require.True(t, ok) require.Empty(t, got) }) @@ -90,14 +91,25 @@ func TestOpenAIResponsesInputTextIsNeverSilentlyTruncated(t *testing.T) { reqBody := map[string]any{"input": input} require.False(t, truncateOpenAIResponsesInputText(reqBody)) - require.Equal(t, atLimit, input[0].(map[string]any)["output"]) + first, ok := input[0].(map[string]any) + require.True(t, ok) + require.Equal(t, atLimit, first["output"]) for _, rawItem := range input[1:5] { - item := rawItem.(map[string]any) + item, ok := rawItem.(map[string]any) + require.True(t, ok) require.Equal(t, oversized, item["output"]) } - content := input[5].(map[string]any)["content"].([]any) - require.Equal(t, "short", content[0].(map[string]any)["text"]) - require.Equal(t, oversized, content[1].(map[string]any)["text"]) + last, ok := input[5].(map[string]any) + require.True(t, ok) + content, ok := last["content"].([]any) + require.True(t, ok) + require.Len(t, content, 2) + shortPart, ok := content[0].(map[string]any) + require.True(t, ok) + oversizedPart, ok := content[1].(map[string]any) + require.True(t, ok) + require.Equal(t, "short", shortPart["text"]) + require.Equal(t, oversized, oversizedPart["text"]) } func TestOpenAIResponsesInputNeverRequestsPreemptiveTruncation(t *testing.T) {