Add `'max'` to unified `thinking` effort levels

Merged
DS
9d ago
+1876-66

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'})
Routethinking='max' sends
OpenAI Responses, GPT-5.6 and GPT-6max
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 Musexhigh
DeepSeek V4, Kimi K3/K2.6/K2.7 (Chat Completions)max
Anthropic and Bedrock Claude with an effort settingmax (Claude Opus 4.5: high)
OpenRoutermax (OpenRouter lowers it per model)
Z.AI GLM-5.2/5.3max
Gemini, xAI, Groq, Mistral, Snowflake, Bedrock Qwen, Bedrock xAI Groktheir 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 max live
  • 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 -

2026-10-21 · split OpenAI old-model xhigh 400 · iter -

2026-10-21 · CLAI 2 effort menus · iter -

2026-10-21 · Meta Muse falls back to xhigh · iter -

2026-10-21 · split OpenRouter xhigh, deepseek-flash, Bedrock gpt-oss · iter -

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 -

2026-10-21 · pre-push review 1 of 3 at f8b1a7ef41a07f277089934e8763ff0ed53e5c42 · iter -

2026-10-21 · pre-push review 2 of 3 at 92e4f21034699d37f6ed0fa57380278a2955e987 · iter -

2026-10-21 · pre-push review 2 of 3 at 92e4f21034699d37f6ed0fa57380278a2955e987 · iter -

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 -

2026-10-22 · pre-push review 3 of 3 at 535486a4520e378785d8fd437b6dba0b2142dce3 · iter -

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.