From 41f12e874d443cf2ee021414f73e42df9d928458 Mon Sep 17 00:00:00 2001 From: wucm667 Date: Fri, 24 Jul 2026 16:25:53 +0800 Subject: [PATCH] fix(grok): drop orphaned Responses tool choice --- .../internal/service/openai_gateway_grok.go | 8 ++- .../service/openai_gateway_grok_test.go | 54 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/backend/internal/service/openai_gateway_grok.go b/backend/internal/service/openai_gateway_grok.go index d887ff73fc..efd4a328ee 100644 --- a/backend/internal/service/openai_gateway_grok.go +++ b/backend/internal/service/openai_gateway_grok.go @@ -663,7 +663,13 @@ var grokResponsesSupportedToolTypes = map[string]struct{}{ func sanitizeGrokResponsesTools(body []byte) ([]byte, error) { tools := gjson.GetBytes(body, "tools") - if !tools.Exists() || !tools.IsArray() { + if !tools.Exists() { + if gjson.GetBytes(body, "tool_choice").Exists() { + return sjson.DeleteBytes(body, "tool_choice") + } + return body, nil + } + if !tools.IsArray() { return body, nil } diff --git a/backend/internal/service/openai_gateway_grok_test.go b/backend/internal/service/openai_gateway_grok_test.go index 3a8e347332..ce6424d28f 100644 --- a/backend/internal/service/openai_gateway_grok_test.go +++ b/backend/internal/service/openai_gateway_grok_test.go @@ -209,6 +209,60 @@ func TestPatchGrokResponsesBodyDropsToolChoiceWhenNoSupportedToolsRemain(t *test require.False(t, gjson.GetBytes(patched, "tool_choice").Exists()) } +func TestSanitizeGrokResponsesToolsKeepsToolChoiceOnlyWithSupportedTools(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + body string + wantTools bool + wantToolChoice bool + }{ + { + name: "missing tools with string tool choice", + body: `{"input":"hello","tool_choice":"auto"}`, + }, + { + name: "missing tools with object tool choice", + body: `{"input":"hello","tool_choice":{"type":"function","name":"lookup"}}`, + }, + { + name: "empty tools", + body: `{"input":"hello","tools":[],"tool_choice":"auto"}`, + wantTools: true, + }, + { + name: "all tools unsupported", + body: `{"input":"hello","tools":[{"type":"namespace","name":"client_tools"}],"tool_choice":"auto"}`, + }, + { + name: "supported tool", + body: `{"input":"hello","tools":[{"type":"function","name":"lookup"}],"tool_choice":"auto"}`, + wantTools: true, + wantToolChoice: true, + }, + { + name: "malformed non-array tools remain untouched", + body: `{"input":"hello","tools":{"type":"function","name":"lookup"},"tool_choice":"auto"}`, + wantTools: true, + wantToolChoice: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + patched, err := sanitizeGrokResponsesTools([]byte(tt.body)) + require.NoError(t, err) + require.True(t, json.Valid(patched)) + require.Equal(t, tt.wantTools, gjson.GetBytes(patched, "tools").Exists()) + require.Equal(t, tt.wantToolChoice, gjson.GetBytes(patched, "tool_choice").Exists()) + if tt.wantToolChoice { + require.Equal(t, "auto", gjson.GetBytes(patched, "tool_choice").String()) + } + }) + } +} + func TestPatchGrokResponsesBodyPromotesCodexAdditionalTools(t *testing.T) { t.Parallel()