feat(xai): support grok-4.5 reasoning effort mapping#519
Conversation
xAI's Chat Completions API takes reasoning effort as a flat top-level reasoning_effort string (grok-4.5: low/medium/high, default high), while GoModel's normalized shape is the nested reasoning.effort object. Add an AdaptChatRequest hook to the xai provider (same pattern as deepseek and gemini) that rewrites the nested shape into the flat field, downgrading xhigh/max to high except on the multi-agent Grok family, which accepts xhigh natively. The Responses API keeps the nested shape untouched since it is xAI-native there. Also add grok-4.5 to the xai contract fixture, document the provider in docs/providers/xai.mdx (reasoning mapping table, prompt-cache affinity via X-Grok-Conv-Id / prompt_cache_key), bump the overview example, and apply a go fix modernization in keyring_test.go to keep the fix-check guard green. Verified end-to-end against the live xAI API: chat, vision, streamed multimodal tool calls, tool round-trip, native and streamed /responses, bare-name routing, per-model cost accounting, and prompt-cache affinity (derived conv-id stable across turns, upstream cache hits confirmed). Co-Authored-By: Claude Fable 5 <[email protected]>
📝 WalkthroughWalkthroughThe xAI provider now maps reasoning effort into xAI-compatible requests, documents Chat Completions and Responses API behavior, exposes grok-4.5 in model fixtures, and adds the provider guide to documentation navigation. ChangesxAI provider support
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant ChatRequest
participant xAIAdapter
participant ReasoningAdapter
participant xAIAPI
ChatRequest->>xAIAdapter: submit Reasoning.Effort
xAIAdapter->>ReasoningAdapter: normalize effort
ReasoningAdapter->>xAIAPI: send reasoning_effort
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/providers/xai.mdx`:
- Line 35: Update the Grok reasoning models documentation to remove the phrase
“defaulting to high” and state that xAI uses the model’s own default when
reasoning_effort is omitted.
In `@internal/providers/xai/xai_test.go`:
- Around line 956-990: Add a companion test alongside
TestChatCompletion_OmitsReasoningEffortWhenReasoningAbsent that sends a non-nil
core.Reasoning with Effort set to blank or whitespace, then verifies the
translated request omits both reasoning_effort and reasoning. Keep the same mock
server and successful ChatCompletion assertions, explicitly covering the
adaptChatRequest branch for strings.TrimSpace(req.Reasoning.Effort) == "".
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 754fd622-856c-4a2a-913d-44e1fac83686
📒 Files selected for processing (8)
docs/docs.jsondocs/providers/overview.mdxdocs/providers/xai.mdxinternal/providers/keyring_test.gointernal/providers/xai/xai.gointernal/providers/xai/xai_test.gotests/contract/testdata/golden/xai/models.golden.jsontests/contract/testdata/xai/models.json
|
|
||
| ## Reasoning effort mapping | ||
|
|
||
| Grok reasoning models (e.g. `grok-4.5`, defaulting to `high`) accept |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Does xAI's Grok-4.5 Chat Completions API default to "high" reasoning_effort when the field is omitted?
💡 Result:
No, xAI's Grok-4.5 Chat Completions API does not default to high reasoning_effort when the field is omitted [1][2]. When the reasoning_effort parameter is not specified, the model uses its default internal behavior [2][3]. For reasoning-capable models like Grok-4.5, specific SDKs and integration libraries (such as Vercel AI SDK or Pydantic AI) often map default settings to 'low' reasoning effort [4][3], but at the API level, omitting the field relies on the model's native default rather than forcing it to 'high' [2][3]. The reasoning_effort parameter is intended to be used explicitly to control the model's "thinking" process, with supported values typically including 'low', 'medium', and 'high' (depending on the specific model variant) [4][5][6].
Citations:
- 1: https://docs.x.ai/developers/model-capabilities/text/reasoning
- 2: https://pydantic.dev/docs/ai/advanced-features/thinking/
- 3: https://github.com/pydantic/pydantic-ai/blob/7f5214c6/docs/thinking.md
- 4: https://ai-sdk.dev/providers/ai-sdk-providers/xai
- 5: https://docs.rs/grok_api/latest/grok_api/struct.ChatRequest.html
- 6: https://docs.rs/grok_api/latest/grok_api/enum.Model.html
🏁 Script executed:
sed -n '25,45p' docs/providers/xai.mdx | cat -nRepository: ENTERPILOT/GoModel
Length of output: 1167
**Remove the defaulting to \high`wording.** xAI uses the model’s own default whenreasoning_effortis omitted; this should not say it defaults tohigh`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/providers/xai.mdx` at line 35, Update the Grok reasoning models
documentation to remove the phrase “defaulting to high” and state that xAI uses
the model’s own default when reasoning_effort is omitted.
| func TestChatCompletion_OmitsReasoningEffortWhenReasoningAbsent(t *testing.T) { | ||
| var gotBody map[string]any | ||
|
|
||
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { | ||
| http.Error(w, "decode error", http.StatusBadRequest) | ||
| return | ||
| } | ||
| w.Header().Set("Content-Type", "application/json") | ||
| _, _ = w.Write([]byte(`{ | ||
| "id":"chatcmpl-xai", | ||
| "created":1677652288, | ||
| "model":"grok-4.5", | ||
| "choices":[{"index":0,"message":{"role":"assistant","content":"hello"},"finish_reason":"stop"}] | ||
| }`)) | ||
| })) | ||
| defer server.Close() | ||
|
|
||
| provider := NewWithHTTPClient("test-api-key", nil, llmclient.Hooks{}) | ||
| provider.SetBaseURL(server.URL) | ||
|
|
||
| _, err := provider.ChatCompletion(context.Background(), &core.ChatRequest{ | ||
| Model: "grok-4.5", | ||
| Messages: []core.Message{{Role: "user", Content: "hi"}}, | ||
| }) | ||
| if err != nil { | ||
| t.Fatalf("ChatCompletion() error = %v", err) | ||
| } | ||
| if _, ok := gotBody["reasoning_effort"]; ok { | ||
| t.Fatalf("reasoning_effort should be absent, got %#v", gotBody["reasoning_effort"]) | ||
| } | ||
| if _, ok := gotBody["reasoning"]; ok { | ||
| t.Fatalf("reasoning should be absent, got %#v", gotBody["reasoning"]) | ||
| } | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Add a test for Reasoning present with blank effort.
adaptChatRequest has a distinct branch for strings.TrimSpace(req.Reasoning.Effort) == "" that is not covered by existing tests. TestChatCompletion_OmitsReasoningEffortWhenReasoningAbsent tests req.Reasoning == nil, not req.Reasoning != nil with blank effort. If the blank-effort guard is removed, normalizeReasoningEffort returns "" and AdaptReasoningEffortRequest would inject "reasoning_effort": "" — a regression no current test catches.
As per coding guidelines, tests should cover request translation and default configuration.
♻️ Suggested companion test
func TestChatCompletion_OmitsReasoningEffortWhenReasoningAbsent(t *testing.T) {
+
+func TestChatCompletion_OmitsReasoningEffortWhenEffortBlank(t *testing.T) {
+ var gotBody map[string]any
+
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
+ http.Error(w, "decode error", http.StatusBadRequest)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ _, _ = w.Write([]byte(`{
+ "id":"chatcmpl-xai",
+ "created":1677652288,
+ "model":"grok-4.5",
+ "choices":[{"index":0,"message":{"role":"assistant","content":"hello"},"finish_reason":"stop"}]
+ }`))
+ }))
+ defer server.Close()
+
+ provider := NewWithHTTPClient("test-api-key", nil, llmclient.Hooks{})
+ provider.SetBaseURL(server.URL)
+
+ _, err := provider.ChatCompletion(context.Background(), &core.ChatRequest{
+ Model: "grok-4.5",
+ Messages: []core.Message{{Role: "user", Content: "hi"}},
+ Reasoning: &core.Reasoning{Effort: " "},
+ })
+ if err != nil {
+ t.Fatalf("ChatCompletion() error = %v", err)
+ }
+ if _, ok := gotBody["reasoning_effort"]; ok {
+ t.Fatalf("reasoning_effort should be absent for blank effort, got %#v", gotBody["reasoning_effort"])
+ }
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func TestChatCompletion_OmitsReasoningEffortWhenReasoningAbsent(t *testing.T) { | |
| var gotBody map[string]any | |
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { | |
| http.Error(w, "decode error", http.StatusBadRequest) | |
| return | |
| } | |
| w.Header().Set("Content-Type", "application/json") | |
| _, _ = w.Write([]byte(`{ | |
| "id":"chatcmpl-xai", | |
| "created":1677652288, | |
| "model":"grok-4.5", | |
| "choices":[{"index":0,"message":{"role":"assistant","content":"hello"},"finish_reason":"stop"}] | |
| }`)) | |
| })) | |
| defer server.Close() | |
| provider := NewWithHTTPClient("test-api-key", nil, llmclient.Hooks{}) | |
| provider.SetBaseURL(server.URL) | |
| _, err := provider.ChatCompletion(context.Background(), &core.ChatRequest{ | |
| Model: "grok-4.5", | |
| Messages: []core.Message{{Role: "user", Content: "hi"}}, | |
| }) | |
| if err != nil { | |
| t.Fatalf("ChatCompletion() error = %v", err) | |
| } | |
| if _, ok := gotBody["reasoning_effort"]; ok { | |
| t.Fatalf("reasoning_effort should be absent, got %#v", gotBody["reasoning_effort"]) | |
| } | |
| if _, ok := gotBody["reasoning"]; ok { | |
| t.Fatalf("reasoning should be absent, got %#v", gotBody["reasoning"]) | |
| } | |
| } | |
| func TestChatCompletion_OmitsReasoningEffortWhenReasoningAbsent(t *testing.T) { | |
| var gotBody map[string]any | |
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { | |
| http.Error(w, "decode error", http.StatusBadRequest) | |
| return | |
| } | |
| w.Header().Set("Content-Type", "application/json") | |
| _, _ = w.Write([]byte(`{ | |
| "id":"chatcmpl-xai", | |
| "created":1677652288, | |
| "model":"grok-4.5", | |
| "choices":[{"index":0,"message":{"role":"assistant","content":"hello"},"finish_reason":"stop"}] | |
| }`)) | |
| })) | |
| defer server.Close() | |
| provider := NewWithHTTPClient("test-api-key", nil, llmclient.Hooks{}) | |
| provider.SetBaseURL(server.URL) | |
| _, err := provider.ChatCompletion(context.Background(), &core.ChatRequest{ | |
| Model: "grok-4.5", | |
| Messages: []core.Message{{Role: "user", Content: "hi"}}, | |
| }) | |
| if err != nil { | |
| t.Fatalf("ChatCompletion() error = %v", err) | |
| } | |
| if _, ok := gotBody["reasoning_effort"]; ok { | |
| t.Fatalf("reasoning_effort should be absent, got %#v", gotBody["reasoning_effort"]) | |
| } | |
| if _, ok := gotBody["reasoning"]; ok { | |
| t.Fatalf("reasoning should be absent, got %#v", gotBody["reasoning"]) | |
| } | |
| } | |
| func TestChatCompletion_OmitsReasoningEffortWhenEffortBlank(t *testing.T) { | |
| var gotBody map[string]any | |
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { | |
| http.Error(w, "decode error", http.StatusBadRequest) | |
| return | |
| } | |
| w.Header().Set("Content-Type", "application/json") | |
| _, _ = w.Write([]byte(`{ | |
| "id":"chatcmpl-xai", | |
| "created":1677652288, | |
| "model":"grok-4.5", | |
| "choices":[{"index":0,"message":{"role":"assistant","content":"hello"},"finish_reason":"stop"}] | |
| }`)) | |
| })) | |
| defer server.Close() | |
| provider := NewWithHTTPClient("test-api-key", nil, llmclient.Hooks{}) | |
| provider.SetBaseURL(server.URL) | |
| _, err := provider.ChatCompletion(context.Background(), &core.ChatRequest{ | |
| Model: "grok-4.5", | |
| Messages: []core.Message{{Role: "user", Content: "hi"}}, | |
| Reasoning: &core.Reasoning{Effort: " "}, | |
| }) | |
| if err != nil { | |
| t.Fatalf("ChatCompletion() error = %v", err) | |
| } | |
| if _, ok := gotBody["reasoning_effort"]; ok { | |
| t.Fatalf("reasoning_effort should be absent for blank effort, got %#v", gotBody["reasoning_effort"]) | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/providers/xai/xai_test.go` around lines 956 - 990, Add a companion
test alongside TestChatCompletion_OmitsReasoningEffortWhenReasoningAbsent that
sends a non-nil core.Reasoning with Effort set to blank or whitespace, then
verifies the translated request omits both reasoning_effort and reasoning. Keep
the same mock server and successful ChatCompletion assertions, explicitly
covering the adaptChatRequest branch for strings.TrimSpace(req.Reasoning.Effort)
== "".
Source: Coding guidelines
Confidence Score: 5/5This looks safe to merge. No blocking issues found in the changed code. The xAI chat adapter follows the existing provider request-rewrite pattern. The test modernization matches the repository's declared Go toolchain. No files need attention.
What T-Rex did
Reviews (1): Last reviewed commit: "feat(xai): support grok-4.5 reasoning ef..." | Re-trigger Greptile |
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
Summary
Adds first-class support for xAI's new
grok-4.5model (announcement). The model itself is auto-discovered from xAI's/modelsendpoint; the one gap was reasoning control:AdaptChatRequesthook for the xai provider (same pattern as deepseek/gemini): rewrites GoModel's normalized"reasoning": {"effort": "..."}into the flatreasoning_effortstring xAI documents for Chat Completions (grok-4.5:low/medium/high, defaulthigh).xhigh/maxdowngrade tohigh, except on the multi-agent Grok family wherexhighis native (it selects the agent count). The Responses API is untouched — the nested shape is xAI-native there.grok-4.5added to the xai models replay fixture + regenerated golden.docs/providers/xai.mdx(config, reasoning mapping table, prompt-cache affinity viaX-Grok-Conv-Id/prompt_cache_key), registered in navigation; overview example bumped togrok-4.5.go fixmodernization inkeyring_test.goto keep the pre-commit fix-check guard green under the current toolchain.User-visible impact
Clients using the OpenAI-style nested
reasoning.effortnow get it applied on xAI chat completions in the documented shape — no client changes needed. Requests withoutreasoningare unaffected.Provider-specific behavior
Verified against the live xAI API: reasoning token counts scale with the mapped effort level (flat-low 774 vs flat-high 1526 on the same prompt);
xhighis accepted upstream on grok-4.5 but appears clamped, so the gateway downgrades it per the documented ladder.Testing
reasoningis absent, effort normalization table.-tags=contract) green;go build ./...,go vetclean./v1/responses(nested reasoning,prompt_cache_key,json_object), bare-name routing, per-model cost accounting, and prompt-cache affinity (derived conv-id stable across turns; upstream reported 2176/2289 cached tokens on a growing conversation).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation