fix(transport): omit thinking_config for Gemma on the gemini provider (#17426)#17441
fix(transport): omit thinking_config for Gemma on the gemini provider (#17426)#17441briandevans wants to merge 1 commit into
Conversation
…NousResearch#17426) The `gemini` provider also serves Gemma (e.g. `gemma-4-31b-it`) and historically other Google models like PaLM. Those reject `extra_body.thinking_config` with HTTP 400: Unknown name "thinking_config": Cannot find field `_build_gemini_thinking_config()` was unconditionally producing a config dict for any model on the `gemini` / `google-gemini-cli` provider, which `ChatCompletionsTransport.build_kwargs` then dropped into `extra_body["thinking_config"]`. The result: every chat turn for Gemma users on the gemini provider blew up at the API edge. The fix is the same shape Hermes already uses for the Gemini-2.5 vs Gemini-3 family clamping: normalise the model id, strip an `OpenRouter`-style `google/` prefix, and short-circuit early when the result doesn't start with `gemini`. We return `None` rather than `{"includeThoughts": False}`, because the API rejects the field name itself — even the polite "off" form trips the same 400. Three regression tests cover Gemma with reasoning enabled, Gemma with reasoning disabled, and the `google/gemma-…` OpenRouter-style id; the existing Gemini-2.5 / Gemini-3 / `google/gemini-…` cases keep passing because the Gemini guard fires after the prefix strip. Fixes NousResearch#17426 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Fixes gemini-provider chat requests failing for non-Gemini Google models (notably Gemma) by ensuring extra_body.thinking_config is only sent for Gemini-family model IDs.
Changes:
- Add model-family detection to
_build_gemini_thinking_config()(stripgoogle/prefix, then omitthinking_configunless the model starts withgemini). - Add regression tests asserting
thinking_configis omitted forgemma-4-31b-it(includinggoogle/-prefixed IDs), regardless of reasoning enabled/disabled.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
agent/transports/chat_completions.py |
Prevents thinking_config from being emitted for non-Gemini models routed through the gemini provider. |
tests/agent/transports/test_chat_completions.py |
Adds focused regression coverage to ensure Gemma requests never include thinking_config. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
CI audit — all 32 Touched-code regression check: focused regression test passes — Spot-checked baselines (each reproduced on clean
The remaining failures (test_copilot_acp_client redact, test_run_progress_topics, test_config_env_expansion, test_container_aware_cli, test_plugin_scanner_recursion, test_provider_config_validation, test_pty_bridge, test_web_server, test_background_review_toolset_restriction, test_session_split_brain_11016, test_gateway_shutdown, test_accretion_caps, test_tui_gateway_server, test_browser_orphan_reaper, test_docker_environment, test_mcp_dynamic_discovery, test_protocol) are the same baseline set visible on neighboring open PRs (#17569, #17386, #17348). None touch the Gemini thinking-config code path. |
Summary
extra_body.thinking_configfor every chat call routed through thegeminiprovider — including Gemma (gemma-4-31b-it), PaLM, and any other non-Gemini Google model on the same endpointHTTP 400 — Unknown name \"thinking_config\": Cannot find fieldgeminiprovider exit immediately on every chat ([Bug]: Model calls to google provider return 400 error with Invalid JSON payload on thinking_config #17426)The bug
chat_completions.py:365-368 builds
thinking_configwheneverprovider_name in {\"gemini\", \"google-gemini-cli\"}:```python
if provider_name in {"gemini", "google-gemini-cli"}:
thinking_config = _build_gemini_thinking_config(model, reasoning_config)
if thinking_config:
extra_body["thinking_config"] = thinking_config
```
The helper unconditionally returned a dict — never
None— for any model under those providers. Soextra_body[\"thinking_config\"]was always set, including for Gemma. The Gemini API's strict request validator rejects unknown fields, even the polite{\"includeThoughts\": False}shape.The reporter's payload:
```
provider: gemini
model: gemma-4-31b-it
→ HTTP 400: 'Invalid JSON payload received. Unknown name "thinking_config": Cannot find field.'
```
The fix
_build_gemini_thinking_config()now mirrors the same family-detection pattern it already uses for Gemini-2.5 vs Gemini-3 clamping: normalise the model id, strip an OpenRouter-stylegoogle/prefix, and short-circuit toNonewhen the result doesn't start withgemini.We return
Nonerather than{\"includeThoughts\": False}because the API rejects the field name itself — there is no "off" shape that's safe.The Gemini guard fires after the
google/strip, so:gemini-2.5-flash→ still getsthinking_configgemini-3-flash-preview→ still getsthinking_configgoogle/gemini-3.1-pro-preview→ still getsthinking_config(existing behaviour preserved)gemma-4-31b-it→ nothinking_configgoogle/gemma-4-31b-it→ nothinking_configTest plan
tests/agent/transports/test_chat_completions.py:test_gemma_does_not_receive_thinking_config— reasoning enabled, high effort → field absenttest_gemma_disabled_reasoning_still_omits_thinking_config—enabled: False→ field absent (proves the omit-not-disable contract)test_google_prefixed_gemma_also_omits_thinking_config— OpenRouter-style id → field absentchat_completions.pychange makes both Gemma tests fail at `assert "thinking_config" not in kw.get("extra_body", {})` (the field is present); restoring the fix flips them back to passingContract Protected
gemini-2.5-flashgemini-3-flash-previewthinkingLevel)gemini-3-flash-previewincludeThoughts: False)google/gemini-3.1-pro-previewgemma-4-31b-itgemma-4-31b-itgoogle/gemma-4-31b-itFixes #17426