Split `models/anthropic.py` into a `models/anthropic/` package
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 themainmerges below; CI's coverage gate covers the current head. mainmerges:mainchangedanthropic.pyfour times while this was open. For #7434 and #7752 the merge regenerates the package with the same script frommain'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__.pymatches theirs line for line.- API compatibility:
griffecli check pydantic_ai --against v2.60.0reports nothing forpydantic_ai.models.anthropic.
PR decisions
2026-10-23 · Follow-up split: models/anthropic/ package
- Decision: Split
models/anthropic.pyinto amodels/anthropic/package as a follow-up to #11116;AnthropicModelstays one class, no mixins. - Why: the module-level helpers form concerns a reader of
AnthropicModelcan skip, and a package can carry its ownAGENTS.md.
2026-10-23 · Docs rule in the split PR
- Decision: The
models/AGENTS.mdpackage rule andmodels/anthropic/AGENTS.mdland in this PR. - Why: the layout this PR introduces is the one the rule describes.
2026-10-23 · Package submodules by concern
- Decision:
__init__.pykeepsAnthropicModel, settings,AnthropicCompactionand the client gates;_errors,_thinking_binding,_response,_streaming,_citations,_native_tools,_cache_control,_message_passes,_containersand_bedrock_count_tokenshold the rest. - Why: each submodule is one concern; none imports
__init__.pyat runtime, so the import graph stays acyclic.
2026-10-23 · No underscore on functions in models/anthropic/_*.py
- Decision: Every function in a
_*.pysubmodule 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_taTypeAdapters, 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
mainand rebuiltmodels/anthropic/by rerunning the scripted split onmain'santhropic.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_textandmap_citation_for_replaygo to_citations.py; theAnthropicModelhunk 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_citationsbecamecitations_includedand_history_fetched_pagesbecamefetched_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 onlyAGENTS.md, and themodels/AGENTS.mdpackage rule no longer asks for a symlink. - Why: Claude Code loads nested
AGENTS.mdfiles 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.