# NousResearch/hermes-agent · Gemini thinking_config malformed request causing 400 errors

## Open now

- 1 issue, 2 PRs open on GitHub.
- #25123: fix: Fallback to gemini-3-flash-preview fails with 400 (thinking_config)
- PR #25124: fix: exclude gemini-3-flash-preview from thinking_config injection
- PR #59504: fix(agent): merge extra_body into request debug dumps so they match t…

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

## TL;DR

- 11 member(s): 1 open issue(s), 3 closed issue(s), 2 open PR(s), 1 merged PR(s), 4 PR(s) closed without merging.
- A fix was merged to main in PR #17904. Verify it addresses your case.
- **Recommended:** read PR #17904 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-17904`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): 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 member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Open related issues

- #25123: fix: Fallback to gemini-3-flash-preview fails with 400 (thinking_config)

## Closed / duplicate issues

- #17426: \[Bug\]: Model calls to google provider return 400 error with Invalid JSON payload on thinking_config
- #26161: \[Bug\]: Gemini API Failed with 400 Bad Request
- #59283: Gemini OpenAI-compat: extra_body.google.thinking_config double-nested (400, surfaced as generic empty response)

## Prior & competing PRs

- **#17904: fix(transport): omit thinking_config for Gemma on the gemini provider (#17426)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `agent/transports/chat_completions.py`, `tests/agent/transports/test_chat_completions.py`
  - fix hunks:
  - `agent/transports/chat_completions.py`

    ```diff
    @@ -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.
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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(
    ```
- **#17441: fix(transport): omit thinking_config for Gemma on the gemini provider (#17426)**. closed without merge
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `990eccdf6`, `5a61c116e`, `5a61c116e`, `#17334`, `#17334`, `#17334`, `#17334`, `#17569`, `#17386`, `#17348`
- **#25124: fix: exclude gemini-3-flash-preview from thinking_config injection**. open
  - maintainer direction: category `wontfix_bydesign`; implication `wontfix`; refs `#25123`, `#25123`
  - files: `agent/transports/chat_completions.py`
- **#32353: fix(agent): exclude gemini-3-flash-preview from thinkingLevel injection (#25123)**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`
- **#59502: fix(gemini): remove double-nested extra_body in thinking_config builder**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`
- **#59504: fix(agent): merge extra_body into request debug dumps so they match t…**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - files: `agent/agent_runtime_helpers.py`, `tests/run_agent/test_run_agent_codex_responses.py`
- **#59699: fix(gemini): fix double-nested thinking_config in extra_body (#59283)**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#59502`, `#59283`, `#59502`, `#59475`, `#59283`

## Fix status

- Merged to main in PR #17904 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-04-30`.
- Subsystem: `agent`
