Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat(xai): support grok-4.5 reasoning effort mapping#519

Merged
SantiagoDePolonia merged 1 commit into
mainfrom
feat/xai
Jul 10, 2026
Merged

feat(xai): support grok-4.5 reasoning effort mapping#519
SantiagoDePolonia merged 1 commit into
mainfrom
feat/xai

Conversation

@SantiagoDePolonia

@SantiagoDePolonia SantiagoDePolonia commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds first-class support for xAI's new grok-4.5 model (announcement). The model itself is auto-discovered from xAI's /models endpoint; the one gap was reasoning control:

  • AdaptChatRequest hook for the xai provider (same pattern as deepseek/gemini): rewrites GoModel's normalized "reasoning": {"effort": "..."} into the flat reasoning_effort string xAI documents for Chat Completions (grok-4.5: low/medium/high, default high). xhigh/max downgrade to high, except on the multi-agent Grok family where xhigh is native (it selects the agent count). The Responses API is untouched — the nested shape is xAI-native there.
  • Contract fixture: grok-4.5 added to the xai models replay fixture + regenerated golden.
  • Docs: new docs/providers/xai.mdx (config, reasoning mapping table, prompt-cache affinity via X-Grok-Conv-Id / prompt_cache_key), registered in navigation; overview example bumped to grok-4.5.
  • Includes one mechanical go fix modernization in keyring_test.go to keep the pre-commit fix-check guard green under the current toolchain.

User-visible impact

Clients using the OpenAI-style nested reasoning.effort now get it applied on xAI chat completions in the documented shape — no client changes needed. Requests without reasoning are 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); xhigh is accepted upstream on grok-4.5 but appears clamped, so the gateway downgrades it per the documented ladder.

Testing

  • Unit tests: nested→flat reshaping, omission when reasoning is absent, effort normalization table.
  • Contract suite (-tags=contract) green; go build ./..., go vet clean.
  • Live E2E against api.x.ai through the gateway: chat, vision, streamed multimodal + tool calls, tool round-trip, native + streamed /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

    • Added support for the xAI Grok 4.5 model.
    • Improved compatibility with xAI reasoning settings, including normalized effort levels.
    • Added support for xAI provider configuration, voice models, prompt caching, and API usage guidance.
  • Documentation

    • Added a dedicated xAI (Grok) provider guide.
    • Updated the provider directory with Grok 4.5 details and a link to its setup guide.

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]>
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The 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.

Changes

xAI provider support

Layer / File(s) Summary
Reasoning effort request adaptation
internal/providers/xai/xai.go, internal/providers/xai/xai_test.go
xAI requests normalize reasoning effort values into the top-level reasoning_effort field, with tests covering mapped and omitted values.
Model metadata and provider documentation
tests/contract/testdata/xai/models.json, tests/contract/testdata/golden/xai/models.golden.json, docs/providers/xai.mdx, docs/docs.json, docs/providers/overview.mdx
grok-4.5 is added to model fixtures, and xAI configuration, API behavior, prompt-cache handling, and navigation entries are documented.
Keyring concurrency test update
internal/providers/keyring_test.go
The concurrency test uses WaitGroup.Go while preserving its existing counting assertions.

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
Loading

Possibly related PRs

  • ENTERPILOT/GoModel#486: Refactors the xAI provider around the compatible provider and introduces the request adaptation mechanism used here.

Poem

A bunny hops through xAI’s guide,
With grok-4.5 tucked inside.
Reasoning blooms, neatly aligned,
Cache clues and headers are defined.
“Hop hooray!” the keyring sings—
Clean requests grow useful wings.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main xAI change: adding grok-4.5 reasoning effort mapping.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/xai

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 24b9c17 and 5f72d52.

📒 Files selected for processing (8)
  • docs/docs.json
  • docs/providers/overview.mdx
  • docs/providers/xai.mdx
  • internal/providers/keyring_test.go
  • internal/providers/xai/xai.go
  • internal/providers/xai/xai_test.go
  • tests/contract/testdata/golden/xai/models.golden.json
  • tests/contract/testdata/xai/models.json

Comment thread docs/providers/xai.mdx

## Reasoning effort mapping

Grok reasoning models (e.g. `grok-4.5`, defaulting to `high`) accept

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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:


🏁 Script executed:

sed -n '25,45p' docs/providers/xai.mdx | cat -n

Repository: 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.

Comment on lines +956 to +990
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"])
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Suggested change
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

@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

This 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.

T-Rex T-Rex Logs

What T-Rex did

  • Ran unit tests for the XAI reasoning suite, executing TestChatCompletion_MapsReasoningToXAIReasoningEffort, TestChatCompletion_OmitsReasoningEffortWhenReasoningAbsent, and TestNormalizeReasoningEffort; all tests passed with exit code 0 and results captured in the xai-reasoning-unit.log.
  • Ran contract tests for the XAI replay models, executing TestXAIReplayListModels; the contract test passed with exit code 0 and results captured in the xai-contract-models.log.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "feat(xai): support grok-4.5 reasoning ef..." | Re-trigger Greptile

@mintlify

mintlify Bot commented Jul 10, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
gomodel 🟢 Ready View Preview Jul 10, 2026, 4:29 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@SantiagoDePolonia SantiagoDePolonia merged commit 44f0b70 into main Jul 10, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants