Split `models/anthropic.py` into a `models/anthropic/` package

Merged
DS
8d ago
+2265-1975

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

Part of #10983.

models/anthropic.py had grown to over 5,000 lines, most of them module-level helpers that a reader of AnthropicModel doesn't need. This turns it into a models/anthropic/ package: __init__.py keeps AnthropicModel, AnthropicModelSettings, AnthropicCompaction, the client-class gates and the public API, and each other concern gets its own submodule. Pure refactor: request bodies are byte-identical and the public API is unchanged.

models/anthropic/
├─ __init__.py               AnthropicModel, settings, AnthropicCompaction, __all__
├─ _streaming.py             AnthropicStreamedResponse
├─ _native_tools.py          native tool blocks, request and response
├─ _response.py              usage, finish reasons, compaction details, input transformations
├─ _citations.py             citation requests, response citations, citation replay
├─ _thinking_binding.py      stale thinking block retry, AnthropicStaleThinkingBlockWarning
├─ _message_passes.py        passes over the whole mapped message list
├─ _cache_control.py         cache_control breakpoints
├─ _errors.py                API error mapping
├─ _containers.py            was models/_anthropic_containers.py
└─ _bedrock_count_tokens.py  was models/_anthropic_bedrock_count_tokens.py

New public surface: none. __init__.py gains an __all__ listing the names the module already exposed: the API docs render a re-exported name only when __all__ lists it.

What changes for existing users: nothing through the public API. Private helpers moved into submodules and lost their leading underscore, so code that imported one, such as pydantic_ai.models.anthropic._map_usage, now imports it from its submodule.

models/AGENTS.md now says when and how a provider module becomes a package, models/anthropic/AGENTS.md holds the Anthropic-only rules, and the precedents in agent_docs/provider-adapters.md point at the new paths.

Verification

  • Byte identity: a pytest plugin recorded every outgoing request body per test node, on main (8d781a4649) and on this branch, across the 113 test files that reference the Anthropic model: 1,691 nodes, 792 of them sending an Anthropic request. The one Anthropic difference is a test's random plan id (uuid4().hex[:8]). The other differing nodes send no Anthropic request (random choices, retry timing, cross-test fixture state).
  • Coverage: every file in models/anthropic/ at 100% line and branch on the split before the main merges below; CI's coverage gate covers the current head.
  • main merges: main changed anthropic.py four times while this was open. For #7434 and #7752 the merge regenerates the package with the same script from main's file; on the pre-citations source that script reproduces the reviewed split. #11036 and #11091 only touched code that stays in __init__.py, so git carried them over, and the merge's diff to __init__.py matches theirs line for line.
  • API compatibility: griffecli check pydantic_ai --against v2.60.0 reports nothing for pydantic_ai.models.anthropic.
PR decisions

2026-10-23 · Follow-up split: models/anthropic/ package

  • Decision: Split models/anthropic.py into a models/anthropic/ package as a follow-up to #11116; AnthropicModel stays one class, no mixins.
  • Why: the module-level helpers form concerns a reader of AnthropicModel can skip, and a package can carry its own AGENTS.md.

2026-10-23 · Docs rule in the split PR

  • Decision: The models/AGENTS.md package rule and models/anthropic/AGENTS.md land in this PR.
  • Why: the layout this PR introduces is the one the rule describes.

2026-10-23 · Package submodules by concern

  • Decision: __init__.py keeps AnthropicModel, settings, AnthropicCompaction and the client gates; _errors, _thinking_binding, _response, _streaming, _citations, _native_tools, _cache_control, _message_passes, _containers and _bedrock_count_tokens hold the rest.
  • Why: each submodule is one concern; none imports __init__.py at runtime, so the import graph stays acyclic.

2026-10-23 · No underscore on functions in models/anthropic/_*.py

  • Decision: Every function in a _*.py submodule is public-named; module constants internal to one submodule keep the underscore.
  • Why: the module name already marks a function private; constants follow agent_docs/index.md ("prefix with _ if internal").

2026-10-23 · __all__ on the package, *_ta TypeAdapters kept importable

  • Decision: __all__ lists the nine documented names plus the five *_tool_result_content_ta TypeAdapters, which moved to _native_tools.
  • Why: unified-docs renders an imported member only when __all__ lists it; the TypeAdapters were public-named module attributes.

2026-10-23 · Port #7434 citations by regenerating the split

  • Decision: Merged main and rebuilt models/anthropic/ by rerunning the scripted split on main's anthropic.py; the citation helpers go to _citations.py.
  • Why: on the pre-citations source the script reproduces the reviewed package, so its delta is exactly the port.

2026-10-23 · Port #7752 citation replay the same way

  • Decision: citations_fit_text and map_citation_for_replay go to _citations.py; the AnthropicModel hunk lands in __init__.py.
  • Why: same script, same rule: a submodule function has no underscore.

2026-10-23 · Two citation helpers renamed beyond the underscore

  • Decision: _include_citations became citations_included and _history_fetched_pages became fetched_pages_from_history.
  • Why: dropping only the underscore would shadow same-named parameters.

2026-10-23 · No CLAUDE.md symlink in models/anthropic/

  • Decision: models/anthropic/ has only AGENTS.md, and the models/AGENTS.md package rule no longer asks for a symlink.
  • Why: Claude Code loads nested AGENTS.md files on its own.

Checklist

  • No breaking changes in accordance with the version policy.
  • Any permitted compatibility impact has a label, warning, migration, release note, and exact API-check waiver.
  • PR title is fit for the release changelog.