Add `'max'` to unified `thinking` effort levels
This pull request was posted by Claude Code using claude-opus-5-5 on behalf of David.
Closes #9993
Unified thinking stopped at 'xhigh', so the highest effort OpenAI, Anthropic and others offer was only reachable through provider-specific settings. 'max' is now a unified level. It is sent as the native max where the model accepts it on the API the request goes to, and falls back to the highest level the model accepts everywhere else, so the request still succeeds.
[!WARNING]
Compatibility impact: ThinkingEffort gains 'max', so code that receives a thinking level, such as a custom Model reading ModelRequestParameters.thinking or a FunctionModel function, can now be handed 'max'. An exhaustive match ending in assert_never fails type checking, and a dict keyed by level raises KeyError when a run sets 'max'.
Why this can ship in a minor release: existing settings send exactly what they sent before; 'max' only reaches that code when a user opts into it.
Migration: handle 'max' wherever you map levels, for example treating it like 'xhigh'.
New public surface: 'max' in ThinkingEffort; profile flags openai_responses_supports_max_reasoning_effort, openai_chat_supports_max_reasoning_effort and anthropic_supports_max_effort; resolve_anthropic_effort(..., supports_max=True).
User-visible behavior: one setting now requests the top effort on every provider.
-Agent('openai-responses:gpt-6.1-sol', model_settings={'openai_reasoning_effort': 'max'})
-Agent('anthropic:claude-opus-5-5', model_settings={'anthropic_effort': 'max'})
+Agent('openai-responses:gpt-6.1-sol', model_settings={'thinking': 'max'})
+Agent('anthropic:claude-opus-5-5', model_settings={'thinking': 'max'})
| Route | thinking='max' sends |
|---|---|
| OpenAI Responses, GPT-5.6 and GPT-6 | max |
| OpenAI Chat Completions, older OpenAI models, OpenAI-compatible endpoints (Azure, LiteLLM, Vercel, Bedrock Mantle, GitHub Copilot), Bedrock GPT-5.6 and GPT-6, Codex, ChatGPT plan usage, realtime, GPT-Live, Meta Muse | xhigh |
| DeepSeek V4, Kimi K3/K2.6/K2.7 (Chat Completions) | max |
| Anthropic and Bedrock Claude with an effort setting | max (Claude Opus 4.5: high) |
| OpenRouter | max (OpenRouter lowers it per model) |
| Z.AI GLM-5.2/5.3 | max |
| Gemini, xAI, Groq, Mistral, Snowflake, Bedrock Qwen, Bedrock xAI Grok | their highest level |
Verification: live cassettes in test_max_effort_wire and test_bedrock_anthropic_unified_max_effort (the Opus 4.5 case fails on main); test_every_effort_level_resolves runs every level through every provider mapping; test_openai_compatible_endpoints_fall_back_from_max_effort pins the xhigh fallback; test_anthropic_modes_and_effort covers the CLAI 2 menu.
Left for follow-ups: unified xhigh 400s on gpt-5/o3/o4-mini, OpenRouter xhigh → high, the deepseek-flash id, Bedrock gpt-oss xhigh, and CLAI 2's thinking field and openai-chat effort menu.
What changes for existing users: unified thinking='xhigh' on Claude Opus 4.5, which accepts only low/medium/high, now sends high instead of a max that got a 400, and CLAI 2's /effort menu now offers max on Claude Sonnet 4.6, 5 and 5.5.
PR decisions
2026-10-21 · #9993 scope settled · iter -
- Decision: Ship as compatibility impact in a minor; OpenAI max defaults to xhigh with per-API upgrade flags; fold Opus 4.5 xhigh->max 400; split OpenAI old-model xhigh 400, OpenRouter, deepseek-flash, Bedrock gpt-oss; Meta Muse falls back to xhigh; CLAI 2 menus fold only if flag-driven
- Why: Maintainer decision on compatibility and the OpenAI flags; Opus 4.5 folds because it is the same function and changes only a failing request; Meta Muse has no key to verify
maxlive - Source: https://github.com/pydantic/pydantic-ai/issues/9993
2026-10-21 · compat: widen ThinkingEffort in a minor · iter -
- Decision: Ship as compatibility impact: allowlist waiver, label, [!WARNING] naming exhaustive matches and level-keyed dicts on ModelRequestParameters.thinking, migration, release note
- Why: version-policy.md has no listed exception; David accepted the impact
- Source: https://github.com/pydantic/pydantic-ai/issues/9993
2026-10-21 · OpenAI max mapping · iter -
- Decision: OPENAI_REASONING_EFFORT_MAP['max']='xhigh'; openai_responses_supports_max_reasoning_effort / openai_chat_supports_max_reasoning_effort upgrade to 'max' "only where a probe showed it works": Responses on GPT-5.6 and GPT-6; Chat in the DeepSeek V4 and Kimi providers
- Why: OpenAI Chat rejects max on every model; realtime SDK lacks max; unverified routes keep xhigh's wire value
- Source: https://github.com/pydantic/pydantic-ai/issues/9993
2026-10-21 · fold Opus 4.5 xhigh->max 400 · iter -
- Decision: resolve_anthropic_effort sends 'high' for xhigh/max on models without max (Opus 4.5); failing test + cassette first
- Why: Same function; only alters a request that already 400s
- Source: https://github.com/pydantic/pydantic-ai/issues/9993
2026-10-21 · split OpenAI old-model xhigh 400 · iter -
- Decision: gpt-5/o3/o4-mini/gpt-5.5-pro xhigh 400 not folded; repro in local-notes, reported at end
- Why: Different level, needs its own flag
- Source: https://github.com/pydantic/pydantic-ai/issues/9993
2026-10-21 · CLAI 2 effort menus · iter -
- Decision: Fold "only if the menus can read the new profile flags directly, without a mechanism of their own"; otherwise split
- Why: Keep CLAI 2 on profile facts, no parallel capability logic
- Source: https://github.com/pydantic/pydantic-ai/issues/9993
2026-10-21 · Meta Muse falls back to xhigh · iter -
- Decision: No max flag on muse_model_profile; correct docs/models/meta.md claim that unified thinking reaches 'max'
- Why: No Meta key to verify max live
- Source: https://github.com/pydantic/pydantic-ai/issues/9993
2026-10-21 · split OpenRouter xhigh, deepseek-flash, Bedrock gpt-oss · iter -
- Decision: Not folded; repro in local-notes, reported at end
- Why: Separate defects outside #9993
- Source: https://github.com/pydantic/pydantic-ai/issues/9993
2026-10-21 · GPT-Live backend max falls back to xhigh · iter -
- Decision: _backend_reasoning_effort passes supports_max=False, so unified max sends xhigh on the GPT-Live delegation
- Why: OpenAILiveResponsesDelegation.reasoning_effort stops at xhigh and max was not probed on Live; decision 2 allows max only where a probe showed it works
- Source: https://github.com/pydantic/pydantic-ai/issues/9993
2026-10-21 · CLAI 2 thinking field stays at xhigh; Anthropic menu folds, OpenAI menu splits · iter -
- Decision: ModelSettingsForm.thinking keeps its five levels; _anthropic_efforts reads anthropic_supports_max_effort; _openai_efforts openai-chat max bug split
- Why: Older CLAI builds raise in model_settings_from_json on a saved thinking='max' (no migration); the OpenAI menu's openai-chat path also serves OpenRouter/vLLM, so a flag read alone would drop max there, needing a mechanism of its own (decision 5)
- Source: https://github.com/pydantic/pydantic-ai/issues/9993
2026-10-21 · Bedrock Mantle max falls back to xhigh · iter -
- Decision: bedrock_mantle_model_profile overrides openai_responses_supports_max_reasoning_effort=False
- Why: Mantle returned 401 access_denied for gpt-5.6-luna and gpt-6.1-sol on this account, so max is unverified there; decision 2 allows max only where a probe showed it works
- Source: https://github.com/pydantic/pydantic-ai/issues/9993
2026-10-21 · Z.AI max covered by unit test, not cassette · iter -
- Decision: test_zai_reasoning_effort left unchanged; Z.AI max pass-through pinned in test_every_effort_level_resolves
- Why: Z.AI API returned 429 code 1113 'Insufficient balance' on every call, so no cassette could be recorded; GLM-5.3 already sends max for xhigh in the existing cassette
- Source: https://github.com/pydantic/pydantic-ai/issues/9993
2026-10-21 · pre-push review 1 of 3 at f8b1a7ef41a07f277089934e8763ff0ed53e5c42 · iter -
- Decision: dispatched (outcome follows)
- Why: tree 66d48a9c992562b973d3f8b3a45bfd913531342e; scope merge-base 9b83099d782bd33b362db139cdba494f6caaa039..f8b1a7ef4; policy-base d0056a598a4f; first review
- Source: https://github.com/pydantic/pydantic-ai/tree/thinking-max-effort
2026-10-21 · pre-push review 1 of 3 at f8b1a7ef41a07f277089934e8763ff0ed53e5c42 · iter -
- Decision: remediated: Responses max flag moved from openai_model_profile to OpenAIProvider.model_profile (Azure/LiteLLM/Vercel/Mantle fall back to xhigh), Mantle override removed, Azure case added; CLAI 2 thinking field kept as a split (already decided)
- Why: reviewed tree 66d48a9c992562b973d3f8b3a45bfd913531342e; scope 9b83099d..f8b1a7ef4; first launch failed on CLI arg parsing before any review ran
- Source: https://github.com/pydantic/pydantic-ai/tree/thinking-max-effort
2026-10-21 · pre-push review 2 of 3 at 92e4f21034699d37f6ed0fa57380278a2955e987 · iter -
- Decision: dispatched (outcome follows)
- Why: tree aa5285dca5dd2d60ea9d510ab47082d2fbf577d5; follow-up scope f8b1a7ef4..92e4f2103 plus prior findings; full branch diff as context
- Source: https://github.com/pydantic/pydantic-ai/tree/thinking-max-effort
2026-10-21 · pre-push review 2 of 3 at 92e4f21034699d37f6ed0fa57380278a2955e987 · iter -
- Decision: remediated: docstring reflow only; prior findings verified fixed
- Why: reviewed tree aa5285dca5dd2d60ea9d510ab47082d2fbf577d5; follow-up scope f8b1a7ef4..92e4f2103; delta to b487bb0c0817dce166afef56f4b550e7a726804e is docstring-only (exempt: comments or docstrings only, no docs page), chained from 92e4f2103
- Source: https://github.com/pydantic/pydantic-ai/tree/thinking-max-effort
2026-10-21 · exempt delta: API-compat waivers at 6f4888eb4ff5961686723895e07ac4572e03bffb · iter -
- Decision: pushed without a third review; chained from reviewed 92e4f2103 (plus exempt docstring reflow b487bb0c0)
- Why: Delta is only .github/api-compatibility-allowlist.json: five griffe fingerprints required by settled decision 1; it touches no runtime, public API, docs or tests, so no rubric rung (public API, behavior, docs, tests, code quality) can move; validated with check_api_compatibility.load_waivers
- Source: https://github.com/pydantic/pydantic-ai/pull/10920
2026-10-21 · API-compat gate fix split; waivers kept · iter -
- Decision: Keep the 5 waivers; a separate PR fixes check_api_compatibility.py (stderr not parsed, prefect griffe 1.15 shadows griffelib); quality checks stays red here until it merges
- Why: Waivers verified exact against griffe's stderr output; the gate on main reports zero findings for every PR
- Source: https://github.com/pydantic/pydantic-ai/pull/10920
2026-10-22 · pre-push review 3 of 3 at 535486a4520e378785d8fd437b6dba0b2142dce3 · iter -
- Decision: dispatched (outcome follows)
- Why: tree 0e607c9a66b8de897335db59420fc1e7afdbb4bf; follow-up scope 92e4f2103..535486a45: waiver commit, merge of main 5753715b5 with one conflict (tests/models/test_bedrock.py), upstream interaction; policy-base 7c4f93b50; full branch diff as context
- Source: https://github.com/pydantic/pydantic-ai/pull/10920
2026-10-22 · pre-push review 3 of 3 at 535486a4520e378785d8fd437b6dba0b2142dce3 · iter -
- Decision: remediated: Codex and ChatGPT plan usage profiles set openai_responses_supports_max_reasoning_effort=False (unified max falls back to xhigh); docs/models/github-copilot.md notes kimi-k3 max needs openai_reasoning_effort='max'
- Why: reviewed tree 0e607c9a66b8de897335db59420fc1e7afdbb4bf; follow-up scope 92e4f2103..535486a45 incl. merge of main 5753715b5 (conflict in tests/models/test_bedrock.py); budget exhausted, no fourth review
- Source: https://github.com/pydantic/pydantic-ai/pull/10920
2026-10-22 · Codex and ChatGPT plan usage max falls back to xhigh · iter -
- Decision: openai_codex_model_profile and openai_chatgpt_model_profile override openai_responses_supports_max_reasoning_effort=False
- Why: Both merge OpenAIProvider.model_profile but max was probed only on the direct OpenAI API; decision 2 allows max "only where a probe showed it works"; same precedent as openai_responses_supports_prompt_cache_diagnostics
- Source: https://github.com/pydantic/pydantic-ai/pull/10920
2026-10-22 · merge of main 391a13bc0 pushed at beac3a4c7 without review (budget exhausted) · iter -
- Decision: allowlist conflict resolved as the union of main's 4 waivers and this PR's 5; no fourth pre-push review
- Why: 3 of 3 reviews used; resolution verified by the fixed gate locally (check_api_compatibility.py --against v2.59.0 exit 0, all 9 waivers used) plus targeted tests; main's 9 commits since 5753715b5 change no effort mapping
- Source: https://github.com/pydantic/pydantic-ai/pull/10920
2026-10-22 · Bedrock xAI Grok max falls back to xhigh · iter -
- Decision: Bedrock 'xai' effort_map from main's #8639 gains 'max': 'xhigh'; bedrock-xai case added to test_every_effort_level_resolves
- Why: Merged main without it, unified max raised KeyError on us.xai.grok-4.6; Grok tops out at xhigh, and the brief says snap down to the provider's top level
- Source: https://github.com/pydantic/pydantic-ai/pull/8639
2026-10-22 · merge of main 767f52677 and Grok max fix pushed at 970caaac6 without review (budget exhausted) · iter -
- Decision: allowlist conflict resolved as union (main's Grok waiver + this PR's 5); thinking.md table keeps main's Bedrock Grok row and this PR's Mantle 'max' note; Grok effort_map gains 'max': 'xhigh'
- Why: 3 of 3 reviews used; semantic conflict reproduced (KeyError: 'max' on us.xai.grok-4.6 without the fix); API-compat gate exit 0 locally; targeted thinking, bedrock, resolution-matrix, capability-spec, tool-choice and moonshotai tests pass; pyright and ruff clean
- Source: https://github.com/pydantic/pydantic-ai/pull/10920
2026-10-23 · merge of main 3491aaa40 pushed at 9e5ffea88 without review (budget exhausted) · iter -
- Decision: realtime/settings.py thinking docstring conflict: took main's per-provider True wording, added 'max' to the level list; main's new Bedrock openai_responses variant (GPT-5.6/GPT-6 on Converse) resolves max via OPENAI_REASONING_EFFORT_MAP to xhigh, pinned in test_every_effort_level_resolves and its docs row; no other new effort map in main's 67 commits
- Why: 3 of 3 reviews used; API-compat gate exit 0 locally with all 14 waivers used; targeted thinking, bedrock, openai, anthropic, realtime and resolution-matrix tests pass; pyright and ruff clean
- Source: https://github.com/pydantic/pydantic-ai/pull/10920
Checklist
- No breaking changes in accordance with the version policy.
- Any permitted compatibility impact has a label, warning, migration, release note, and exact API-check waiver.
- PR title is fit for the release changelog.