Skip to content

fix(gemini): map thinkingLevel to thinkingBudget for Gemini 2.5 models#3005

Open
maxronner wants to merge 3 commits intoolimorris:mainfrom
maxronner:fix/gemini-thinking-config
Open

fix(gemini): map thinkingLevel to thinkingBudget for Gemini 2.5 models#3005
maxronner wants to merge 3 commits intoolimorris:mainfrom
maxronner:fix/gemini-thinking-config

Conversation

@maxronner
Copy link
Copy Markdown

Description

This fixes Gemini reasoning config serialization for gemini-2.5-* models.

Previously, the adapter mapped thinkingLevel directly into generationConfig.thinkingConfig, which caused Gemini 2.5 requests to fail because those models expect thinkingConfig to contain a numeric thinkingBudget. This change moves thinkingLevel handling into form_parameters, where the adapter now builds the correct thinkingConfig shape for Gemini 2.5 models while preserving the existing { thinkingLevel = ... } shape for other reasoning models, including Gemini 3.x.

A model-specific budget mapping is added for Gemini 2.5 Pro, Flash, and Flash-Lite. Serialization is also guarded so thinkingConfig is only added for reasoning models and only when thinkingLevel is present.

Test coverage was added for the Gemini 2.5 budget mappings, the existing Gemini 3.x thinkingLevel shape, non-reasoning models, preservation of existing generationConfig keys, and the absence of thinkingConfig when thinkingLevel is unset.

Note: for gemini-2.5-pro, none maps to 128, not 0, because the API rejects thinkingBudget = 0.

Docs were regenerated via make all per the project guidelines, otherwise untouched.

References

https://ai.google.dev/gemini-api/docs/thinking

AI Usage

I used claude-opus-4-6 to cross-check this change against the request-shaping patterns used in lua/codecompanion/adapters/http/anthropic.lua and to draft the test cases. I reviewed and revised those tests before including them. I also used gpt-5-4 for wording and review feedback, but the implementation and final changes are mine.

Related Issue(s)

#2994

Screenshots

N/A

Checklist

  • I've read the contributing guidelines and have adhered to them in this PR
  • I confirm that this PR has been majority created by me, and not AI (unless stated in the "AI Usage" section above)
  • I've run make all to ensure docs are generated, tests pass and StyLua has formatted the code
  • (optional) I've added test coverage for this fix/feature
  • (optional) I've updated the README and/or relevant docs pages

Gemini 2.5 models expect `generationConfig.thinkingConfig` to be an
object with a `thinkingBudget` field, not a raw level string. Stage
`thinkingLevel` in `adapter.temp` and serialize it in `form_parameters`,
emitting `{ thinkingBudget = N }` for models tagged with a budget tier
via `opts.thinking_budget`. Other reasoning models keep the existing
`{ thinkingLevel = "<level>" }` shape.

On Gemini 2.5 Pro, `none` maps to the minimum valid budget (128) because
the API rejects `thinkingBudget = 0` for that model. Flash and Flash-Lite
accept 0 and map accordingly.
flash_lite = { none = 0, low = 512, medium = 8192, high = 24576 },
}

local function resolve_thinking_config(opts, level)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Can we add some type annotations here? And it's common practice in the plugin to have level, opts as the parameter order

---`opts.thinking_budget`, which selects a row from `THINKING_LEVEL_BUDGETS`.
---Other reasoning models continue to use the `{ thinkingLevel = "<level>" }` shape.
---
---Gemini 2.5 Pro cannot fully disable thinking: the API rejects thinkingBudget = 0,
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This is excessive commenting. A single line explaining what they do and a link to Google's own docs will suffice here

@olimorris
Copy link
Copy Markdown
Owner

2026-04-09 08_00_07 - Google Chrome

I've just seen that these models are being shutdown in 2-3 months time

@maxronner
Copy link
Copy Markdown
Author

maxronner commented Apr 9, 2026

Happy to fix the remaining blockers if you still want to take this for the 2.5 models.

I can see both sides, though. With Gemini 2.5 this close to EOL, adding model-specific handling here probably only makes sense if we’re okay with a bit of temporary special-casing. If that’s the preference, I think it’d be reasonable to land this with a follow-up note to remove the internal mapping once 2.5 support is gone.

@olimorris
Copy link
Copy Markdown
Owner

I'll give this a test later on tonight and merge. I think there's still value in having this in the adapter.

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