Gemini thinking_config malformed request causing 400 errors
Cluster 17426 · agent. Data as of
3 open items. Fix merged in #17904. Check it covers your case.
The short answer
- Known?
- Yes. 11 related items grouped here, 3 issues already closed.
- Fix event
- A fix was merged to main in PR #17904. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PR #17904. That is separate from the fix event. - Still open
- 1 issue and 2 PRs.
Next step: read #17904, then check whether your report is one of the 1 still open.
Read #17904Pick up a Lab taskMultiple issues and fixes related to incorrectly structured or inappropriately injected thinking_config in Gemini API requests, resulting in 400 Bad Request errors. Specific cases include double-nesting in extra_body, injection for unsupported models like Gemma and gemini-3-flash-preview, and OpenAI-compat path mismatches.
Open issues1
- Issue#25123fix: Fallback to gemini-3-flash-preview fails with 400 (thinking_config)
Open PRs2
Fixes on record
-
#17904 fix(transport): omit thinking_config for Gemma on the gemini provider (#17426)
files:
agent/transports/chat_completions.py,tests/agent/transports/test_chat_completions.pyShow the diff
-
agent/transports/chat_completions.py@@ -24,6 +24,18 @@ def _build_gemini_thinking_config(model: str, reasoning_config: dict | None) -> if reasoning_config is None or not isinstance(reasoning_config, dict): return None + normalized_model = (model or "").strip().lower() + if normalized_model.startswith("google/"): + normalized_model = normalized_model.split("/", 1)[1] + + # ``thinking_config`` is a Gemini-only request parameter. The same + # ``gemini`` provider also serves Gemma (and historically PaLM/Bard); + # those reject the field with HTTP 400 "Unknown name 'thinking_config': + # Cannot find field" — including the polite ``{"includeThoughts": False}`` + # form. Omit the field entirely on non-Gemini models. (#17426) + if not normalized_model.startswith("gemini"): + return None + if reasoning_config.get("enabled") is False: # Gemini can hide thought parts even when internal thinking still # happens; omit thinkingLevel to avoid model-specific validation quirks. @@ -34,9 +46,6 @@ def _build_gemini_thinking_config(model: str, reasoning_config: dict | None) -> return {"includeThoughts": False} thinking_config: Dict[str, Any] = {"includeThoughts": True} - normalized_model = (model or "").strip().lower() - if normalized_model.startswith("google/"): - normalized_model = normalized_model.split("/", 1)[1] # Gemini 2.5 accepts thinkingBudget; don't guess a budget from Hermes' # coarse effort levels. ``includeThoughts`` alone is enough to surface -
tests/agent/transports/test_chat_completions.py@@ -244,6 +244,46 @@ def test_gemini_flash_minimal_clamps_to_low(self, transport): "thinking_level": "low", } + def test_gemma_does_not_receive_thinking_config(self, transport): + # The `gemini` provider also serves Gemma (e.g. `gemma-4-31b-it`), + # but Gemma rejects `thinking_config` with HTTP 400 (#17426). Even + # when Hermes has reasoning enabled, the field must be omitted for + # non-Gemini models on this provider. + msgs = [{"role": "user", "content": "Hi"}] + kw = transport.build_kwargs( + model="gemma-4-31b-it", + messages=msgs, + provider_name="gemini", + reasoning_config={"enabled": True, "effort": "high"}, + ) + assert "thinking_config" not in kw.get("extra_body", {}) + + def test_gemma_disabled_reasoning_still_omits_thinking_config(self, transport): + # The `Unknown name 'thinking_config': Cannot find field` rejection + # fires even on `{"includeThoughts": False}` — the entire field must + # be absent, not just disabled. (#17426) + msgs = [{"role": "user", "content": "Hi"}] + kw = transport.build_kwargs( + model="gemma-4-31b-it", + messages=msgs, + provider_name="gemini", + reasoning_config={"enabled": False}, + ) + assert "thinking_config" not in kw.get("extra_body", {}) + + def test_google_prefixed_gemma_also_omits_thinking_config(self, transport): + # OpenRouter-style `google/gemma-...` IDs hit the same provider path + # and must also omit `thinking_config`. The existing `google/` + # prefix-stripping must not accidentally classify Gemma as Gemini. + msgs = [{"role": "user", "content": "Hi"}] + kw = transport.build_kwargs( + model="google/gemma-4-31b-it", + messages=msgs, + provider_name="gemini", + reasoning_config={"enabled": True, "effort": "medium"}, + ) + assert "thinking_config" not in kw.get("extra_body", {}) + def test_max_tokens_with_fn(self, transport): msgs = [{"role": "user", "content": "Hi"}] kw = transport.build_kwargs(
-
4 PRs closed without merging
From the close comments: done elsewhere 1 · duplicate 1 · superseded by another PR 1 · other 1
-
#17441 fix(transport): omit thinking_config for Gemma on the gemini provider (#17426) · other →
990eccd,5a61c11,#17334,#17569,#17386,#17348 - #32353 fix(agent): exclude gemini-3-flash-preview from thinkingLevel injection (#25123) · done elsewhere
- #59502 fix(gemini): remove double-nested extra_body in thinking_config builder · superseded by another PR
-
#59699 fix(gemini): fix double-nested thinking_config in extra_body (#59283) · duplicate →
#59502,#59283,#59475
Closed issues · 3
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.15: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- agent
- Merged PRs
- #17904
- Closed issues
- 3
- Attempts
- 4 PRs closed without merging
- Fix link checked
- 30 Apr 2026