Repository navigation
Allow adaptive thinking with Tool Output and forced tool choice on Anthropic - #7200
Conversation
…thropic Anthropic's docs state only manual extended thinking conflicts with forced tool use; adaptive thinking is compatible. The adapter incorrectly treated both modes identically. Resolve the effective thinking type (not a boolean) in both compatibility guards so only 'enabled' blocks Tool Output and forcing. Closes #7195
This comment has been minimized.
This comment has been minimized.
PR reworded the Anthropic forcing error from 'with thinking mode' to 'with manual extended thinking', but left the test_thinking_with_forced_tool_choice_raises match regex stale, failing across all Python-version jobs.
…thropic API
Replace the mock-client test with a VCR recording of a real request. The cassette
shows `thinking={'type':'adaptive'}` and `tool_choice={'type':'any'}` going out
together and the API answering 200 with a `tool_use` block, which is the evidence
the capability claim rested on. An httpx event hook pins the outbound pair so the
assertion runs against what the client sends, not what the recording holds.
Also align the output-tool error message and docs with the narrowed guard: only
manual extended thinking conflicts with Tool Output.
|
David's AICA here: 🔍 Local review suite (automated, pre-merge)
Verdict: 🛑 2 blocking + 🔧 13 required action(s) before PASS · ✅ 15 applied — was 🛑 2 blocking + 🔧 12 required last round; both prior blockers and all 12 required items were resolved in Wave 1's gating lanes (runtime-behavior, public-api, integration-impact) are all clear — 0 blocking — so the gate is closed. Both blocking findings come from non-gating lanes (spec-conformance, ambient-matrix). Waves 2–3 and the per-wave
🛑 2 blockingAutomated review — flagged by Claude, not yet confirmed by a maintainer. 1. The tuple
Missing cell: Why it blocks: the failure is silent — 2. The new doc sentence and the new Anthropic documents Why it blocks: it is a factual error in this PR's own new user-facing text, on the exact model class the re-scope was written to protect. Wording-only fix — correcting the behavior instead would move every default-settings Fable/Mythos user off Tool Output, an outward regression, so that is not on the table. 🔧 13 required (do)Automated review — flagged by Claude, not yet confirmed by a maintainer. 1. 2. 3. 4. Extract the "is forcing available?" rule the two guards now duplicate · 5. 6. The 7. 8. The error text names a mode the most common caller never typed · 9. Gate 10. Note the 11. Cross-link the output-mode consequence from the thinking docs · 12. 13. Confirm the CI failure's cause before anything else · CI ✅ 15 applied — the 2 blocking + 12 required actions from the prior round, now resolvedAutomated review — flagged by Claude, not yet confirmed by a maintainer. Every item the previous round raised was addressed in 1. BLOCKER — Fable 5 / Mythos 5 outward regression · 2. BLOCKER — 3. "manual extended thinking" was a coined term with no landing page · 4. Neither error offered the remedy this PR unlocked · 5. 6. 7. 8. DouweM's #6957 "filtered to the requested set" correction wasn't ported · 9. The 10. Stale "Mirror the dual-check pattern from 11. 12. 13. Docs prose wasn't cross-referenced · 14. Two near-identical tests should have been one parametrize · 15. The Bedrock/OpenRouter defer had no tracking artifact · #7283
|
There was a problem hiding this comment.
Reviewed at 7d35df5f0a563a8e902f4a143bfd07430a51065a.
Requesting changes because of the Fable 5 / Mythos 5 / Mythos Preview regression: the new prepare_request guard branches only on thinking_type == 'enabled' and no longer consults anthropic_supports_forced_tool_choice, so on the three adaptive-capable + non-forced-tool-capable models the ToolOutput path now falls through to soft tool_choice={'type':'auto'} instead of being preserved as native output. The user's structured-output guarantee moves from "always schema-valid" to "model may answer in prose", and the resulting tool-defs filter invalidates Anthropic's prompt cache (the warning the docs paragraph directly above the edited line calls out). The PR's live probe is silent on this class — it only covers forced=True models — so I'd like either the guard to also gate on supports_forced_tool_choice (mirroring _support_tool_forcing a few lines down), or a sibling check that does the right thing for these three models, plus a VCR test pinning one of them with ToolOutput + adaptive thinking and asserting output_mode='native'. Two smaller items (call-site flag-off coverage, misleading comment at line 1485) are posted inline; they can ride along in this PR or a follow-up.
Generated by 🔎 CI Review
Comments that could not be inline-anchored
pydantic_ai_slim/pydantic_ai/models/anthropic.py:718
HIGH — Fable 5 / Mythos 5 silently degrade ToolOutput instead of falling back to Native
claude-fable-5, claude-mythos-5 and claude-mythos-preview are the only shipped models with anthropic_supports_adaptive_thinking=True and anthropic_supports_forced_tool_choice=False. The old thinking_enabled = type in ('enabled', 'adaptive') collapse grouped them with the manual-thinking case, so an `Agent('anthropic:claude-fable-5', output_type=ToolOutput(Foo), model_settings=ModelSett…
pydantic_ai_slim/pydantic_ai/models/anthropic.py:1455
LOW — call-site wiring for supports_adaptive_thinking is only exercised against the flag-on side via a real profile
The new VCR test on claude-opus-4-6 covers the supports_adaptive_thinking=True branch end-to-end, but the False branch is only covered by the parametrize unit test test_support_tool_forcing_adaptive_profile_mapping. The README Profile-flag test pinning rule says one side alone doesn't satisfy it — a recording can drift and not catch the loss of the field. Cheap to…
pydantic_ai_slim/pydantic_ai/models/anthropic.py:1485
NITPICK — comment still says "thinking enabled" now that the trigger is thinking_type == 'enabled' or supports_forced_tool_choice=False
With the new _support_tool_forcing logic, this soft-fallback path triggers for two distinct reasons that the old comment conflated:
thinking_type == 'enabled'— manual extended thinking on a model that otherwise accepts forcing.supports_forced_tool_choice=False— Claude Fable 5 / Mythos 5 / Mythos Preview, regardless of thinking mode.
A …
…ble models
`prepare_request` branched only on `thinking_type == 'enabled'`, so
`claude-fable-5` and `claude-mythos-5` — the only shipped models with
`anthropic_supports_adaptive_thinking=True` and
`anthropic_supports_forced_tool_choice=False` — moved from guaranteed Native
Output to a soft `tool_choice='auto'` the model may ignore. The guard now also
consults `anthropic_supports_forced_tool_choice`, mirroring its sibling
`_support_tool_forcing`, so only forcing-capable models change behavior.
Also:
- pin the new `supports_adaptive_thinking=` wiring with a live-recorded unified
`thinking` case, which is the only source that reads the profile flag
- record `/v1/messages/count_tokens` accepting adaptive thinking alongside
`tool_choice={'type': 'any'}`, the one surface the widened guard newly reaches
- use the repo's established "extended thinking" wording instead of the coined
"manual extended thinking", and point both errors at the adaptive remedy
- pin the `anthropic_thinking={'type': 'disabled'}` precedence over a unified
`thinking` moved into `params.thinking`
- port DouweM's #6957 single-tool filtering correction into the Anthropic doc,
and stop over-warning about cache breakage under adaptive thinking
…ive-thinking-forcing
… forcing call site
All three items from the
|
This review dismissal was performed by Claude Code using claude-opus-4-8 on behalf of David.
All three items were addressed on this branch (see the reply on this review naming the fixing commits); CI is green on the current head 186a7a256. Dismissing this stale verdict because the CI Review workflow shows skipped on the current sha, so the bot cannot re-review to clear its own state.
…e-thinking-forcing
`Cassette.requests` returns the recorded interactions, and the suite's VCR config sets no body matcher, so a request that stops sending adaptive thinking or the forced `tool_choice` still replays the recording and leaves the assertion green. Read the pair off an httpx event hook instead, matching the sibling Tool Output test. Also: name the non-forcing-model case in the cache-breaking table, make `_effective_thinking_type`'s adaptive flag required so a future caller can't silently inherit the pre-fix classification, align the stale profile comment with its docstring, attribute the OpenRouter forcing restriction to that route, and narrow a new test parameter off `Any`.
There was a problem hiding this comment.
Reviewed at 7c68318afa2b3662208b3eaa90034bc048c94e13.
Clean, well-targeted fix. The shared _effective_thinking_type helper unifies the mapping with _translate_thinking, so both prepare_request and _support_tool_forcing now branch on the effective type ('enabled'/'adaptive'/None) instead of the boolean conflation. Both call sites of _support_tool_forcing (scalar and tuple) carry the new supports_adaptive_thinking= argument.
Test coverage pins all three flag combinations in the live profile (claude-opus-4-6: adaptive=True/force=True; claude-sonnet-4-5: adaptive=False/force=True; claude-fable-5: adaptive=True/force=False), exercises both anthropic_thinking and unified thinking paths, and asserts the outbound pair via an httpx event hook so the cassette doesn't mask regressions. The Fable 5 / Mythos unconditional-rejection gate is verified before the thinking-type check. Docs and existing error-message wording are updated consistently.
Bedrock's sibling conflation in _is_thinking_enabled is intentionally deferred (tracked at #7283) — out of scope.
Generated by 🔎 CI Review
OpenRouter maps `reasoning` without `reasoning.max_tokens` to adaptive thinking on Claude 4.6, and Claude 4.7 is adaptive-only, so the route does have an adaptive equivalent. Pydantic AI's guard is blanket-off for every `anthropic/` model regardless of thinking mode, so state that adapter behavior and why it stays conservative, and link the tracking issue.
There was a problem hiding this comment.
Reviewed at 65f10c93adda8aa285d7e5264680622f4b177cd8.
LGTM — the fix is narrowly scoped to the exact bug that #7195 reported, the thinking_enabled boolean collapse is replaced by a shared _effective_thinking_type helper that mirrors _translate_thinking's mapping, and both guards (prepare_request and _support_tool_forcing) now branch on the effective type rather than a lumped flag. The session is documented in the issue as live-verified, not docs-only, and the new VCR cassette plus the httpx event hook pin the outbound thinking/tool_choice pair end-to-end.
Coverage of the relevant combinations is solid: test_auto_output_mode exercises every (model, anthropic_thinking) cell that flips the output mode; test_anthropic_single_tool_forcing_under_unified_thinking pins both sides of anthropic_supports_adaptive_thinking at the tuple call site in _prepare_tools_and_tool_choice (the site that was flagged previously as untested); test_support_tool_forcing_rejects_unsupported_model_with_adaptive_thinking and the Fable/Mythos assertions pin the anthropic_supports_forced_tool_choice=False gate; and the two-cassette test_anthropic_opus_46_adaptive_thinking_accepts_tool_output covers both the provider-specific and unified-thinking paths. Fable/Mythos regression coverage uses the live shipped profile rather than a hand-built one, so the guard is asserted against the actual flag combination rather than a synthetic shape.
The docs changes are consistent: the Anthropic doc now distinguishes extended vs. adaptive thinking, the openrouter md explicitly flags the conservative divergence pending #7283, and the tools-advanced table line is updated to match. No new findings surface.
Generated by 🔎 CI Review
The suite matches cassettes on method/scheme/host/port/path/query, never on the body, so a request whose payload drifts still replays against its recording. A `RequestCapture` fixture attaches an httpx event hook — which runs above the transport VCR patches and so fires on replay — and tests snapshot a projection of what actually went out: native tool declarations and `tool_choice`, the advertised tool set and system text, compaction message shape, cache breakpoint placement, and tool_result blocks. Three sites that asserted tool and `tool_addition` claims against `vcr.requests` now read the captured request instead. The snapshots record today's payloads, several of which already differ from their recordings; those are annotated in place as pre-existing drift.
…apture The factory existed in `tests/models/anthropic/conftest.py`, where the tests in the parent directory could not reach it — which is why the pattern never spread past that subdirectory. Move it up one level and add `capture=True`, which hands out the function-scoped `request_capture` client so a test can assert on the request as sent with a single argument. Collapses 28 three-line constructions to one line each. Imports stay inside the factory: this conftest also loads on shards installed without the `anthropic` extra, where a module-level import fails at collection.
There was a problem hiding this comment.
Reviewed at afb3eac68bda881ddf042db42aa2a585999ce70c.
The fix is correct and well-scoped. _effective_thinking_type mirrors _translate_thinking's mapping for every input I traced (provider-specific enabled/adaptive/disabled, unified True/string/False), so the two guards now branch on the same wire-level type that _translate_thinking emits. Fable 5 / Mythos regression is preserved by the new supports_forced_tool_choice check in prepare_request, pinned by test_auto_output_mode[adaptive_profile_that_cannot_force_switches_to_native] and [adaptive_profile_that_cannot_force_names_the_model] against the real shipped profile. The cassette for test_anthropic_opus_46_adaptive_thinking_accepts_tool_output confirms thinking: {type: adaptive} + tool_choice: {type: any} going out together, and test_anthropic_single_tool_forcing_under_unified_thinking exercises both sides of anthropic_supports_adaptive_thinking at the tuple call site of _support_tool_forcing. Docs (docs/models/anthropic.md, docs/tools-advanced.md, docs/models/openrouter.md) match the new behavior, with the OpenRouter page calling out the deliberate conservatism for that route. Posting as COMMENT rather than APPROVE/REQUEST_CHANGES per the bot-authored rule, and because the macroscopeapp thread on tests/models/conftest.py:61 remains open. One inline comment flags that the centerpiece new test bypasses the new request_capture fixture, leaving the connection-pool leak the fixture is meant to centralize.
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
index.crates.io
To allow these domains, add them to the
network.allowedlist in your workflow frontmatter:
network:
allowed:
- defaults
- "index.crates.io"See Network Configuration for more information.
Generated by 🔎 CI Review
…hrough it The fixture built its `httpx.AsyncClient` directly, so the autouse `close_httpx_clients` tracker — which only sees clients from `create_async_http_client` — never closed it and each test leaked a pool. `RequestCapture` also records request headers now, which is what the two lazy-advertisement tests needed from their own hooks: the cassette serializer strips `anthropic-*`, so beta gating is only visible on the wire. That lets the four remaining inline hooks go through the fixture.
…_model test_multiple_parallel_tool_calls hand-rolled the provider, the capture client and its own ANTHROPIC_API_KEY fallback; the anthropic_api_key fixture already does the env lookup and the factory already wires the capture client.
There was a problem hiding this comment.
Reviewed at 1945f29e0114e598ef427267549b51e0f9195be5.
End-to-end review of the 10-file diff; the prior wave-1 pass at d756c3bf covered the gating lanes (runtime, public API, integration) and the newly-added 3-round fixes (Fable 5/Mythos regression, tuple call-site coverage, request_capture conversion for the four tests with inline httpx.AsyncClient + event hooks) all hold against the current tree. Spot-checked on top:
_effective_thinking_typecorrectly handles the empty-/missing-anthropic_thinkingcases (bool({})isFalse, falls through to unified thinking;NonereturnsNone).- The
thinking_blocks_output_toolspredicate inprepare_requestcovers all four cases (None,'enabled','adaptive'×supports_forced_tool_choice). - The
adaptive_hintin_support_tool_forcingis correctly gated onsupports_adaptive_thinking— onclaude-fable-5(supports_forced_tool_choice=False) the unconditional rejection happens before the hint is computed, so no misleading "switch to adaptive" suggestion reaches the user. - The
RequestCapturefixture'sawait capture.client.aclose()lands the explicit cleanup the autouseclose_httpx_clientstracker can't see (client built directly, not viacreate_async_http_client). test_anthropic_count_tokens_with_adaptive_thinking_and_output_toolsreads the capturedcount_tokensbody via path suffix, so the assertion runs against what the client sent rather than the cassette.- The new test
test_anthropic_single_tool_forcing_under_unified_thinking[adaptive_profile_keeps_forcing]now pins the tuple_support_tool_forcingcall site withsupports_adaptive_thinking=True(claude-opus-4-6profile), closing the asymmetry the prior wave flagged.
One pre-existing oddity (not introduced by this PR but adjacent to it): the new anthropic_supports_adaptive_thinking docstring grew from one consumer to three, but the sibling anthropic_supports_forced_tool_choice docstring at pydantic_ai_slim/pydantic_ai/profiles/anthropic.py line 117 still says "Claude Fable 5 and Claude Mythos Preview reject it unconditionally" — the implementation at line 244 also rejects claude-mythos-5, and the docs and the _support_tool_forcing docstring use the broader "Mythos models" phrasing. Dropping or keeping is a minor doc-hygiene call, not a blocker.
No HIGH or CRITICAL findings. Verdict: APPROVE.
Generated by 🔎 CI Review
`tests/AGENTS.md` still described hand-building an `httpx.AsyncClient` with event hooks, which every migrated test in `tests/models/` now contradicts. Name `request_capture`, the `anthropic_model(capture=True)` routing and the body projections, and move the cited worked example onto the fixture so the doc and the example agree.
… gap The old wording called the drift 'most worth watching', which reads as the moved breakpoint being a live cache hazard. #4840 moved automatic caching onto the top-level parameter deliberately; only the recording is stale. Say that, and note that the usage snapshot below was recorded under the previous request shape.
There was a problem hiding this comment.
Reviewed at 8bcc1928f274d2a4b582645a92b614ae8f9912f1.
Verdict: no issues found. This PR correctly fixes #7195 by introducing _effective_thinking_type() and rewiring both prepare_request and _support_tool_forcing to branch on the effective type rather than collapsing 'enabled' and 'adaptive' into one boolean. The change preserves the existing manual-extended-thinking rejection, unblocks adaptive thinking with Tool Output and forced tool choice, and keeps the no-forcing gate (Fable 5 / Mythos) intact.
The test matrix in tests/models/test_tool_choice_unit.py and tests/test_thinking.py pins both sides of anthropic_supports_adaptive_thinking (claude-opus-4-6 on the flag-on side, claude-sonnet-4-5 on the flag-off side), so the profile-flag branches are exercised on both sides — Example 4 of the review guidelines is satisfied. The renamed test_anthropic_opus_46_adaptive_thinking_accepts_tool_output is a VCR-recorded end-to-end regression test that asserts the outbound thinking/tool_choice pair via request_capture, and the deleted test_unified_thinking_with_output_tools_auto_mode has been replaced by a parametrize matrix covering all combinations.
Note: this PR is bot-authored, so the review is submitted as a non-verdict comment per workflow rules.
Generated by 🔎 CI Review
…st shape Both recordings predated #4840, which moved automatic caching onto the top-level `cache_control` parameter, so they still carried a per-block breakpoint the code no longer sends. Re-recorded, which removes the gap and the comments explaining it. The streaming variant now records a cache read: the two tests share a padding string, so whichever records second finds the prefix already cached. Its docstring says so, and the pair now covers both compaction cache directions.
There was a problem hiding this comment.
Reviewed at 9a1452813ed054a18945d78812c99e64396c275b.
No findings.
The fix is correctly scoped: a single shared _effective_thinking_type helper now mirrors _translate_thinking's adaptive/enabled mapping, and both compatibility guards (prepare_request and _support_tool_forcing) branch on the resolved type instead of collapsing the two modes into one boolean. The gate in prepare_request (thinking_blocks_output_tools = thinking_type == 'enabled' or (thinking_type == 'adaptive' and not supports_forced_tool_choice)) correctly preserves all four combinations of {thinking type} × {supports_forced_tool_choice}, and both _support_tool_forcing call sites (scalar 'required' and tuple ('required', {names})) now thread supports_adaptive_thinking through.
Test coverage pins both sides of the new profile-flag dependencies end-to-end (Example 4 from review-instructions.md): test_support_tool_forcing_adaptive_profile_mapping parametrizes True/False; test_auto_output_mode and test_explicit_tool_output_raises cover claude-fable-5 (no forcing, no adaptive) and claude-opus-4-6 (both) for both adaptive and extended thinking; test_anthropic_single_tool_forcing_under_unified_thinking covers the unified-thinking single-tool path on claude-opus-4-6 (adaptive) and claude-sonnet-4-5 (non-adaptive); and test_anthropic_opus_46_adaptive_thinking_accepts_tool_output + the count_tokens sibling cover the live wire path for both provider_specific and unified thinking. The cassettes confirm thinking: {type: adaptive} paired with tool_choice: {type: any} returning 200. The request_capture/anthropic_model factory migration closes the connection-pool leak from the prior review and is adopted consistently across the centerpiece regression tests.
Manual extended thinking still rejects Tool Output, and claude-fable-5 / claude-mythos-* still reject forcing unconditionally — both invariants are pinned by the new tests.
Generated by 🔎 CI Review
This MR contains the following updates: | Package | Type | Update | Change | OpenSSF | |---|---|---|---|---| | [numpy](https://github.com/numpy/numpy) ([changelog](https://numpy.org/doc/stable/release)) | dependencies | patch | `2.5.2` → `2.5.3` | [](https://securityscorecards.dev/viewer/?uri=github.com/numpy/numpy) | | [openai](https://github.com/openai/openai-python) | dependencies | patch | `2.52.0` → `2.52.1` | [](https://securityscorecards.dev/viewer/?uri=github.com/openai/openai-python) | | [pydantic](https://github.com/pydantic/pydantic) ([changelog](https://docs.pydantic.dev/latest/changelog/)) | dependencies | patch | `2.13.4` → `2.13.5` | [](https://securityscorecards.dev/viewer/?uri=github.com/pydantic/pydantic) | | [pydantic-ai](https://github.com/pydantic/pydantic-ai) ([changelog](https://github.com/pydantic/pydantic-ai/releases)) | dependencies | patch | `2.27.0` → `2.27.1` | [](https://securityscorecards.dev/viewer/?uri=github.com/pydantic/pydantic-ai) | | [types-requests](https://github.com/python/typeshed) ([changelog](https://github.com/typeshed-internal/stub_uploader/blob/main/data/changelogs/requests.md)) | dependencies | patch | `2.33.0.20260712` → `2.33.0.20260906` | [](https://securityscorecards.dev/viewer/?uri=github.com/python/typeshed) | | [uvicorn](https://github.com/Kludex/uvicorn) ([changelog](https://uvicorn.dev/release-notes)) | dependencies | patch | `0.52.3` → `0.52.4` | [](https://securityscorecards.dev/viewer/?uri=github.com/Kludex/uvicorn) | --- ### Release Notes <details> <summary>numpy/numpy (numpy)</summary> ### [`v2.5.3`](https://github.com/numpy/numpy/releases/tag/v2.5.3): (Sep 6, 2026) [Compare Source](numpy/numpy@v2.5.2...v2.5.3) ### NumPy 2.5.3 Release Notes The NumPy 2.5.3 is a patch release that fixes bugs discovered after the 2.5.2 release. Apart from the usual bug and maintenance work, there are a number of StringDType related fixes for problems discovered during the ongoing string work in the main branch. This release supports Python versions 3.12-3.15 #### Changes - Casting a fixed-width byte string array (`np.bytes_`) to `StringDType` now raises `TypeError` when the bytes are not valid UTF-8. Previously the invalid bytes were stored as-is and later caused undefined behavior in string operations. ([gh-32296](numpy/numpy#32296)) - `MaskedArray._fill_value` would become stale when ufuncs that change dtype left the result holding a fill\_value typed for the old dtype. The mismatch was silent until something later called `_check_fill_value`, such as `.view()`, and then a `TypeError` would be raised. Now, when the copied fill\_value is no longer valid for the new dtype, fall back to the default fill\_value for that dtype instead of propagating the stale value. This may raise a `ComplexWarning` if the fill\_value is complex and the new dtype is real. ([gh-32423](numpy/numpy#32423)) #### Contributors A total of 9 people contributed to this release. People with a "+" by their names contributed a patch for the first time. - Charles Harris - Iason Krommydas - James Davies + - Joren Hammudoglu - Maanas Arora - Matti Picus - Nathan Goldbaum - Shikhar Goel + - Yeonho Kim + #### Pull requests merged A total of 27 pull requests were merged for this release. - [#​32235](numpy/numpy#32235): MAINT: Prepare 2.5.x for further development - [#​32289](numpy/numpy#32289): BUG: raise ValueError when reading into record array with references... - [#​32290](numpy/numpy#32290): BUG: avoid uninitialized memory access / NULL-pointer deref in... - [#​32291](numpy/numpy#32291): TYP: fix `np.random.{get,set}_bit_generator` implicit re-exports... - [#​32292](numpy/numpy#32292): BUG: don't assume strides are a multiple of itemsize in stringdtype... - [#​32293](numpy/numpy#32293): CI: fix ccache CC override, add CXX in mac Conda CI ([#​32285](numpy/numpy#32285)) - [#​32303](numpy/numpy#32303): BUG: Fix ref leak in \[convert\_from\_type]{#convert\_from\_type} for custom scalar types... - [#​32304](numpy/numpy#32304): BUG: fix a number of issues around iterators and StringDType... - [#​32326](numpy/numpy#32326): TST: avoid allocating huge tuple of arrays in concatenate test... - [#​32338](numpy/numpy#32338): BUG: avoid possible UB in 'safe' multiplication helpers ([#​32294](numpy/numpy#32294)) - [#​32339](numpy/numpy#32339): MAINT: use `PyObject_` functions instead of raw `PyArray_ ones` ([#​32331](numpy/numpy#32331)) - [#​32378](numpy/numpy#32378): BUG: validate UTF-8 and harden StringDType bounds handling ([#​32296](numpy/numpy#32296)) - [#​32380](numpy/numpy#32380): BUG: fix two error handling mistakes in stringdtype replace loop... - [#​32381](numpy/numpy#32381): BUG: fix visibility annotations for functions in StringDType... - [#​32384](numpy/numpy#32384): MAINT: Update ml\_dtypes pin to 8/21/2026. - [#​32385](numpy/numpy#32385): MAINT: Update numpy/\_core/src/umath/svml - [#​32410](numpy/numpy#32410): MAINT: skip failing cython limited API tests on Cython 3.3.0... - [#​32427](numpy/numpy#32427): BUG: close duplicated file descriptor if fdopen fails ([#​32386](numpy/numpy#32386)) - [#​32428](numpy/numpy#32428): MAINT: add missing space in warning and error messages ([#​32405](numpy/numpy#32405)) - [#​32430](numpy/numpy#32430): BUG: fix error handling in StringDType to fixed-width bytes case... - [#​32441](numpy/numpy#32441): MAINT: Only run nightly BLAS tests on main. - [#​32471](numpy/numpy#32471): BUG: fix memory leak in StringDType creation error path ([#​32470](numpy/numpy#32470)) - [#​32477](numpy/numpy#32477): BUG: fix stale fill\_value after ufuncs change MaskedArray dtype... - [#​32478](numpy/numpy#32478): MAINT: exit deadlock tests quickly on slow hardware ([#​32466](numpy/numpy#32466)) - [#​32481](numpy/numpy#32481): BUG: Backport StringDType byteorder fixes - [#​32506](numpy/numpy#32506): DOC: use static scipy doc site for intershpinx ([#​32503](numpy/numpy#32503)) - [#​32509](numpy/numpy#32509): BUG: fix crash in ufunc.resolve\_dtypes with a Python scalar type... </details> <details> <summary>openai/openai-python (openai)</summary> ### [`v2.52.1`](https://github.com/openai/openai-python/blob/HEAD/CHANGELOG.md#2521-2026-07-31) [Compare Source](openai/openai-python@v2.52.0...v2.52.1) Full Changelog: [v2.52.0...v2.52.1](openai/openai-python@v2.52.0...v2.52.1) ##### Chores - **ci:** pin setup-uv v5 to its underlying commit ([#​3560](openai/openai-python#3560)) ([cbdc98b](openai/openai-python@cbdc98b)) </details> <details> <summary>pydantic/pydantic (pydantic)</summary> ### [`v2.13.5`](https://github.com/pydantic/pydantic/releases/tag/v2.13.5) [Compare Source](pydantic/pydantic@v2.13.4...v2.13.5) #### v2.13.5 (2026-08-28) ##### What's Changed ##### Fixes - Allow reuse of validators when plugins are set by [@​Viicos](https://github.com/Viicos) in [#​13535](pydantic/pydantic#13535) - Fix missing GC traversal on some `pydantic-core` struct fields by [@​Viicos](https://github.com/Viicos) in [#​13624](pydantic/pydantic#13624) - Fix missing GC traversal in `pydantic-core` for `GeneralFieldsSerializer` by [@​Viicos](https://github.com/Viicos) in [#​13629](pydantic/pydantic#13629) - Count validated model fields once in smart unions by [@​tamird](https://github.com/tamird) in [#​13731](pydantic/pydantic#13731) </details> <details> <summary>pydantic/pydantic-ai (pydantic-ai)</summary> ### [`v2.27.1`](https://github.com/pydantic/pydantic-ai/releases/tag/v2.27.1): (2026-08-10) [Compare Source](pydantic/pydantic-ai@v2.27.0...v2.27.1) ##### 🛡️ Security This release fixed an information-disclosure issue: retry-prompt content (validation feedback sent back to the model, which can quote invalid values from its output) was not redacted by `InstrumentationSettings(include_content=False)` when the retry was not tied to a tool call. Now disclosed as [GHSA-3gh4-cghq-f8v4](GHSA-3gh4-cghq-f8v4) (low). Fixed here in `2.27.1` ([#​7357](pydantic/pydantic-ai#7357)); v1 users should upgrade to `1.107.4` or later. <!-- Release notes generated using configuration in .github/release.yml at main --> #### What's Changed ##### 🐛 Bug Fixes - Restore tool spans for failed argument validation by [@​adtyavrdhn](https://github.com/adtyavrdhn) in [#​6601](pydantic/pydantic-ai#6601) - Fix `XaiStreamedResponse` finish\_reason mapping for streaming responses by [@​pydanty](https://github.com/pydanty)\[bot] in [#​6814](pydantic/pydantic-ai#6814) - Point offline web UI hosting at the self-contained chat UI build by [@​dsfaccini](https://github.com/dsfaccini) in [#​7349](pydantic/pydantic-ai#7349) - Allow adaptive thinking with Tool Output and forced tool choice on Anthropic by [@​pydanty](https://github.com/pydanty)\[bot] in [#​7200](pydantic/pydantic-ai#7200) - Gate `RetryPromptPart` OpenTelemetry content on `include_content` by [@​sean-kim05](https://github.com/sean-kim05) in [#​7357](pydantic/pydantic-ai#7357) **Full Changelog**: <pydantic/pydantic-ai@v2.27.0...v2.27.1> </details> <details> <summary>Kludex/uvicorn (uvicorn)</summary> ### [`v0.52.4`](https://github.com/Kludex/uvicorn/releases/tag/0.52.4): Version 0.52.4 [Compare Source](Kludex/uvicorn@0.52.3...0.52.4) ##### Fixed - Remove duplicate `Date` headers from accepted WebSocket handshakes with `websockets-sansio` ([#​3078](Kludex/uvicorn#3078)) **Full Changelog**: <Kludex/uvicorn@0.52.3...0.52.4> </details> --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zOS4yIiwidXBkYXRlZEluVmVyIjoiNDQuOTAuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIiwicmVub3ZhdGUiXX0=--> See merge request swiss-armed-forces/cyber-command/cea/loom!769 Co-authored-by: Loom MR Pipeline Trigger <group_103951964_bot_9504bb8dead6d4e406ad817a607f24be@noreply.gitlab.com> Co-authored-by: shrewd-laidback palace <shrewd-laidback-palace-736-c41-2c1-e464fc974@swiss-armed-forces-open-source.ch>
Fixes #7195.
Problem
The Anthropic adapter treated adaptive thinking (
anthropic_thinking={"type": "adaptive"}) as incompatible with Tool Output and forced tool choice — identical to manual extended thinking ({"type": "enabled"}). On current flagship Claude models (Sonnet 4.6+, Opus 4.6+) that support adaptive thinking, enabling it raised aUserErrorfor explicitToolOutput(...)or silently degraded auto-mode output to Native Output.This is incorrect: per Anthropic's official docs, only manual extended thinking conflicts with forced tool use; adaptive thinking supports it.
Root cause
prepare_requestand module-level_support_tool_forcingboth computed a booleanthinking_enabled = type in ("enabled", "adaptive"), collapsing both thinking modes into one flag._translate_thinkingalready distinguishes the two (mapping unified thinking toadaptiveon adaptive-capable profiles,enabledotherwise), but the compatibility guards ignored that distinction.Fix
Add a shared module-level helper
_effective_thinking_type(anthropic_thinking, unified_thinking, supports_adaptive_thinking) -> "enabled" | "adaptive" | None, using the same mapping_translate_thinkingbuilds the wire payload with. Both guards now branch on the effective type rather than a boolean:prepare_requestrejects Tool Output / switches auto output mode only when the type is"enabled"(not"adaptive")._support_tool_forcingallows forcing when the type is not"enabled".Invariants preserved
{"type": "enabled"}) still raisesUserErrorfor Tool Output and rejects forcing — not regressed.anthropic_supports_forced_tool_choice=False(Claude Fable 5, Claude Mythos) still reject forcing unconditionally; that gate is checked before the thinking type and is pinned by a new unit test._support_tool_forcing(via_prepare_tools_and_tool_choice), so no separate copy needed fixing.Verification
The capability claim is live-verified, not docs-only. Probing the API directly on
claude-sonnet-4-6,claude-opus-4-6andclaude-opus-5, adaptive thinking is accepted with bothtool_choice={'type': 'any'}and{'type': 'tool', ...}, while manual extended thinking is rejected on all three with400 Thinking may not be enabled when tool_choice forces tool use.test_anthropic_opus_46_adaptive_thinking_accepts_tool_outputis now a VCR test recorded from that live call: the cassette showsthinking: {type: adaptive}andtool_choice: {type: any}going out together and the API answering200with atool_useblock. An httpx event hook pins the outbound pair so the assertion runs against what the client actually sends rather than what the recording holds.Live probe matrix (identical on all three models)
tool_choice{'type': 'adaptive'}{'type': 'any'}tool_use{'type': 'adaptive'}{'type': 'tool', 'name': ...}tool_use{'type': 'adaptive'}{'type': 'auto'}thinking+tool_use{'type': 'enabled', 'budget_tokens': 1024}{'type': 'any'}Thinking may not be enabled when tool_choice forces tool use.{'type': 'any'}tool_useThis supersedes the note on #4216 ("tool choice + adaptive thinking is as unsupported as before"), which held when Opus 4.6 shipped in February but no longer does.
Closes #7195
💰 Run usage: 650,333 tokens, 32 model requests (est. cost $0.3075)
pydanty-pr-openerpydanty-edit-executorpydanty-test-runner💰 Run usage: 478,123 tokens, 25 model requests (est. cost $0.2300)
pydanty-pr-openerpydanty-test-runnerpydanty-edit-executor