Skip to content

Allow adaptive thinking with Tool Output and forced tool choice on Anthropic - #7200

Merged
dsfaccini merged 19 commits into
mainfrom
fix-anthropic-adaptive-thinking-forcing
Aug 10, 2026
Merged

dsfaccini merged 19 commits into
mainfrom
fix-anthropic-adaptive-thinking-forcing

Conversation

@pydanty

@pydanty pydanty Bot commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

This pull request was posted by Claude Code using claude-opus-5 on behalf of David.
David directed the live verification but has not reviewed the diff yet.

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 a UserError for explicit ToolOutput(...) 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_request and module-level _support_tool_forcing both computed a boolean thinking_enabled = type in ("enabled", "adaptive"), collapsing both thinking modes into one flag. _translate_thinking already distinguishes the two (mapping unified thinking to adaptive on adaptive-capable profiles, enabled otherwise), 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_thinking builds the wire payload with. Both guards now branch on the effective type rather than a boolean:

  • prepare_request rejects Tool Output / switches auto output mode only when the type is "enabled" (not "adaptive").
  • _support_tool_forcing allows forcing when the type is not "enabled".

Invariants preserved

  • Manual extended thinking ({"type": "enabled"}) still raises UserError for Tool Output and rejects forcing — not regressed.
  • Models with 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.
  • The streaming path uses the same module-level _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-6 and claude-opus-5, adaptive thinking is accepted with both tool_choice={'type': 'any'} and {'type': 'tool', ...}, while manual extended thinking is rejected on all three with 400 Thinking may not be enabled when tool_choice forces tool use.

test_anthropic_opus_46_adaptive_thinking_accepts_tool_output is now a VCR test recorded from that live call: the cassette shows thinking: {type: adaptive} and tool_choice: {type: any} going out together and the API answering 200 with a tool_use block. 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)
thinking tool_choice result
{'type': 'adaptive'} {'type': 'any'} 200, tool_use
{'type': 'adaptive'} {'type': 'tool', 'name': ...} 200, tool_use
{'type': 'adaptive'} {'type': 'auto'} 200, thinking + tool_use
{'type': 'enabled', 'budget_tokens': 1024} {'type': 'any'} 400 Thinking may not be enabled when tool_choice forces tool use.
(none) {'type': 'any'} 200, tool_use

This 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

Review in cubic

💰 Run usage: 650,333 tokens, 32 model requests (est. cost $0.3075)
Agent Cost Input Output Requests Tool calls Model
pydanty-pr-opener $0.2247 568,885 9,341 22 28 glm-5.2
pydanty-edit-executor $0.0522 47,492 790 6 4 gpt-5.6-luna
pydanty-test-runner $0.0305 22,493 1,332 4 4 gpt-5.6-luna
total $0.3075 638,870 11,463 32 36
💰 Run usage: 478,123 tokens, 25 model requests (est. cost $0.2300)
Agent Cost Input Output Requests Tool calls Model
pydanty-pr-opener $0.1833 428,223 7,842 18 24 glm-5.2
pydanty-test-runner $0.0256 21,744 642 4 2 gpt-5.6-luna
pydanty-edit-executor $0.0212 19,372 300 3 2 gpt-5.6-luna
total $0.2300 469,339 8,784 25 28

…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
@pydanty pydanty Bot added bug Report that something isn't working, or PR implementing a fix anthropic labels Aug 6, 2026
@github-actions github-actions Bot added the size: M Medium PR (101-500 weighted lines) label Aug 6, 2026
@macroscopeapp

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.
@dsfaccini

dsfaccini commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

David's AICA here: 🔍 Local review suite (automated, pre-merge)

Wave Status Blocking Required (do) Warnings (defer/skip)
1 · code-correctness ✅ gate closed (9 ran, 3 skipped) 2 13 18
2 · tests/cassettes/docs — not yet 0 0 0
3 · cross-cutting/siblings — not yet 0 0 0

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 b09a3e25, and this is a fresh wave-1 pass over the resulting diff, not the same findings restated.

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 review-claim-check adjudication have not been dispatched, so counts are pre-adjudication and two findings below are in open disagreement between lanes.

⚠️ CI is RED on this sha — every test on 3.x job fails (both pydantic-ai-slim and pydantic-evals shards) while main is green and all six other open PRs show 0 failing test jobs, so this is not the known HuggingFace flake. Not reproduced locally: tests/models/test_anthropic.py, tests/test_thinking.py, tests/models/test_tool_choice_unit.py, tests/models/test_bedrock.py, tests/models/test_openrouter.py and the relevant test_examples.py selection are all green here (837 passed). The branch is ~20 commits behind main and CI tests the refs/pull/7200/merge commit, so the leading hypothesis is an interaction with commits landed since 6805c0eb6 — merge main and re-run before chasing anything else. Cause not yet confirmed; Actions logs are not downloadable from this environment.

🛑 2 blocking

Automated review — flagged by Claude, not yet confirmed by a maintainer.

1. The tuple _support_tool_forcing call site's new supports_adaptive_thinking= argument is unpinned — deleting it leaves the suite green · pydantic_ai_slim/pydantic_ai/models/anthropic.py · AnthropicModel._prepare_tools_and_tool_choice

_prepare_tools_and_tool_choice calls _support_tool_forcing from two sites: the scalar 'required' site and the tuple ('required', {names}) site. The new [unified] cassette pins the scalar site end-to-end, closing last round's blocker for that half — but the tuple site never receives supports_adaptive_thinking=True in any test. Verified executably (a scratchpad pytest plugin recomputed every call with the flag forced to False, no repo mutation): across test_anthropic.py + test_tool_choice_unit.py + test_tool_choice_matrix.py + test_thinking.py (595 green tests) exactly two calls diverge, and both are on the scalar site or hand-pass the flag.

Missing cell: anthropic_supports_adaptive_thinking=True × unified thinking='high' × resolved shape tuple × anthropic_supports_forced_tool_choice=True. Reachable in production via Agent(AnthropicModel('claude-opus-4-6'), model_settings=ModelSettings(thinking='high', tool_choice=ToolOrOutput(function_tools=['tool_a']))) — ToolOrOutput is public and is not caught by the agent-level static-tool_choice guard, which only rejects 'required' and list[str]. Also reachable via tool_choice='none' with output tools, a callable get_model_settings(), or pydantic_ai.direct.model_request.

Why it blocks: the failure is silent — tool_choice degrades from {'type':'tool','name':...} to {'type':'auto'} with tool_defs filtered client-side, so the model may answer with text instead of calling the tool, and the tool-array filter invalidates Anthropic's prompt cache. No error, no 400, no cassette diff. Same shape as #6614. Fix is one parametrize row beside the existing test_anthropic_fallback_single_tool_with_thinking_filters_tool_defs, routed through model.prepare_request(...) first so the unified value arrives as params.thinking the way production delivers it. No cassette needed — the wire claim is already proven by the new {'type':'any'} recordings.

2. The new doc sentence and the new UserError both say "while thinking" for models where thinking is always on and cannot be turned off · docs/models/anthropic.md · pydantic_ai_slim/pydantic_ai/models/anthropic.py · AnthropicModel.prepare_request

Anthropic documents claude-fable-5, claude-mythos-5 and claude-mythos-preview as thinking-always-on ("no configuration needed"), and that setting a parameter to its default equals omitting it. Verified executably on this branch through prepare_request (no network): those models with no thinking setting accept explicit ToolOutput(...) (output_mode='tool'), while the same models with anthropic_thinking={'type':'adaptive'} — a no-op there — raise UserError. So two artifacts this PR adds state something untrue: the doc clause "those switch away from it while thinking, as they always have", and the message f'{self.model_name!r} does not support output tools while thinking, …'.

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. test_anthropic_count_tokens_with_adaptive_thinking_and_output_tools asserts the cassette body, not the live request · tests/models/test_anthropic.py
Raised independently by precedent (APPLIES:H) and spec-conformance. It reads json.loads(vcr.requests[0].body); tests/conftest.py::vcr_config sets no match_on, so the body is not matched and a regression that stopped sending thinking: adaptive + tool_choice: any would still match its recording and still pass. tests/AGENTS.md codifies the hook as the default for outbound-field assertions (from DouweM on #6765), and this PR's sibling test already attaches one. Fix: same event_hooks={'request': [...]} client, assert on captured [0].

2. docs/models/openrouter.md still asserts, as an Anthropic-API fact, what this PR disproves · docs/models/openrouter.md
Converged by integration-impact, precedent and auto-review. Line 100 reads "Anthropic models don't support a forced tool_choice while thinking is enabled. Where the Anthropic API rejects that combination outright…". Verified: OpenRouterModel._support_tool_forcing genuinely blocks forcing for any thinking (openrouter_reasoning is effort-shaped, no adaptive type), so lines 102–103 are correct and must not change — only line 100's framing is wrong, attributing the restriction to the Anthropic API rather than the OpenRouter route. Precedent: dsfaccini on #5911 and #6793 — a doc sentence the PR invalidates gets fixed in that PR.

3. docs/tools-advanced.md Anthropic cache-breaking row is now the only one omitting the non-forcing-model case · docs/tools-advanced.md
The diff rewrites this exact cell to …OR a single tool with extended thinking, but the same client-side filter also fires when anthropic_supports_forced_tool_choice=False (Fable 5 / Mythos), no thinking involved. Both sibling rows name it verbatim (OpenAI Chat: "on a model that doesn't support forcing"; Bedrock: "with thinking enabled or on a model that doesn't support forcing"). One clause, on a line this diff already touches.

4. Extract the "is forcing available?" rule the two guards now duplicate · pydantic_ai_slim/pydantic_ai/models/anthropic.py · prepare_request / _support_tool_forcing
The rule is encoded twice over the same two inputs and must be hand-kept in sync. This already bit inside this PR: e8a89961f wrote the prepare_request guard without anthropic_supports_forced_tool_choice, which is exactly last round's blocker 1; b09a3e25 fixed it by hand-copying the condition rather than sharing it, so the divergence stays available to the next change. Proposed helper is truth-table-equivalent on all five reachable cells (verified by the reviewer) and has two call sites, so it does not trip the no-single-use-utilities rule.

5. _translate_thinking carries no pointer back to the guard that must mirror it · pydantic_ai_slim/pydantic_ai/models/anthropic.py
Raised by both code-reuse and auto-review. _effective_thinking_type's docstring names _translate_thinking as the mapping it reproduces; the reverse marker doesn't exist, so an editor of _translate_thinking gets no signal a second copy of the mapping is now load-bearing. Failure is silent both ways (a wrongly-permitted force → 400; a wrongly-blocked one → unexplained Tool Output→Native switch). Comment-only.

6. The = False default on _effective_thinking_type's supports_adaptive_thinking is never exercised and is the unsafe direction · pydantic_ai_slim/pydantic_ai/models/anthropic.py
Both call sites pass it explicitly and no test calls the helper directly, so the default is dead — and it defaults to the pre-fix classification, so a future caller who forgets the kwarg silently reintroduces #7195 with no type error. Deletion-only. (The identical-looking default on _support_tool_forcing is a different case and should stay — tests call that one with the default, and it mirrors its own pre-existing supports_forced_tool_choice: bool = True.)

7. params_thinking: Any where ThinkingLevel | None is exact · tests/models/test_tool_choice_unit.py · test_support_tool_forcing_thinking_detection
Only None and 'high' are passed, and the call site's ModelRequestParameters.thinking is typed ThinkingLevel | None. Repo rule against Any where a narrower type exists; one import + one annotation. (The sibling settings: Any is pre-existing and genuinely heterogeneous — not the same case.)

8. The error text names a mode the most common caller never typed · pydantic_ai_slim/pydantic_ai/models/anthropic.py
"extended thinking" is the right vendor term and the right replacement for "thinking mode" — but the commonest way to reach either message is thinking='high' on a non-adaptive model, where the user typed no type at all and the adaptive remedy hint is suppressed (supports_adaptive_thinking=False). Tie the term to what they wrote. This is the third round for this wording; the decision log already flagged it twice, so decide it here rather than deferring again.

9. Gate prepare_request's adaptive remedy hint on supports_forced_tool_choice, as its sibling already is · pydantic_ai_slim/pydantic_ai/models/anthropic.py
Lanes disagree — needs adjudication. public-api says do: _support_tool_forcing early-raises on not supports_forced_tool_choice before building its hint, so its hint can never misfire; prepare_request's has no such gate, and on a profile with adaptive=True + forced=False + budget thinking allowed it would advise a switch that lands on the other new error two lines up. runtime-behavior says skip: verified unreachable today (Fable/Mythos hit anthropic_disallows_budget_thinking first; Mythos Preview isn't adaptive-capable), and adding the conjunct creates a branch arm no profile reaches — a partial-branch risk under fail_under = 100. Both agree an invariant comment is the zero-coverage-cost floor.

10. Note the anthropic_thinking={'type':'disabled'} precedence flip in the PR body · PR description
_support_tool_forcing used to short-circuit on bool(params.thinking) before reading anthropic_thinking; it now lets anthropic_thinking win. Executably confirmed: {'type':'disabled'} + unified thinking='high' returns False at merge-base and True here. It is correct (that's what goes on the wire) and now has a test param — but it is a second behavior change riding along, and a reviewer reading only the PR body would miss it.

11. Cross-link the output-mode consequence from the thinking docs · docs/capabilities/thinking.md
After this PR the thinking type silently decides whether Tool Output survives, and on Fable/Mythos raises a UserError — documented only under docs/models/anthropic.md § Forced tool choice, a heading a user configuring anthropic_thinking has no reason to open. The page already hosts this kind of cross-cutting caveat (the Opus 5 effort-cap note). One line. May be one finding with whatever review-docs raises in wave 2.

12. claude-mythos-preview is missing from anthropic_supports_adaptive_thinking · pydantic_ai_slim/pydantic_ai/profiles/anthropic.py
Anthropic lists it among the always-on/adaptive models; the profile omits it. Verified inert for resolved output_mode, but this PR makes the flag load-bearing for a second decision, so the gap is now user-visible: unified thinking='high' + ToolOutput(...) there reports "does not support extended thinking" for a model with no extended-thinking mode. Tracked in #7283 — fixing it flips that model's wire payload and shifts test_resolution_matrix, which is why it isn't folded in here.

13. Confirm the CI failure's cause before anything else · CI
See the banner above. Merge main (the branch is ~20 commits behind and CI tests the merge commit) and re-run; if it stays red with main merged, the cause is in this diff and every finding below it is premature.

✅ 15 applied — the 2 blocking + 12 required actions from the prior round, now resolved

Automated review — flagged by Claude, not yet confirmed by a maintainer.

Every item the previous round raised was addressed in b09a3e25; the two blocking findings above are new, surfaced by re-deriving the matrix over the resulting diff.

1. BLOCKER — Fable 5 / Mythos 5 outward regression · models/anthropic.py · prepare_request
The guard now also consults anthropic_supports_forced_tool_choice, so adaptive thinking keeps Tool Output only on models that can actually force. runtime-behavior re-probed all three non-forcing models × {explicit enabled, enabled-no-budget, unified 'high', explicit adaptive}: every combination still raises, none reaches a soft-auto request. Pinned by real-model tests on claude-fable-5.

2. BLOCKER — supports_adaptive_thinking= call-site wiring untested · models/anthropic.py · _prepare_tools_and_tool_choice
Closed for the scalar 'required' call site by a live-recorded [unified] cassette (ModelSettings(thinking='high') on claude-opus-4-6 → wire tool_choice={'type':'any'}); deleting that argument now fails the suite. The tuple call site remains unpinned — that residue is blocking finding 1 above.

3. "manual extended thinking" was a coined term with no landing page · models/anthropic.py, profiles/anthropic.py, docs/models/anthropic.md
Reverted to the repo's established "extended thinking" (docs/capabilities/thinking.md already contrasts it with adaptive thinking) in both UserError strings, both profile docstrings and the docs.

4. Neither error offered the remedy this PR unlocked · models/anthropic.py
Both guards now append an adaptive suggestion, gated on supports_adaptive_thinking.

5. anthropic_supports_adaptive_thinking docstring was stale · profiles/anthropic.py
Now documents its second contract — that it also decides whether unified thinking blocks forcing and switches Tool Output away.

6. count_tokens × adaptive was unverified · tests/models/test_anthropic.py
Live-probed: /v1/messages/count_tokens returns 200 for thinking={'type':'adaptive'} + tool_choice={'type':'any'} on claude-opus-4-6. New cassette pins it. (Its assertion style is required action 1 above.)

7. docs/tools-advanced.md cache row over-warned · docs/tools-advanced.md
Both the provider-support row and the cache-breaking row now say "extended thinking" instead of "thinking enabled". (The missing non-forcing-model clause is required action 3.)

8. DouweM's #6957 "filtered to the requested set" correction wasn't ported · docs/models/anthropic.md
The single-tool scoping and the retry sentence are now carried over verbatim from the corrected openrouter.md text.

9. The {'type':'disabled'} precedence flip was unpinned · tests/models/test_tool_choice_unit.py
New provider_specific_disabled_takes_precedence_over_params_thinking param, with params_thinking threaded through the whole parametrize.

10. Stale "Mirror the dual-check pattern from prepare_request()" comment · models/anthropic.py
Removed; only the still-load-bearing sentence about params.thinking remains.

11. _map_tool_definitions comment contradicted the file · models/anthropic.py
Now says "extended thinking, or a model that rejects it outright".

12. supports_adaptive_thinking was positional on _effective_thinking_type · models/anthropic.py
Keyword-only, matching its sibling. (Its default value is required action 6.)

13. Docs prose wasn't cross-referenced · docs/models/anthropic.md
"extended thinking" and "adaptive thinking" now link to capabilities/thinking.md#anthropic and #adaptive-thinking-effort; both anchors verified to resolve.

14. Two near-identical tests should have been one parametrize · tests/test_thinking.py
TestAnthropicUnifiedThinkingConflict became TestAnthropicThinkingOutputToolsConflict — two parametrized tests over 7 cases, built on real shipped profiles via AnthropicModel(model_name, provider=...) instead of AnthropicModel.__new__ + hand-built profiles.

15. The Bedrock/OpenRouter defer had no tracking artifact · #7283
Filed, covering Bedrock's _is_thinking_enabled conflation, the openrouter.md contradiction and the claude-mythos-preview flag gap — each with the live-probe step it needs first. Five reviewers this round still reported it as unfiled because pr-decisions.md's older entry says so; the newer entry supersedes it.

⚠️ 5 informational (defer/skip)

Automated review — flagged by Claude, not yet confirmed by a maintainer.

1. Bedrock _is_thinking_enabled carries the identical conflation · pydantic_ai_slim/pydantic_ai/models/bedrock.py
Five reviewers raised it. Not widened here — AGENTS.md requires reproducing on the sibling first, and Bedrock Converse acceptance of toolChoice: {any:{}} alongside additionalModelRequestFields.thinking={'type':'adaptive'} is unverified. Tracked at #7283.

2. anthropic_supports_adaptive_thinking's public contract widened from one consumer to three · pydantic_ai_slim/pydantic_ai/profiles/anthropic.py
A user who set it on a custom profile purely to select the {'type':'adaptive'} wire payload now also gets forcing unblocked and Tool Output preserved. Skip — the coupling is the fix: a guard that doesn't mirror _translate_thinking answers a question the wire payload already answered differently, which is the #7195 bug class. Shape untouched, docstring and docs updated in the same diff.

3. TemporalModel.prepare_request bypasses the AnthropicModel override on the model-string path · pydantic_ai_slim/pydantic_ai/durable_exec/temporal/_model.py
Pre-existing and narrowed by this diff — for adaptive the instance path now matches the string path. Skip; belongs to durable-exec owners.

4. No test pins adaptive + Tool Output through run_stream · tests/models/test_anthropic.py
Verified as a non-dimension: request, request_stream and count_tokens all route through the same prepare_request, and _prepare_tools_and_tool_choice is shared across stream=True/False. Skip — coverage shape is wave 2's lane.

5. prepare_request is ~78 lines across five concerns and this diff adds ~19 · pydantic_ai_slim/pydantic_ai/models/anthropic.py
Checked against BedrockConverseModel.prepare_request, which has the same shape and inlines its equivalent block identically. Skip — extracting would diverge from the sibling for no correctness gain.

Reviewed at 2026-08-07T19:05Z · diff d756c3bf · wave 1 only (9 ran, 3 skipped: spec-coverage, ui-security, suspend-resume); claim-check and waves 2–3 not yet dispatched.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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:

  1. thinking_type == 'enabled' — manual extended thinking on a model that otherwise accepts forcing.
  2. 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
Comment thread docs/models/anthropic.md Resolved
@dsfaccini

Copy link
Copy Markdown
Contributor

This reply was posted by Claude Code using claude-opus-5 on behalf of David.

All three items from the CI Review at 7d35df5f0 are addressed — the HIGH one in b09a3e25b, which the review predates.

  1. HIGH — Fable 5 / Mythos regression. prepare_request's guard now also consults anthropic_supports_forced_tool_choice, so those three models keep their guaranteed Native Output. Pinned by test_auto_output_mode[adaptive_profile_that_cannot_force_switches_to_native] (claude-fable-5 + thinking='high' → output_mode='native'), which uses the real shipped profile rather than a hand-built one. It's a pre-request guard, so there's no request to record.
  2. LOW — call-site flag-off coverage. 795f57779 adds test_anthropic_single_tool_forcing_under_unified_thinking, parametrized over both sides of anthropic_supports_adaptive_thinking at the single-tool call site: claude-opus-4-6 stays {'type':'tool'} with unfiltered tools, claude-sonnet-4-5 softens to {'type':'auto'} with tools filtered. Verified by mutation — deleting the supports_adaptive_thinking= argument fails the flag-on row.
  3. NITPICK — comment. Reworded in b09a3e25b to name both triggers.

@dsfaccini
dsfaccini dismissed github-actions[bot]’s stale review August 7, 2026 17:52

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.

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

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.
@github-actions github-actions Bot added size: L Large PR (501-1500 weighted lines) and removed size: M Medium PR (101-500 weighted lines) labels Aug 8, 2026
Comment thread tests/models/conftest.py Resolved
…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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "index.crates.io"

See Network Configuration for more information.

Generated by 🔎 CI Review

Comment thread tests/models/test_anthropic.py Resolved
…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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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_type correctly handles the empty-/missing-anthropic_thinking cases (bool({}) is False, falls through to unified thinking; None returns None).
  • The thinking_blocks_output_tools predicate in prepare_request covers all four cases (None, 'enabled', 'adaptive' × supports_forced_tool_choice).
  • The adaptive_hint in _support_tool_forcing is correctly gated on supports_adaptive_thinking — on claude-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 RequestCapture fixture's await capture.client.aclose() lands the explicit cleanup the autouse close_httpx_clients tracker can't see (client built directly, not via create_async_http_client).
  • test_anthropic_count_tokens_with_adaptive_thinking_and_output_tools reads the captured count_tokens body 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_forcing call site with supports_adaptive_thinking=True (claude-opus-4-6 profile), 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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

@dsfaccini
dsfaccini merged commit 9b0e725 into main Aug 10, 2026
60 checks passed
@dsfaccini
dsfaccini deleted the fix-anthropic-adaptive-thinking-forcing branch August 10, 2026 22:06
736-c41-2c1-e464fc974 added a commit to Swiss-Armed-Forces/Loom that referenced this pull request Sep 15, 2026
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` | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/numpy/numpy/badge)](https://securityscorecards.dev/viewer/?uri=github.com/numpy/numpy) |
| [openai](https://github.com/openai/openai-python) | dependencies | patch | `2.52.0` → `2.52.1` | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/openai/openai-python/badge)](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` | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/pydantic/pydantic/badge)](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` | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/pydantic/pydantic-ai/badge)](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` | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/python/typeshed/badge)](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` | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Kludex/uvicorn/badge)](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.

- [#&#8203;32235](numpy/numpy#32235): MAINT: Prepare 2.5.x for further development
- [#&#8203;32289](numpy/numpy#32289): BUG: raise ValueError when reading into record array with references...
- [#&#8203;32290](numpy/numpy#32290): BUG: avoid uninitialized memory access / NULL-pointer deref in...
- [#&#8203;32291](numpy/numpy#32291): TYP: fix `np.random.{get,set}_bit_generator` implicit re-exports...
- [#&#8203;32292](numpy/numpy#32292): BUG: don't assume strides are a multiple of itemsize in stringdtype...
- [#&#8203;32293](numpy/numpy#32293): CI: fix ccache CC override, add CXX in mac Conda CI ([#&#8203;32285](numpy/numpy#32285))
- [#&#8203;32303](numpy/numpy#32303): BUG: Fix ref leak in \[convert\_from\_type]{#convert\_from\_type} for custom scalar types...
- [#&#8203;32304](numpy/numpy#32304): BUG: fix a number of issues around iterators and StringDType...
- [#&#8203;32326](numpy/numpy#32326): TST: avoid allocating huge tuple of arrays in concatenate test...
- [#&#8203;32338](numpy/numpy#32338): BUG: avoid possible UB in 'safe' multiplication helpers ([#&#8203;32294](numpy/numpy#32294))
- [#&#8203;32339](numpy/numpy#32339): MAINT: use `PyObject_` functions instead of raw `PyArray_ ones` ([#&#8203;32331](numpy/numpy#32331))
- [#&#8203;32378](numpy/numpy#32378): BUG: validate UTF-8 and harden StringDType bounds handling ([#&#8203;32296](numpy/numpy#32296))
- [#&#8203;32380](numpy/numpy#32380): BUG: fix two error handling mistakes in stringdtype replace loop...
- [#&#8203;32381](numpy/numpy#32381): BUG: fix visibility annotations for functions in StringDType...
- [#&#8203;32384](numpy/numpy#32384): MAINT: Update ml\_dtypes pin to 8/21/2026.
- [#&#8203;32385](numpy/numpy#32385): MAINT: Update numpy/\_core/src/umath/svml
- [#&#8203;32410](numpy/numpy#32410): MAINT: skip failing cython limited API tests on Cython 3.3.0...
- [#&#8203;32427](numpy/numpy#32427): BUG: close duplicated file descriptor if fdopen fails ([#&#8203;32386](numpy/numpy#32386))
- [#&#8203;32428](numpy/numpy#32428): MAINT: add missing space in warning and error messages ([#&#8203;32405](numpy/numpy#32405))
- [#&#8203;32430](numpy/numpy#32430): BUG: fix error handling in StringDType to fixed-width bytes case...
- [#&#8203;32441](numpy/numpy#32441): MAINT: Only run nightly BLAS tests on main.
- [#&#8203;32471](numpy/numpy#32471): BUG: fix memory leak in StringDType creation error path ([#&#8203;32470](numpy/numpy#32470))
- [#&#8203;32477](numpy/numpy#32477): BUG: fix stale fill\_value after ufuncs change MaskedArray dtype...
- [#&#8203;32478](numpy/numpy#32478): MAINT: exit deadlock tests quickly on slow hardware ([#&#8203;32466](numpy/numpy#32466))
- [#&#8203;32481](numpy/numpy#32481): BUG: Backport StringDType byteorder fixes
- [#&#8203;32506](numpy/numpy#32506): DOC: use static scipy doc site for intershpinx ([#&#8203;32503](numpy/numpy#32503))
- [#&#8203;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 ([#&#8203;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 [@&#8203;Viicos](https://github.com/Viicos) in [#&#8203;13535](pydantic/pydantic#13535)
- Fix missing GC traversal on some `pydantic-core` struct fields by [@&#8203;Viicos](https://github.com/Viicos) in [#&#8203;13624](pydantic/pydantic#13624)
- Fix missing GC traversal in `pydantic-core` for `GeneralFieldsSerializer` by [@&#8203;Viicos](https://github.com/Viicos) in [#&#8203;13629](pydantic/pydantic#13629)
- Count validated model fields once in smart unions by [@&#8203;tamird](https://github.com/tamird) in [#&#8203;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` ([#&#8203;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 [@&#8203;adtyavrdhn](https://github.com/adtyavrdhn) in [#&#8203;6601](pydantic/pydantic-ai#6601)
- Fix `XaiStreamedResponse` finish\_reason mapping for streaming responses by [@&#8203;pydanty](https://github.com/pydanty)\[bot] in [#&#8203;6814](pydantic/pydantic-ai#6814)
- Point offline web UI hosting at the self-contained chat UI build by [@&#8203;dsfaccini](https://github.com/dsfaccini) in [#&#8203;7349](pydantic/pydantic-ai#7349)
- Allow adaptive thinking with Tool Output and forced tool choice on Anthropic by [@&#8203;pydanty](https://github.com/pydanty)\[bot] in [#&#8203;7200](pydantic/pydantic-ai#7200)
- Gate `RetryPromptPart` OpenTelemetry content on `include_content` by [@&#8203;sean-kim05](https://github.com/sean-kim05) in [#&#8203;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` ([#&#8203;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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

anthropic bug Report that something isn't working, or PR implementing a fix size: L Large PR (501-1500 weighted lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Anthropic adaptive thinking incorrectly disables Tool Output mode

1 participant