diff --git a/backend/internal/service/openai_gateway_grok.go b/backend/internal/service/openai_gateway_grok.go index adb5f8fbc3..d6db84cdb4 100644 --- a/backend/internal/service/openai_gateway_grok.go +++ b/backend/internal/service/openai_gateway_grok.go @@ -558,7 +558,7 @@ func normalizeGrokResponsesReasoningEffort(body []byte, upstreamModel string) ([ if !value.Exists() { continue } - normalized, keep := normalizeGrokReasoningEffortValue(value.String()) + normalized, keep := normalizeGrokReasoningEffortValue(value.String(), upstreamModel) if !supportsEffort || !keep { out, err = sjson.DeleteBytes(out, field) } else { @@ -569,7 +569,7 @@ func normalizeGrokResponsesReasoningEffort(body []byte, upstreamModel string) ([ } } if camel := gjson.GetBytes(out, "reasoningEffort"); camel.Exists() { - normalized, keep := normalizeGrokReasoningEffortValue(camel.String()) + normalized, keep := normalizeGrokReasoningEffortValue(camel.String(), upstreamModel) out, err = sjson.DeleteBytes(out, "reasoningEffort") if err != nil { return nil, fmt.Errorf("remove Grok reasoningEffort: %w", err) @@ -595,7 +595,7 @@ func normalizeGrokChatReasoningEffort(body []byte, upstreamModel string) ([]byte if raw == "" { raw = strings.TrimSpace(gjson.GetBytes(body, "reasoningEffort").String()) } - normalized, keep := normalizeGrokReasoningEffortValue(raw) + normalized, keep := normalizeGrokReasoningEffortValue(raw, upstreamModel) keep = keep && grokSupportsReasoningEffort(upstreamModel) out := body var err error @@ -615,20 +615,30 @@ func normalizeGrokChatReasoningEffort(body []byte, upstreamModel string) ([]byte return out, err } -func normalizeGrokReasoningEffortValue(raw string) (string, bool) { +func normalizeGrokReasoningEffortValue(raw, model string) (string, bool) { value := strings.NewReplacer("-", "", "_", "", " ", "").Replace(strings.ToLower(strings.TrimSpace(raw))) switch value { case "none", "low", "medium", "high": return value, true case "minimal": return "low", true - case "xhigh", "extrahigh", "max", "ultra": + case "xhigh", "extrahigh": + if grokSupportsXHighReasoningEffort(model) { + return "xhigh", true + } + return "high", true + case "max", "ultra": return "high", true default: return "", false } } +func grokSupportsXHighReasoningEffort(model string) bool { + model = strings.ToLower(xai.StripGrokProviderPrefix(strings.TrimSpace(model))) + return model == "grok-4.6" || model == "grok-4.6-latest" +} + func grokSupportsReasoningEffort(model string) bool { model = strings.ToLower(xai.StripGrokProviderPrefix(strings.TrimSpace(model))) switch model { diff --git a/backend/internal/service/openai_gateway_grok_test.go b/backend/internal/service/openai_gateway_grok_test.go index 243736b1c8..8e34ec85db 100644 --- a/backend/internal/service/openai_gateway_grok_test.go +++ b/backend/internal/service/openai_gateway_grok_test.go @@ -157,18 +157,20 @@ func TestPatchGrokResponsesBodyNormalizesReasoningEffortAliases(t *testing.T) { t.Parallel() tests := []struct { - name string - body string - path string - want string + name string + body string + upstreamModel string + path string + want string }{ - {name: "minimal nested", body: `{"input":"hi","reasoning":{"effort":"minimal"}}`, path: "reasoning.effort", want: "low"}, - {name: "xhigh snake", body: `{"input":"hi","reasoning_effort":"xhigh"}`, path: "reasoning_effort", want: "high"}, - {name: "max camel", body: `{"input":"hi","reasoningEffort":"max"}`, path: "reasoning_effort", want: "high"}, + {name: "minimal nested", body: `{"input":"hi","reasoning":{"effort":"minimal"}}`, upstreamModel: "grok-4.5", path: "reasoning.effort", want: "low"}, + {name: "xhigh stays high for 4.5", body: `{"input":"hi","reasoning_effort":"xhigh"}`, upstreamModel: "grok-4.5", path: "reasoning_effort", want: "high"}, + {name: "xhigh nested for 4.6", body: `{"input":"hi","reasoning":{"effort":"xhigh"}}`, upstreamModel: "grok-4.6", path: "reasoning.effort", want: "xhigh"}, + {name: "xhigh snake for 4.6 latest", body: `{"input":"hi","reasoning_effort":"xhigh"}`, upstreamModel: "grok-4.6-latest", path: "reasoning_effort", want: "xhigh"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - patched, err := patchGrokResponsesBody([]byte(tt.body), "grok-4.5") + patched, err := patchGrokResponsesBody([]byte(tt.body), tt.upstreamModel) require.NoError(t, err) require.Equal(t, tt.want, gjson.GetBytes(patched, tt.path).String(), string(patched)) require.False(t, gjson.GetBytes(patched, "reasoningEffort").Exists()) @@ -194,6 +196,10 @@ func TestNormalizeGrokChatReasoningEffort(t *testing.T) { require.Equal(t, "high", gjson.GetBytes(patched, "reasoning_effort").String()) require.False(t, gjson.GetBytes(patched, "reasoningEffort").Exists()) + patched, err = normalizeGrokChatReasoningEffort([]byte(`{"reasoning_effort":"xhigh"}`), "grok-4.6") + require.NoError(t, err) + require.Equal(t, "xhigh", gjson.GetBytes(patched, "reasoning_effort").String()) + patched, err = normalizeGrokChatReasoningEffort([]byte(`{"reasoning_effort":"high"}`), "grok-composer-2.5-fast") require.NoError(t, err) require.False(t, gjson.GetBytes(patched, "reasoning_effort").Exists())