mirror of
https://github.com/pydantic/pydantic-ai.git
synced 2026-10-11 21:59:51 +00:00
Add support for claude-haiku-5-5 (#9998)
This commit is contained in:
@@ -58,6 +58,7 @@ For every variable, tuple, and literal you're about to touch, grep its readers a
|
||||
Specifically, for a typical model add, grep for:
|
||||
|
||||
- The previous model id literal you're mirroring (e.g. `gpt-5.4`, `claude-opus-4-5`) — `rg '<prev-id>' --glob '!**/*.yaml' --glob '!**/cassettes/**'`
|
||||
- Search for the previous model's display name in docs and public docstrings; inspect capability rosters for the same features.
|
||||
- Every prefix/membership key in the profile module you're editing (e.g. OpenAI's `_REASONING_SUPPORT_BY_PREFIX` keys, Anthropic's inline `model_name.startswith((...))` tuples, xAI's `_GROK_43_REASONING_MODELS`)
|
||||
- `KnownModelName` and its provider-block neighbours
|
||||
- Snapshot test files: `tests/models/test_model_names.py`, `tests/test_capability_spec.py`
|
||||
@@ -71,12 +72,16 @@ If `rg` output looks mangled (unicode/regex artifacts), drop to `grep -n` — do
|
||||
|
||||
## Step 3b — Pair the genai-prices entry
|
||||
|
||||
Cost and `context_window` do not live in this repo. Both come from `pydantic/genai-prices` through
|
||||
`_genai_prices.py`, and `Model.profile` only consults it when nothing else set `context_window`, so a
|
||||
new id has neither until genai-prices ships an entry and this repo's lock picks up that release.
|
||||
Until then, for that id: `ModelResponse.cost()` raises `LookupError`, `RunContext.context_window_used`
|
||||
is `None`, and a `cost_limit` cannot be enforced — the run warns `CostNotFoundWarning` at the end
|
||||
instead. Open the genai-prices PR alongside the model add and link the two.
|
||||
Cost and `context_window` data come from `pydantic/genai-prices` through `_genai_prices.py`.
|
||||
Bundled data requires a `genai-prices` release.
|
||||
Update the Pydantic AI lock to verify the bundled entry locally.
|
||||
The live merged feed can supply entries through `pydantic_ai.prices.update_in_background()`.
|
||||
Check the updater for an entry before claiming a package release blocks support.
|
||||
`Model.profile` only consults genai-prices when nothing else set `context_window`.
|
||||
Without a price entry in either source, `ModelResponse.cost()` raises `LookupError` and a
|
||||
`cost_limit` cannot be enforced — the run warns `CostNotFoundWarning` at the end instead.
|
||||
Without a `context_window` value from any source, `RunContext.context_window_used` is `None`.
|
||||
If no genai-prices entry exists, open the genai-prices PR alongside the model add and link the two.
|
||||
|
||||
Before you write the entry, check that no one has added it already. Someone else may have added
|
||||
it on release day. Grep genai-prices `main` for each provider file you plan to edit, then the
|
||||
@@ -159,6 +164,9 @@ The gateway reaches the canonical API through an ordinary SDK client carrying a
|
||||
- **Probe the gateway leg rather than reasoning about it.** `Model('<id>', provider='gateway')`, then
|
||||
exercise whatever capability you gated. If `PYDANTIC_AI_GATEWAY_BASE_URL` is set in the environment,
|
||||
check it points at the gateway root: a provider-specific proxy path 404s every other provider.
|
||||
- **Probe Bedrock Gateway profile regions individually.** A successful `gateway/bedrock:` inference-profile ID
|
||||
does not establish support for another region prefix or the bare ID. Probe each candidate; exclude only IDs
|
||||
Gateway rejects.
|
||||
|
||||
A model the gateway genuinely does not serve is the other case entirely: it belongs in
|
||||
`UNSUPPORTED_GATEWAY_MODEL_NAMES`, on evidence that the gateway rejects the id. Never leave the id
|
||||
@@ -231,7 +239,7 @@ check. Keep the model-specific evidence concise:
|
||||
- **Capability flags live as `startswith` prefix tuples in `profiles/anthropic.py`** inside `anthropic_model_profile()` (+ the module-level `_ANTHROPIC_CODE_EXECUTION_20260120_MODEL_PREFIXES`). A new family is NOT a literal-only add — it almost always needs at least one profile override (a literal-only add is only right when the family truly inherits every default branch, which is rare). Probe and set each independently: `models_that_support_json_schema_output`, `supports_adaptive`, `supports_effort`, `supports_xhigh_effort`, `disallows_budget_thinking`, `disallows_sampling_settings`, `supports_task_budgets`, `supports_tool_search`, code-exec version, `anthropic_supports_fast_speed`. Default-`False` flags (e.g. fast speed) are subtractive — just omit the id from that tuple.
|
||||
- **A point release inherits every flag of its base id silently.** The tuples are `startswith` prefixes, so `'claude-opus-5'` already matches `claude-opus-5-5` (as `'claude-fable-5'` matches `claude-fable-5-1`): before you touch anything, the new id resolves to the base model's profile. Tests stay green and nothing warns, so the only way to find a divergence is to read the model's migration guide and probe side by side with the base id. Opus 5.5 looked like an Opus 5 mirror and broke default `output_type` runs with a 400 until it opted out of forcing. Where a flag must *not* carry over, carve the id out explicitly (`startswith('claude-opus-5') and not startswith('claude-opus-5-5')`).
|
||||
- **Read the migration guide's "breaking changes" before probing.** Anthropic's `platform.claude.com/docs/en/models/<id>/migration-guide` and `whats-new-<id>` pages list every divergence from the previous model and name which other models share it (e.g. "the first three also apply on Claude Fable 5.1"). Those map straight onto profile flags, and they tell you what to probe.
|
||||
- **Bump the SDK through the 7-day quarantine rather than bridging, once the SDK lists the id.** `exclude-newer = "7 days"` in the root `pyproject.toml` keeps a same-day `anthropic` release out of the lock; admit exactly that release with a timestamp cutoff under `[tool.uv.exclude-newer-package]` (one second past its last artifact's PyPI `upload_time`, plus a `TODO` to remove it once the global window covers it — past that date it turns into a ceiling), raise the floor in `pydantic_ai_slim/pyproject.toml`, run `uv lock --upgrade-package anthropic`, and refresh the gh-aw runner's own lock with `uv lock --script .github/scripts/pydantic-ai-runner` (no CI step checks it, so a stale one stays green while silently dropping its pinned hashes). File a tracking issue for removing the cutoff and link it from the `TODO`. Precedents: #7989 (1.3.0), #8637 (1.8.0). The local-`Literal` bridge below is the fallback for when no SDK release lists the id yet.
|
||||
- **Bump the SDK through the 7-day quarantine rather than bridging, once the SDK lists the id.** `exclude-newer = "7 days"` in the root `pyproject.toml` keeps a same-day `anthropic` release out of the lock; admit exactly that release with a timestamp cutoff under `[tool.uv.exclude-newer-package]` (one second past its last artifact's PyPI `upload_time`, plus a `TODO` to remove it once the global window covers it — past that date it turns into a ceiling), raise the floor under `tool.hatch.metadata.hooks.uv-dynamic-versioning.optional-dependencies` in `pydantic_ai_slim/pyproject.toml` (move the exact `uv add --optional`-generated requirement there and remove its conflicting `[project.optional-dependencies]` table), run `uv lock --upgrade-package anthropic`, and refresh the gh-aw runner's own lock with `uv lock --script .github/scripts/pydantic-ai-runner` (no CI step checks it, so a stale one stays green while silently dropping its pinned hashes). File a tracking issue for removing the cutoff and link it from the `TODO`. Precedents: #7989 (1.3.0), #8637 (1.8.0). The local-`Literal` bridge below is the fallback for when no SDK release lists the id yet.
|
||||
- **An SDK bump is a real change: pyright `models/anthropic.py` and the Anthropic tests against it.** 1.8.0 renamed the citations request TypedDict to `BetaCitationsConfigParamParam` (`BetaCitationsConfigParam` became a response model, and passing it into a request param broke a dict assertion) and widened `BetaInputTransformation` to a union with `thinking_mismatch_allowed`.
|
||||
- **Opus 5.5 skips thinking on trivial prompts at its default `medium` effort.** A cassette test that needs a thinking block (e.g. `stale_thinking_block_history`) has to raise `anthropic_effort` for it.
|
||||
- **A point release falls into its base model's price entry.** The base entries' prefix and `contains` clauses (`starts_with: claude-opus-5`) also capture `claude-opus-5-5`. So until the new entry exists, `calc_price` returns the *old* model's price with no error: genai-prices 0.1.7 priced Opus 5.5 at Opus 5's $5/$25 instead of $4/$20. Check `calc_price(..., model_ref='<new-id>')` against the published price. The genai-prices PR has to narrow the base entry's clauses so they stop at the base model, keeping every form they matched before and pinning those forms with a positive test (genai-prices #671, #709), as well as add the new entry per Step 3b.
|
||||
|
||||
@@ -28,7 +28,7 @@ pydantic-monty-client = false
|
||||
genai-prices = false
|
||||
pydantic-monty = false
|
||||
termflow-md = "2026-10-02T15:05:25Z"
|
||||
anthropic = "2026-09-28T18:04:04Z"
|
||||
anthropic = "2026-10-08T03:42:01.588842Z"
|
||||
openai = "2026-10-06T21:31:20Z"
|
||||
google-genai = "2026-09-22T17:23:02Z"
|
||||
|
||||
@@ -63,7 +63,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "anthropic"
|
||||
version = "1.9.0"
|
||||
version = "1.12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
@@ -74,9 +74,9 @@ dependencies = [
|
||||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/0a/1ab3bf53a672fd9dd42d3208e335940049431b79781b2a9b324203d30c38/anthropic-1.9.0.tar.gz", hash = "sha256:da5191a0af0b853a797fc0c6b09fc5b05cb5d9da063ba80fdd03c736abe81b6c", size = 1243092, upload-time = "2026-09-28T18:04:03.005Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bd/cc/0da23e94020c62c0077c17648252f32d575b187710faaf3b85145e8e555d/anthropic-1.12.1.tar.gz", hash = "sha256:10f720ab6ddf40ef12e0e5743412c3a6dfac1109c244e633c9a2dbd36778ad30", size = 1555908, upload-time = "2026-10-08T03:42:00.588Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/15/2a/67c1b2ddf91be3112856ed74b96d1576f00dd423e4f20bb251aa144fbd6b/anthropic-1.9.0-py3-none-any.whl", hash = "sha256:a7fe05a9e4b72113b934501698df5d56484ac89f05677f0f793f8bfddb15cfff", size = 1360651, upload-time = "2026-09-28T18:04:01.452Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/35/1ccdc3e950dd869b66de15035e15d9614607fcef9664dec403fe639f01df/anthropic-1.12.1-py3-none-any.whl", hash = "sha256:1c1469bb3f4118291756816b7850cc839f17b2be5cdc99c8fde3890a61a49ca2", size = 1919752, upload-time = "2026-10-08T03:41:58.596Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1211,7 +1211,7 @@ retries = [
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "ag-ui-protocol", marker = "extra == 'ag-ui'", specifier = ">=0.1.10,<1" },
|
||||
{ name = "anthropic", marker = "extra == 'anthropic'", specifier = ">=1.9.0" },
|
||||
{ name = "anthropic", marker = "extra == 'anthropic'", specifier = ">=1.12.1" },
|
||||
{ name = "anyio", specifier = ">=4.7.0" },
|
||||
{ name = "argcomplete", marker = "extra == 'cli'", specifier = ">=3.5.0" },
|
||||
{ name = "boto3", marker = "extra == 'bedrock'", specifier = ">=1.42.63" },
|
||||
|
||||
@@ -44,7 +44,7 @@ The `Thinking` capability maps each effort value to the selected provider's nati
|
||||
|
||||
| Provider | `Thinking()` / `Thinking(effort=True)` | `Thinking(effort='high')` | Notes |
|
||||
|---|---|---|---|
|
||||
| Anthropic (Opus 4.6+) | `anthropic_thinking={'type': 'adaptive'}` | `{type: 'adaptive'}` + `effort='high'` | Claude Opus 4.7, 4.8, 5, 5.5, and Sonnet 5 and 5.5 also support `effort='xhigh'` |
|
||||
| Anthropic (adaptive thinking) | `anthropic_thinking={'type': 'adaptive'}` | `{type: 'adaptive'}` + `effort='high'` | Claude Opus 4.7, 4.8, 5, 5.5, Sonnet 5 and 5.5, and Haiku 5.5 also support `effort='xhigh'` |
|
||||
| Anthropic (older) | `anthropic_thinking={'type': 'enabled', 'budget_tokens': 10000}` | `budget_tokens=16384` | Budget-based; `'low'` → 2048 tokens |
|
||||
| OpenAI | `reasoning_effort='medium'` | `reasoning_effort='high'` | GPT-5.6 maps unified `'minimal'` to `'low'` |
|
||||
| Google (Gemini 3+) | `include_thoughts=True` | `thinking_level='HIGH'` | Unified efforts snap to the nearest documented level — e.g. `gemini-3.1-flash-lite-image` (levels: `minimal`, `high`) maps `'low'` to `'MINIMAL'` and `'medium'`/`'xhigh'` to `'HIGH'` — and models without `minimal` map unified `'minimal'` to `'LOW'`; `gemini-3.1-flash-image` applies its `minimal, high` set only on the Gemini API (see [Google](../models/google.md#configure-thinking)) |
|
||||
@@ -59,7 +59,7 @@ The `Thinking` capability maps each effort value to the selected provider's nati
|
||||
| Ollama | `reasoning_effort='medium'` | `reasoning_effort='high'` | Inherited from `OpenAIChatModel`, so it follows the resolved model profile: `deepseek-r1` reasons, `gpt-oss` on Ollama sends nothing. `thinking=False` → `'none'` only on profiles that accept it |
|
||||
| Z.AI | `thinking={'type': 'enabled'}` | `thinking={'type': 'enabled'}`, plus `reasoning_effort='high'` on GLM-5.2 and GLM-5.3 | Via `extra_body`; `thinking=False` → `type='disabled'`. GLM-5.3 always reasons and ignores `thinking=False` (dropped rather than sent as `type='disabled'`) and accepts only `low`/`high`/`max` (per Z.AI's docs and the error message returned when disabling thinking on it), mapping the other unified levels to the nearest one |
|
||||
| xAI | `reasoning_effort` omitted on Grok 4.3 (uses its default) | `reasoning_effort='high'` | Grok 4.3 supports `'none'`, `'low'`, `'medium'`, and `'high'`, and `thinking=True` omits the parameter so the model applies its own default; Grok 3 Mini only supports `'low'` and `'high'` (so `thinking=True` → `'high'`) and silently ignores `thinking=False`; Grok 4.5 supports `'low'`, `'medium'`, and `'high'` but not `'none'`, so it reasons always-on (`thinking=True` → `'medium'`) and silently ignores `thinking=False` |
|
||||
| Bedrock (Claude 4.6+) | `thinking.type='adaptive'` | `{type: 'adaptive'}` + `output_config.effort='high'` | Effort lives in the sibling `output_config` field per AWS docs; `xhigh` passes through on the models whose profile supports it (the same ones as the direct Anthropic API, e.g. Opus 4.7+ and Sonnet 5) and maps to `max` on the others (Opus 4.6 and Sonnet 4.6 reject `xhigh`) |
|
||||
| Bedrock (Claude 4.6+ and Haiku 5.5) | `thinking.type='adaptive'` | `{type: 'adaptive'}` + `output_config.effort='high'` | Effort lives in the sibling `output_config` field per AWS docs; `xhigh` passes through on the models whose profile supports it (the same ones as the direct Anthropic API, e.g. Opus 4.7+, Sonnet 5, and Haiku 5.5) and maps to `max` on the others (Opus 4.6 and Sonnet 4.6 reject `xhigh`) |
|
||||
| Bedrock (Claude older) | `thinking.type='enabled'` | `budget_tokens=16384` | Budget-based |
|
||||
| Bedrock (OpenAI GPT-OSS) | `reasoning_effort='medium'` | `reasoning_effort='high'` | Converse rejects `'none'`; `thinking=False` silently ignored |
|
||||
| Bedrock (OpenAI GPT-5.6 and GPT-6) | Not mapped | Not mapped | No verified unified-thinking mapping on Converse |
|
||||
@@ -106,7 +106,7 @@ agent = Agent(model, model_settings=settings)
|
||||
To enable thinking, use the [`AnthropicModelSettings.anthropic_thinking`][pydantic_ai.models.anthropic.AnthropicModelSettings.anthropic_thinking] [model setting](../agent.md#model-run-settings).
|
||||
|
||||
!!! note
|
||||
Extended thinking (`type: 'enabled'` with `budget_tokens`) is deprecated on `claude-opus-4-6` and removed on `claude-opus-4-7`, `claude-opus-4-8`, `claude-opus-5`, `claude-opus-5-5`, `claude-sonnet-5`, and `claude-sonnet-5-5`. For those models, use [adaptive thinking](#adaptive-thinking-effort) instead.
|
||||
Extended thinking (`type: 'enabled'` with `budget_tokens`) is deprecated on `claude-opus-4-6` and removed on `claude-opus-4-7`, `claude-opus-4-8`, `claude-opus-5`, `claude-opus-5-5`, `claude-sonnet-5`, `claude-sonnet-5-5`, and `claude-haiku-5-5`. For those models, use [adaptive thinking](#adaptive-thinking-effort) instead.
|
||||
|
||||
```python {title="anthropic_thinking_part.py"}
|
||||
from pydantic_ai import Agent
|
||||
@@ -143,12 +143,12 @@ agent = Agent(model, model_settings=settings)
|
||||
|
||||
### Adaptive Thinking & Effort {#adaptive-thinking-effort}
|
||||
|
||||
Starting with `claude-opus-4-6`, Anthropic supports [adaptive thinking](https://docs.anthropic.com/en/docs/build-with-claude/adaptive-thinking), where the model dynamically decides when and how much to think based on the complexity of each request. This replaces extended thinking (`type: 'enabled'` with `budget_tokens`) which is deprecated on Opus 4.6 and removed on Opus 4.7, 4.8, 5, 5.5, and Sonnet 5 and 5.5. Claude Opus 4.7, 4.8, 5, 5.5, and Sonnet 5 and 5.5 also add the `xhigh` effort level. Adaptive thinking also automatically enables interleaved thinking.
|
||||
Starting with `claude-opus-4-6`, Anthropic supports [adaptive thinking](https://docs.anthropic.com/en/docs/build-with-claude/adaptive-thinking), where the model dynamically decides when and how much to think based on the complexity of each request. This replaces extended thinking (`type: 'enabled'` with `budget_tokens`) which is deprecated on Opus 4.6 and removed on Opus 4.7, 4.8, 5, 5.5, Sonnet 5 and 5.5, and Haiku 5.5. Claude Opus 4.7, 4.8, 5, 5.5, Sonnet 5 and 5.5, and Haiku 5.5 also add the `xhigh` effort level. Adaptive thinking also automatically enables interleaved thinking.
|
||||
|
||||
Claude Opus 5 and later, Claude Sonnet 5, and the Claude Fable and Mythos models think adaptively without any thinking setting. On Claude Opus 5 and Sonnet 5, the unified `thinking=False` setting turns that off by sending `anthropic_thinking={'type': 'disabled'}`; the others can't turn thinking off, so `thinking=False` is ignored there. Because a forced tool choice stops Claude from thinking, a structured `output_type` on these models uses Native Output rather than Tool Output unless thinking is off; see [Forced tool choice](../models/anthropic.md#forced-tool-choice).
|
||||
Claude Opus 5 and later, Claude Sonnet 5, Haiku 5.5, and the Claude Fable and Mythos models think adaptively without any thinking setting. On Claude Opus 5, Sonnet 5, and Haiku 5.5, the unified `thinking=False` setting turns that off by sending `anthropic_thinking={'type': 'disabled'}`; the others can't turn thinking off, so `thinking=False` is ignored there. Because a forced tool choice stops Claude from thinking, a structured `output_type` on these models uses Native Output rather than Tool Output unless thinking is off; see [Forced tool choice](../models/anthropic.md#forced-tool-choice).
|
||||
|
||||
!!! note "Claude Opus 5 caps effort when thinking is disabled"
|
||||
Claude Opus 5 rejects `xhigh` and `max` effort while thinking is disabled with `thinking=False` or `anthropic_thinking={'type': 'disabled'}`; use an effort of `high` or below, or leave thinking enabled. Claude Opus 4.8 accepts that combination, so audit requests that disable thinking when migrating. Pydantic AI raises a `UserError` before sending the request rather than surfacing Anthropic's 400.
|
||||
!!! note "Claude Opus 5 and Haiku 5.5 cap effort when thinking is disabled"
|
||||
Claude Opus 5 and Haiku 5.5 reject `xhigh` and `max` effort while thinking is disabled with `thinking=False` or `anthropic_thinking={'type': 'disabled'}`; use an effort of `high` or below, or leave thinking enabled. Claude Opus 4.8 accepts that combination, so audit requests that disable thinking when migrating. Pydantic AI raises a `UserError` before sending the request rather than surfacing Anthropic's 400.
|
||||
|
||||
!!! note "Claude Opus 5.5 always thinks"
|
||||
Claude Opus 5.5 can't disable thinking: Anthropic rejects `anthropic_thinking={'type': 'disabled'}` at every effort level. The unified `thinking=False` setting is ignored, so the model thinks adaptively at its default `medium` effort. Where you previously disabled thinking, lower `anthropic_effort` instead (for example `anthropic_effort='low'`).
|
||||
@@ -212,7 +212,7 @@ agent = Agent(model, model_settings=settings)
|
||||
|
||||
## Bedrock
|
||||
|
||||
For Claude Sonnet 4.6+ and Opus 4.6+, Pydantic AI's unified `thinking` setting translates to AWS's required [adaptive thinking](https://docs.aws.amazon.com/bedrock/latest/userguide/claude-messages-adaptive-thinking.html) shape automatically — set [`ModelSettings.thinking`][pydantic_ai.settings.ModelSettings.thinking] and you're done.
|
||||
For Claude Sonnet 4.6+, Opus 4.6+, and Haiku 5.5, Pydantic AI's unified `thinking` setting translates to AWS's required [adaptive thinking](https://docs.aws.amazon.com/bedrock/latest/userguide/claude-messages-adaptive-thinking.html) shape automatically — set [`ModelSettings.thinking`][pydantic_ai.settings.ModelSettings.thinking] and you're done.
|
||||
|
||||
For older Claude models or to pin a specific `budget_tokens`, you can still use [`BedrockModelSettings.bedrock_additional_model_requests_fields`][pydantic_ai.models.bedrock.BedrockModelSettings.bedrock_additional_model_requests_fields] [model setting](../agent.md#model-run-settings) to pass provider-specific configuration directly:
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ agent = Agent(model)
|
||||
```
|
||||
|
||||
!!! note "Claude Opus 4.7 / 4.8 / 5 / 5.5 migration"
|
||||
Anthropic's [Claude Opus migration guide](https://platform.claude.com/docs/en/about-claude/models/migration-guide) recommends removing `temperature`, `top_p`, and `top_k` from Opus 4.7, 4.8, 5, and 5.5 requests. Pydantic AI drops those keys automatically for `claude-opus-4-7`, `claude-opus-4-8`, `claude-opus-5`, `claude-opus-5-5`, `claude-sonnet-5`, `claude-sonnet-5-5`, `claude-fable-5` and `claude-mythos-5`, including `extra_body` overrides.
|
||||
Anthropic's [Claude Opus migration guide](https://platform.claude.com/docs/en/about-claude/models/migration-guide) recommends removing `temperature`, `top_p`, and `top_k` from Opus 4.7, 4.8, 5, and 5.5 requests. Pydantic AI drops those keys automatically for `claude-opus-4-7`, `claude-opus-4-8`, `claude-opus-5`, `claude-opus-5-5`, `claude-sonnet-5`, `claude-sonnet-5-5`, `claude-haiku-5-5`, `claude-fable-5` and `claude-mythos-5`, including `extra_body` overrides.
|
||||
|
||||
The same guide also recommends re-evaluating `max_tokens` and any token-count assumptions when migrating from Opus 4.6, since Opus 4.7 introduced updated tokenization (carried into 4.8). If you rely on `count_tokens()` or `count_tokens_before_request`, verify your thresholds against the new model.
|
||||
|
||||
@@ -110,7 +110,7 @@ agent = Agent(model, model_settings=settings)
|
||||
...
|
||||
```
|
||||
|
||||
Anthropic requires [`max_tokens`][pydantic_ai.settings.ModelSettings.max_tokens], which thinking counts toward. When you don't set it, Pydantic AI sends the model's maximum output, like 64,000 on Claude Sonnet 4.5 or 128,000 on Claude Opus 5, and streams the request behind the scenes, since a response that long can take more than 10 minutes. A [`timeout`][pydantic_ai.settings.ModelSettings.timeout] then limits the wait between streamed chunks rather than the whole response. A model whose maximum isn't known gets 16384, and models older than Claude Sonnet 4.5 get 4096, since they reject a request whose input plus `max_tokens` exceeds the context window. Models are recognized by name, so set `max_tokens` yourself if you reach one through a Bedrock ARN or a custom deployment name.
|
||||
Anthropic requires [`max_tokens`][pydantic_ai.settings.ModelSettings.max_tokens], which thinking counts toward. When you don't set it, Pydantic AI sends the model's maximum output, like 64,000 on Claude Sonnet 4.5 or 128,000 on Claude Opus 5 and Claude Haiku 5.5, and streams the request behind the scenes, since a response that long can take more than 10 minutes. A [`timeout`][pydantic_ai.settings.ModelSettings.timeout] then limits the wait between streamed chunks rather than the whole response. A model whose maximum isn't known gets 16384, and models older than Claude Sonnet 4.5 get 4096, since they reject a request whose input plus `max_tokens` exceeds the context window. Models are recognized by name, so set `max_tokens` yourself if you reach one through a Bedrock ARN or a custom deployment name.
|
||||
|
||||
### Service tier
|
||||
|
||||
@@ -199,7 +199,7 @@ See [Anthropic's Microsoft Foundry documentation](https://platform.claude.com/do
|
||||
|
||||
Anthropic's [task budgets](https://platform.claude.com/docs/en/build-with-claude/task-budgets) let you give Claude an advisory token budget for a full agentic loop — including thinking, tool calls, tool results, and output — so the model can pace itself and finish gracefully as the budget is consumed. Configure them with [`AnthropicModelSettings.anthropic_task_budget`][pydantic_ai.models.anthropic.AnthropicModelSettings.anthropic_task_budget], which takes an [`AnthropicTaskBudget`][pydantic_ai.models.anthropic.AnthropicTaskBudget] payload and maps to `output_config.task_budget`.
|
||||
|
||||
Pydantic AI automatically enables Anthropic's required `task-budgets-2026-03-13` beta when this setting is present. Support is currently limited to native Anthropic `claude-fable-5`, `claude-fable-5-1`, `claude-mythos-5`, `claude-mythos-5-1`, `claude-opus-4-7`, `claude-opus-4-8`, `claude-opus-5`, `claude-opus-5-5`, `claude-sonnet-5`, and `claude-sonnet-5-5` requests, not Bedrock, Vertex, or Microsoft Foundry Anthropic model IDs.
|
||||
Pydantic AI automatically enables Anthropic's required `task-budgets-2026-03-13` beta when this setting is present. See Anthropic's [task budget feature support](https://platform.claude.com/docs/en/build-with-claude/task-budgets#feature-support) for the current supported-model list. Task budgets are not currently documented as supported on Bedrock or Vertex. For Microsoft Foundry, check Microsoft's [Claude model capability list](https://learn.microsoft.com/en-us/azure/foundry/foundry-models/concepts/claude-models#available-claude-models).
|
||||
|
||||
```python {title="anthropic_task_budget.py"}
|
||||
from pydantic_ai import Agent
|
||||
@@ -609,18 +609,18 @@ On a model that doesn't support forcing, or while extended thinking is enabled:
|
||||
|
||||
While adaptive thinking is on, an explicit forcing `tool_choice` is still sent, and that request comes back without thinking. A forced choice that Pydantic AI resolved on your behalf falls back to `'auto'` instead, as above, so the model keeps thinking.
|
||||
|
||||
[Tool Output](../output.md#tool-output) forces a call to the output tool on every request that can't end with text, so a bare structured `output_type` uses [Native Output](../output.md#native-output), where the model supports it, whenever the request thinks: with a thinking setting, or with none on a model that [thinks by default](../capabilities/thinking.md#adaptive-thinking-effort), like Claude Opus 5 and later, Claude Sonnet 5, and the Claude Fable and Mythos models. On a model without JSON schema support, extended thinking falls back to [Prompted Output](../output.md#prompted-output), and adaptive thinking keeps Tool Output with the output tool offered under `tool_choice='auto'`. To keep Tool Output, turn thinking off with `thinking=False` where the model allows it. An explicit `ToolOutput(...)` keeps Tool Output, with the output tool offered under `tool_choice='auto'`.
|
||||
[Tool Output](../output.md#tool-output) forces a call to the output tool on every request that can't end with text, so a bare structured `output_type` uses [Native Output](../output.md#native-output), where the model supports it, whenever the request thinks: with a thinking setting, or with none on a model that [thinks by default](../capabilities/thinking.md#adaptive-thinking-effort), like Claude Opus 5 and later, Claude Sonnet 5, Claude Haiku 5.5, and the Claude Fable and Mythos models. On a model without JSON schema support, extended thinking falls back to [Prompted Output](../output.md#prompted-output), and adaptive thinking keeps Tool Output with the output tool offered under `tool_choice='auto'`. To keep Tool Output, turn thinking off with `thinking=False` where the model allows it. An explicit `ToolOutput(...)` keeps Tool Output, with the output tool offered under `tool_choice='auto'`.
|
||||
|
||||
## Thinking block binding
|
||||
|
||||
**Claude Fable 5.1**, **Claude Opus 5.5**, and **Claude Sonnet 5.5** bind each thinking block to the conversation prefix that produced it. Replaying message history after that prefix changes fails with a 400 (`The block is bound to a different conversation`), and two ordinary Pydantic AI features change it:
|
||||
**Claude Fable 5.1**, **Claude Opus 5.5**, **Claude Sonnet 5.5**, and **Claude Haiku 5.5** bind each thinking block to the conversation prefix that produced it. Anthropic [documents a 400](https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting) (`The block is bound to a different conversation`) when replaying history after `system`, `tools`, or earlier `messages` change. Two ordinary Pydantic AI features change the prefix:
|
||||
|
||||
- a [dynamic instructions](../agent.md#instructions) function whose text differs between runs, and
|
||||
- a [filtered toolset](../toolsets.md#filtering-tools) that advertises a new tool mid-conversation, unless the tool uses [deferred loading](../toolsets.md#deferred-loading).
|
||||
|
||||
Both are the same instability that [costs you a provider's prompt cache](../capabilities/caching.md#what-invalidates-a-cache): a request prefix that changes between turns. The thinking block turns it into a 400 you can see; the cache turns it into a bill you can't — every request after the change re-sends the whole conversation at uncached rates, silently. Where the prefix can be held stable, that is worth more than handling the rejection.
|
||||
|
||||
Anthropic enforces the check for accounts created on or after 31 August 2026. For an older account it records the mismatch but acts on it only if the request sets `thinking.block_binding.prefix_mismatch_behavior`.
|
||||
Anthropic enforces the check for accounts created on or after 31 August 2026. For an older account it records the mismatch but returns the error only if the request sets `thinking.block_binding.prefix_mismatch_behavior`.
|
||||
|
||||
**Pydantic AI sets nothing by default**, so an older account keeps replaying its reasoning untouched. Where the check is enforced, the rejected request is retried once with `prefix_mismatch_behavior='drop_block'`: the stale block is dropped, the run continues, and a [`AnthropicStaleThinkingBlockWarning`][pydantic_ai.models.anthropic.AnthropicStaleThinkingBlockWarning] explains what happened. **The model no longer sees that turn's reasoning** — the trade is one turn's thinking against a failed run. Models marked [`anthropic_binds_thinking_blocks=True`][pydantic_ai.profiles.anthropic.AnthropicModelProfile.anthropic_binds_thinking_blocks] are the only ones that retry. A request whose thinking type isn't `adaptive`, such as `anthropic_thinking={'type': 'between_tools'}`, is not retried, because Anthropic accepts `block_binding` only alongside adaptive thinking: keep that conversation's prefix stable instead.
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ When `trace` is set to `'enabled'` in the guardrail configuration (as in the exa
|
||||
Claude answers a forced tool choice without thinking, and manual extended thinking
|
||||
(`bedrock_additional_model_requests_fields={'thinking': {'type': 'enabled', ...}}`) rejects one outright. So while a
|
||||
Claude request thinks, whether because of a thinking setting or because the model thinks by default (Claude Opus 5 and
|
||||
later, Claude Sonnet 5, Claude Fable 5), Pydantic AI doesn't force the output tool:
|
||||
later, Claude Sonnet 5, Claude Haiku 5.5, Claude Fable 5), Pydantic AI doesn't force the output tool:
|
||||
|
||||
- A bare structured `output_type` uses [`NativeOutput`][pydantic_ai.output.NativeOutput] where Bedrock supports it for
|
||||
the model. Otherwise it keeps tool output, with the output tool offered under `toolChoice={'auto': {}}` and a text
|
||||
|
||||
@@ -190,4 +190,4 @@ agent = Agent(model)
|
||||
|
||||
Copilot's Responses (`/responses`) and Messages (`/v1/messages`) APIs and realtime are not implemented. Neither are embeddings, but because `github-copilot` counts as an OpenAI-chat-compatible provider, `Embedder('github-copilot:...')` still builds an [`OpenAIEmbeddingModel`](../embeddings.md) rather than raising — it points at the gateway's `/embeddings`, which answers `400`. Cost and context-window data are also unavailable: [genai-prices](https://github.com/pydantic/genai-prices) gained a `github-copilot` entry in [genai-prices#683](https://github.com/pydantic/genai-prices/pull/683), but no published release carries it yet.
|
||||
|
||||
Copilot's Claude ids inherit Anthropic's sampling restriction. On Opus 4.7, Opus 4.8, Opus 5, Sonnet 5, Fable 5 and Mythos 5 — and on any id whose name starts with one of those — `temperature` and `top_p` are dropped from the request rather than forwarded, silently, exactly as they are when you reach the same models through the [Anthropic API](anthropic.md). Only those two keys go: `top_k` has no Chat Completions equivalent to drop, and a `temperature` you pass in `extra_body`, or any `openai_*` setting, is still sent. Every other id — `claude-haiku-4.5` among them — forwards both unchanged.
|
||||
Copilot's Claude ids inherit Anthropic's sampling restriction. On Opus 4.7, Opus 4.8, Opus 5, Sonnet 5, Fable 5, Mythos 5, and Haiku 5.5 — and on any id whose name starts with one of those — `temperature` and `top_p` are dropped from the request rather than forwarded, silently, exactly as they are when you reach the same models through the [Anthropic API](anthropic.md). Only those two keys go: `top_k` has no Chat Completions equivalent to drop, and a `temperature` you pass in `extra_body`, or any `openai_*` setting, is still sent. Every other id — `claude-haiku-4.5` among them — forwards both unchanged.
|
||||
|
||||
@@ -17,6 +17,7 @@ KnownModelName = TypeAliasType(
|
||||
'anthropic:claude-fable-5-1',
|
||||
'anthropic:claude-haiku-4-5',
|
||||
'anthropic:claude-haiku-4-5-20251001',
|
||||
'anthropic:claude-haiku-5-5',
|
||||
'anthropic:claude-mythos-5',
|
||||
'anthropic:claude-mythos-5-1',
|
||||
'anthropic:claude-mythos-preview',
|
||||
@@ -54,6 +55,7 @@ KnownModelName = TypeAliasType(
|
||||
'bedrock:anthropic.claude-3-opus-20240229-v1:0',
|
||||
'bedrock:anthropic.claude-3-sonnet-20240229-v1:0',
|
||||
'bedrock:anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
'bedrock:anthropic.claude-haiku-5-5',
|
||||
'bedrock:anthropic.claude-instant-v1',
|
||||
'bedrock:anthropic.claude-opus-4-20250514-v1:0',
|
||||
'bedrock:anthropic.claude-sonnet-4-20250514-v1:0',
|
||||
@@ -61,6 +63,7 @@ KnownModelName = TypeAliasType(
|
||||
'bedrock:anthropic.claude-sonnet-4-6',
|
||||
'bedrock:anthropic.claude-v2',
|
||||
'bedrock:anthropic.claude-v2:1',
|
||||
'bedrock:au.anthropic.claude-haiku-5-5',
|
||||
'bedrock:cohere.command-light-text-v14',
|
||||
'bedrock:cohere.command-r-plus-v1:0',
|
||||
'bedrock:cohere.command-r-v1:0',
|
||||
@@ -68,12 +71,14 @@ KnownModelName = TypeAliasType(
|
||||
'bedrock:deepseek.r1-v1:0',
|
||||
'bedrock:deepseek.v3.2',
|
||||
'bedrock:eu.anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
'bedrock:eu.anthropic.claude-haiku-5-5',
|
||||
'bedrock:eu.anthropic.claude-sonnet-4-20250514-v1:0',
|
||||
'bedrock:eu.anthropic.claude-sonnet-4-5-20250929-v1:0',
|
||||
'bedrock:eu.anthropic.claude-sonnet-4-6',
|
||||
'bedrock:global.amazon.nova-2-lite-v1:0',
|
||||
'bedrock:global.anthropic.claude-fable-5',
|
||||
'bedrock:global.anthropic.claude-fable-5-1',
|
||||
'bedrock:global.anthropic.claude-haiku-5-5',
|
||||
'bedrock:global.anthropic.claude-opus-4-5-20251101-v1:0',
|
||||
'bedrock:global.anthropic.claude-opus-4-6-v1',
|
||||
'bedrock:global.anthropic.claude-opus-4-7',
|
||||
@@ -90,6 +95,7 @@ KnownModelName = TypeAliasType(
|
||||
'bedrock:google.gemma-3-4b-it',
|
||||
'bedrock:in.openai.gpt-5.6-luna',
|
||||
'bedrock:in.openai.gpt-5.6-terra',
|
||||
'bedrock:jp.anthropic.claude-haiku-5-5',
|
||||
'bedrock:meta.llama3-1-405b-instruct-v1:0',
|
||||
'bedrock:meta.llama3-1-70b-instruct-v1:0',
|
||||
'bedrock:meta.llama3-1-8b-instruct-v1:0',
|
||||
@@ -136,6 +142,7 @@ KnownModelName = TypeAliasType(
|
||||
'bedrock:us.anthropic.claude-fable-5',
|
||||
'bedrock:us.anthropic.claude-fable-5-1',
|
||||
'bedrock:us.anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
'bedrock:us.anthropic.claude-haiku-5-5',
|
||||
'bedrock:us.anthropic.claude-opus-4-1-20250805-v1:0',
|
||||
'bedrock:us.anthropic.claude-opus-4-20250514-v1:0',
|
||||
'bedrock:us.anthropic.claude-opus-4-5-20251101-v1:0',
|
||||
@@ -198,6 +205,7 @@ KnownModelName = TypeAliasType(
|
||||
'gateway/anthropic:claude-fable-5-1',
|
||||
'gateway/anthropic:claude-haiku-4-5',
|
||||
'gateway/anthropic:claude-haiku-4-5-20251001',
|
||||
'gateway/anthropic:claude-haiku-5-5',
|
||||
'gateway/anthropic:claude-opus-4-5',
|
||||
'gateway/anthropic:claude-opus-4-5-20251101',
|
||||
'gateway/anthropic:claude-opus-4-6',
|
||||
@@ -214,12 +222,14 @@ KnownModelName = TypeAliasType(
|
||||
'gateway/bedrock:deepseek.r1-v1:0',
|
||||
'gateway/bedrock:deepseek.v3.2',
|
||||
'gateway/bedrock:eu.anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
'gateway/bedrock:eu.anthropic.claude-haiku-5-5',
|
||||
'gateway/bedrock:eu.anthropic.claude-sonnet-4-20250514-v1:0',
|
||||
'gateway/bedrock:eu.anthropic.claude-sonnet-4-5-20250929-v1:0',
|
||||
'gateway/bedrock:eu.anthropic.claude-sonnet-4-6',
|
||||
'gateway/bedrock:global.amazon.nova-2-lite-v1:0',
|
||||
'gateway/bedrock:global.anthropic.claude-fable-5',
|
||||
'gateway/bedrock:global.anthropic.claude-fable-5-1',
|
||||
'gateway/bedrock:global.anthropic.claude-haiku-5-5',
|
||||
'gateway/bedrock:global.anthropic.claude-opus-4-5-20251101-v1:0',
|
||||
'gateway/bedrock:global.anthropic.claude-opus-4-6-v1',
|
||||
'gateway/bedrock:global.anthropic.claude-opus-4-7',
|
||||
|
||||
@@ -479,7 +479,7 @@ AnthropicTaskBudget: TypeAlias = BetaTokenTaskBudgetParam
|
||||
class AnthropicStaleThinkingBlockWarning(Warning):
|
||||
"""Warning raised when Anthropic rejected a replayed thinking block and Pydantic AI retried without it.
|
||||
|
||||
Claude Fable 5.1, Claude Opus 5.5, and Claude Sonnet 5.5 bind each thinking block to the
|
||||
Claude Fable 5.1, Claude Opus 5.5, Claude Sonnet 5.5, and Claude Haiku 5.5 bind each thinking block to the
|
||||
conversation prefix that produced it and reject a replay once that prefix changes — which a dynamic
|
||||
[instructions][pydantic_ai.Agent.instructions] function and a
|
||||
[filtered toolset](../toolsets.md#filtering-tools) both do by design. Anthropic enforces the
|
||||
@@ -815,7 +815,7 @@ def _drop_stale_thinking_blocks(thinking: dict[str, object] | Omit) -> dict[str,
|
||||
|
||||
A binding model emits thinking blocks whether or not the request configured thinking, so the
|
||||
retry usually has no `thinking` object for the binding to ride in, and an `extra_body` one may
|
||||
carry no `type`. Claude Sonnet 5.5 rejects a `thinking` object without a `type`, and every binding
|
||||
carry no `type`. Claude Sonnet 5.5 and Haiku 5.5 reject a `thinking` object without a `type`, and every binding
|
||||
model thinks adaptively when none is given, so `'adaptive'` fills the gap without changing what
|
||||
the caller asked for. The retry sends the whole object through `extra_body`, since the SDK's
|
||||
discriminated union has no typed home for `block_binding` on every config shape.
|
||||
@@ -3182,7 +3182,7 @@ class AnthropicModel(Model[AsyncAnthropicClient]):
|
||||
) -> None:
|
||||
"""Reject `xhigh`/`max` effort combined with explicitly disabled thinking.
|
||||
|
||||
Claude Opus 5 caps effort at `high` once thinking is disabled, while Claude Opus 4.8 accepts
|
||||
Claude Opus 5 and Haiku 5.5 cap effort at `high` once thinking is disabled, while Claude Opus 4.8 accepts
|
||||
every effort level in that combination. Fail fast with a helpful message rather than letting
|
||||
the API return an opaque 400.
|
||||
"""
|
||||
|
||||
@@ -328,6 +328,12 @@ LatestBedrockModelNames = Literal[
|
||||
'anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
'us.anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
'eu.anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
'anthropic.claude-haiku-5-5',
|
||||
'us.anthropic.claude-haiku-5-5',
|
||||
'eu.anthropic.claude-haiku-5-5',
|
||||
'au.anthropic.claude-haiku-5-5',
|
||||
'jp.anthropic.claude-haiku-5-5',
|
||||
'global.anthropic.claude-haiku-5-5',
|
||||
'cohere.command-text-v14',
|
||||
'cohere.command-r-v1:0',
|
||||
'cohere.command-r-plus-v1:0',
|
||||
|
||||
@@ -65,6 +65,7 @@ AdvisorModelName = (
|
||||
'claude-opus-4-6',
|
||||
'claude-sonnet-5-5',
|
||||
'claude-sonnet-4-6',
|
||||
'claude-haiku-5-5',
|
||||
]
|
||||
| str
|
||||
)
|
||||
@@ -74,6 +75,7 @@ These are the models Anthropic currently accepts as the *advisor* — the strong
|
||||
executor consults mid-generation. The executor/advisor pairing is validated by the API, not here.
|
||||
The literals are Anthropic model IDs; on OpenRouter, pass a catalog slug string instead
|
||||
(e.g. `anthropic/claude-opus-4.8` or the `~anthropic/claude-opus-latest` alias).
|
||||
See [Anthropic's model compatibility list](https://platform.claude.com/docs/en/agents-and-tools/tool-use/advisor-tool#model-compatibility).
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ _ANTHROPIC_CODE_EXECUTION_20260120_MODEL_PREFIXES = (
|
||||
'claude-sonnet-4-5',
|
||||
'claude-sonnet-4-6',
|
||||
'claude-sonnet-5',
|
||||
'claude-haiku-5-5',
|
||||
)
|
||||
|
||||
|
||||
@@ -58,7 +59,7 @@ class AnthropicModelProfile(ModelProfile, total=False):
|
||||
"""
|
||||
|
||||
anthropic_supports_adaptive_thinking: bool
|
||||
"""Whether the model supports adaptive thinking (Sonnet 4.6+, Opus 4.6+). Default: `False`.
|
||||
"""Whether the model supports adaptive thinking (Sonnet 4.6+, Opus 4.6+, Haiku 5.5). Default: `False`.
|
||||
|
||||
When True, unified `thinking` translates to `{'type': 'adaptive'}`.
|
||||
When False, it translates to `{'type': 'enabled', 'budget_tokens': N}`.
|
||||
@@ -68,7 +69,7 @@ class AnthropicModelProfile(ModelProfile, total=False):
|
||||
"""
|
||||
|
||||
anthropic_supports_effort: bool
|
||||
"""Whether the model supports the `effort` parameter in `output_config` (Opus 4.5+, Sonnet 4.6+). Default: `False`.
|
||||
"""Whether the model supports the `effort` parameter in `output_config` (Opus 4.5+, Sonnet 4.6+, Haiku 5.5). Default: `False`.
|
||||
|
||||
When True and the unified thinking level is a string (e.g. 'high'), it is also
|
||||
mapped to `output_config.effort`.
|
||||
@@ -84,26 +85,28 @@ class AnthropicModelProfile(ModelProfile, total=False):
|
||||
anthropic_supports_xhigh_effort: bool
|
||||
"""Whether the model supports the `xhigh` effort value in `output_config`. Default: `False`.
|
||||
|
||||
Claude Opus 4.7, 4.8, and 5 accept `xhigh`; older Anthropic models should use `max` instead.
|
||||
Claude Opus 4.7, 4.8, and 5, Sonnet 5, Fable 5, Mythos 5, and Haiku 5.5 accept `xhigh`;
|
||||
other Anthropic models use `max` instead.
|
||||
"""
|
||||
|
||||
anthropic_disallows_budget_thinking: bool
|
||||
"""Whether the model rejects budget-based thinking settings. Default: `False`.
|
||||
|
||||
Claude Opus 4.7, 4.8, and 5 require adaptive thinking and return a 400 for
|
||||
Claude Opus 4.7, 4.8, 5, and Haiku 5.5 require adaptive thinking and return a 400 for
|
||||
`{'type': 'enabled', 'budget_tokens': ...}`.
|
||||
"""
|
||||
|
||||
anthropic_disallows_sampling_settings: bool
|
||||
"""Whether the model rejects sampling settings like `temperature` and `top_p`. Default: `False`.
|
||||
|
||||
Claude Opus 4.7, 4.8, 5, and 5.5 require these settings to be omitted from request payloads.
|
||||
Claude Opus 4.7, 4.8, 5, and 5.5, plus Haiku 5.5, require these settings to be omitted from
|
||||
request payloads.
|
||||
"""
|
||||
|
||||
anthropic_disallows_top_effort_when_thinking_disabled: bool
|
||||
"""Whether the model rejects `xhigh`/`max` effort while thinking is explicitly disabled. Default: `False`.
|
||||
|
||||
Claude Opus 5 caps effort at `high` when `anthropic_thinking={'type': 'disabled'}` and returns a
|
||||
Claude Opus 5 and Haiku 5.5 cap effort at `high` when `anthropic_thinking={'type': 'disabled'}` and return a
|
||||
400 for `xhigh` or `max`; Claude Opus 4.8 accepts the same combination. Claude Opus 5.5 rejects
|
||||
disabled thinking at every effort level, so the flag doesn't apply to it.
|
||||
"""
|
||||
@@ -117,7 +120,7 @@ class AnthropicModelProfile(ModelProfile, total=False):
|
||||
anthropic_supports_task_budgets: bool
|
||||
"""Whether the model supports `output_config.task_budget`. Default: `False`.
|
||||
|
||||
Anthropic currently documents task budgets as a Claude Opus 4.7 / 4.8 / 5 / 5.5 beta feature.
|
||||
Anthropic currently documents task budgets as a Claude Opus 4.7 / 4.8 / 5 / 5.5 and Haiku 5.5 beta feature.
|
||||
"""
|
||||
|
||||
anthropic_supports_forced_tool_choice: bool
|
||||
@@ -145,12 +148,13 @@ class AnthropicModelProfile(ModelProfile, total=False):
|
||||
anthropic_binds_thinking_blocks: bool
|
||||
"""Whether the model binds each thinking block to the conversation prefix that produced it. Default: `False`.
|
||||
|
||||
Claude Fable 5.1, Claude Opus 5.5, and Claude Sonnet 5.5 reject a replayed thinking block once the `system` prompt text changes or a
|
||||
non-deferred tool joins the `tools` array — both of which Pydantic AI causes by design, through
|
||||
dynamic `@agent.instructions` and conditional toolsets. When True, Pydantic AI preserves the
|
||||
account's default behavior on the first request; if Anthropic rejects a stale block, it retries
|
||||
once with `thinking.block_binding.prefix_mismatch_behavior='drop_block'` and warns after the
|
||||
retry succeeds.
|
||||
Claude Fable 5.1, Claude Opus 5.5, Claude Sonnet 5.5, and Claude Haiku 5.5 bind replayed thinking blocks to
|
||||
the conversation prefix. Anthropic documents a 400 after `system`, `tools`, or earlier `messages` change; for
|
||||
accounts created before 31 August 2026, the error requires `thinking.block_binding.prefix_mismatch_behavior`.
|
||||
When True, Pydantic AI preserves the account's default behavior on the first request; if Anthropic
|
||||
rejects a stale block, it retries once with `thinking.block_binding.prefix_mismatch_behavior='drop_block'`
|
||||
and warns after the retry succeeds. See [Anthropic's thinking troubleshooting
|
||||
guide](https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting).
|
||||
"""
|
||||
|
||||
|
||||
@@ -213,6 +217,7 @@ def anthropic_model_profile(model_name: str) -> ModelProfile | None:
|
||||
'claude-opus-4-8',
|
||||
'claude-opus-5',
|
||||
'claude-sonnet-5',
|
||||
'claude-haiku-5-5',
|
||||
)
|
||||
"""These models support both structured outputs and strict tool calling."""
|
||||
# TODO update when new models are released that support structured outputs
|
||||
@@ -223,7 +228,7 @@ def anthropic_model_profile(model_name: str) -> ModelProfile | None:
|
||||
('claude-opus-4-6', 'claude-opus-4-7', 'claude-opus-4-8', 'claude-opus-5')
|
||||
)
|
||||
|
||||
# Sonnet 4.6+ and Opus 4.6+ support adaptive thinking; older models use budget-based
|
||||
# Sonnet 4.6+, Opus 4.6+, and Haiku 5.5 support adaptive thinking; older models use budget-based.
|
||||
supports_adaptive = model_name.startswith(
|
||||
(
|
||||
'claude-fable-5',
|
||||
@@ -235,10 +240,10 @@ def anthropic_model_profile(model_name: str) -> ModelProfile | None:
|
||||
'claude-opus-4-7',
|
||||
'claude-opus-4-8',
|
||||
'claude-opus-5',
|
||||
'claude-haiku-5-5',
|
||||
)
|
||||
)
|
||||
|
||||
# Opus 4.5+ and Sonnet 4.6+ support the effort parameter in output_config
|
||||
supports_effort = model_name.startswith(
|
||||
(
|
||||
'claude-fable-5',
|
||||
@@ -250,27 +255,60 @@ def anthropic_model_profile(model_name: str) -> ModelProfile | None:
|
||||
'claude-opus-5',
|
||||
'claude-sonnet-4-6',
|
||||
'claude-sonnet-5',
|
||||
'claude-haiku-5-5',
|
||||
)
|
||||
)
|
||||
supports_xhigh_effort = model_name.startswith(
|
||||
('claude-fable-5', 'claude-mythos-5', 'claude-opus-4-7', 'claude-opus-4-8', 'claude-opus-5', 'claude-sonnet-5')
|
||||
(
|
||||
'claude-fable-5',
|
||||
'claude-mythos-5',
|
||||
'claude-opus-4-7',
|
||||
'claude-opus-4-8',
|
||||
'claude-opus-5',
|
||||
'claude-sonnet-5',
|
||||
'claude-haiku-5-5',
|
||||
)
|
||||
)
|
||||
disallows_budget_thinking = model_name.startswith(
|
||||
('claude-fable-5', 'claude-mythos-5', 'claude-opus-4-7', 'claude-opus-4-8', 'claude-opus-5', 'claude-sonnet-5')
|
||||
(
|
||||
'claude-fable-5',
|
||||
'claude-mythos-5',
|
||||
'claude-opus-4-7',
|
||||
'claude-opus-4-8',
|
||||
'claude-opus-5',
|
||||
'claude-sonnet-5',
|
||||
'claude-haiku-5-5',
|
||||
)
|
||||
)
|
||||
disallows_sampling_settings = model_name.startswith(
|
||||
('claude-fable-5', 'claude-mythos-5', 'claude-opus-4-7', 'claude-opus-4-8', 'claude-opus-5', 'claude-sonnet-5')
|
||||
(
|
||||
'claude-fable-5',
|
||||
'claude-mythos-5',
|
||||
'claude-opus-4-7',
|
||||
'claude-opus-4-8',
|
||||
'claude-opus-5',
|
||||
'claude-sonnet-5',
|
||||
'claude-haiku-5-5',
|
||||
)
|
||||
)
|
||||
# Opus 5 caps effort at `high` while thinking is disabled; Opus 4.8 and earlier accept every level.
|
||||
# Opus 5 and Haiku 5.5 cap effort at `high` while thinking is disabled; Opus 4.8 and earlier accept every level.
|
||||
# Opus 5.5 rejects disabled thinking outright, so the effort-specific error would mislead.
|
||||
disallows_top_effort_when_thinking_disabled = model_name.startswith('claude-opus-5') and not model_name.startswith(
|
||||
'claude-opus-5-5'
|
||||
)
|
||||
disallows_top_effort_when_thinking_disabled = (
|
||||
model_name.startswith('claude-opus-5') and not model_name.startswith('claude-opus-5-5')
|
||||
) or model_name.startswith('claude-haiku-5-5')
|
||||
default_code_execution_tool_version, supported_code_execution_tool_versions = _code_execution_tool_versions(
|
||||
model_name
|
||||
)
|
||||
supports_task_budgets = model_name.startswith(
|
||||
('claude-fable-5', 'claude-mythos-5', 'claude-opus-4-7', 'claude-opus-4-8', 'claude-opus-5', 'claude-sonnet-5')
|
||||
(
|
||||
'claude-fable-5',
|
||||
'claude-mythos-5',
|
||||
'claude-opus-4-7',
|
||||
'claude-opus-4-8',
|
||||
'claude-opus-5',
|
||||
'claude-sonnet-5',
|
||||
'claude-haiku-5-5',
|
||||
)
|
||||
)
|
||||
|
||||
# The `max_tokens` Anthropic's Models API reports for each model. Mythos 5 isn't reachable with our credentials;
|
||||
@@ -285,6 +323,7 @@ def anthropic_model_profile(model_name: str) -> ModelProfile | None:
|
||||
'claude-opus-5',
|
||||
'claude-sonnet-4-6',
|
||||
'claude-sonnet-5',
|
||||
'claude-haiku-5-5',
|
||||
)
|
||||
):
|
||||
max_output_tokens: int | None = 128_000
|
||||
@@ -315,12 +354,20 @@ def anthropic_model_profile(model_name: str) -> ModelProfile | None:
|
||||
)
|
||||
|
||||
# Anthropic documents these models as thinking when the request omits `thinking`; Fable 5, Fable 5.1, Opus 5,
|
||||
# Opus 5.5 and Sonnet 5 return thinking tokens live with no thinking parameter, where Opus 4.8 and Sonnet 4.6
|
||||
# return none.
|
||||
# Opus 5.5, and Sonnet 5 return thinking tokens live with no thinking parameter, where Opus 4.8 and Sonnet 4.6
|
||||
# return none. Haiku 5.5's adaptive default was verified live with `anthropic_effort='high'` and no `thinking`
|
||||
# parameter; it returned signed thinking blocks and positive thinking-token usage.
|
||||
thinking_enabled_by_default = model_name.startswith(
|
||||
('claude-fable-5', 'claude-mythos-5', 'claude-mythos-preview', 'claude-opus-5', 'claude-sonnet-5')
|
||||
(
|
||||
'claude-fable-5',
|
||||
'claude-mythos-5',
|
||||
'claude-mythos-preview',
|
||||
'claude-opus-5',
|
||||
'claude-sonnet-5',
|
||||
'claude-haiku-5-5',
|
||||
)
|
||||
)
|
||||
# Of those, all but Opus 5 and Sonnet 5 reject `thinking={'type': 'disabled'}` with a 400, so thinking
|
||||
# Of those, all but Opus 5, Sonnet 5, and Haiku 5.5 reject `thinking={'type': 'disabled'}` with a 400, so thinking
|
||||
# can't be turned off.
|
||||
thinking_always_enabled = model_name.startswith(
|
||||
('claude-fable-5', 'claude-mythos-5', 'claude-mythos-preview', 'claude-opus-5-5', 'claude-sonnet-5-5')
|
||||
@@ -336,12 +383,15 @@ def anthropic_model_profile(model_name: str) -> ModelProfile | None:
|
||||
('claude-fable-5-1', 'claude-mythos-5-1', 'claude-opus-5-5', 'claude-sonnet-5-5')
|
||||
)
|
||||
|
||||
# Claude Fable 5.1, Opus 5.5, and Sonnet 5.5 bind thinking blocks to the conversation prefix:
|
||||
# Claude Fable 5, Opus 5, and Sonnet 5 all return 200 for a replayed block under an explicit
|
||||
# `prefix_mismatch_behavior` of `'error'` where Opus 5.5 and Sonnet 5.5 return a 400 once the
|
||||
# `system` prompt changes, and Anthropic documents that Claude Mythos 5.1 "doesn't run this
|
||||
# Claude Fable 5.1, Opus 5.5, Sonnet 5.5, and Haiku 5.5 bind thinking blocks to the conversation prefix.
|
||||
# Anthropic documents this behavior for Haiku 5.5; a live `drop_block` probe returned the expected transformation
|
||||
# after a prefix change, but the explicit `error` behavior was not probed. Claude Fable 5, Opus 5, and Sonnet 5
|
||||
# return 200 for a replayed block under explicit `prefix_mismatch_behavior='error'` where Opus 5.5 and Sonnet 5.5
|
||||
# return a 400 after the `system` prompt changes. Anthropic documents that Claude Mythos 5.1 "doesn't run this
|
||||
# check" — the one capability on which it is not Fable 5.1's mirror.
|
||||
binds_thinking_blocks = model_name.startswith(('claude-fable-5-1', 'claude-opus-5-5', 'claude-sonnet-5-5'))
|
||||
binds_thinking_blocks = model_name.startswith(
|
||||
('claude-fable-5-1', 'claude-opus-5-5', 'claude-sonnet-5-5', 'claude-haiku-5-5')
|
||||
)
|
||||
|
||||
supports_dynamic_filtering = model_name.startswith(
|
||||
(
|
||||
@@ -354,6 +404,7 @@ def anthropic_model_profile(model_name: str) -> ModelProfile | None:
|
||||
'claude-opus-4-7',
|
||||
'claude-opus-4-8',
|
||||
'claude-opus-5',
|
||||
'claude-haiku-5-5',
|
||||
)
|
||||
)
|
||||
# Native tool search requires the `tool_search_tool_bm25_20251119` /
|
||||
@@ -372,10 +423,11 @@ def anthropic_model_profile(model_name: str) -> ModelProfile | None:
|
||||
'claude-opus-4-8',
|
||||
'claude-opus-5',
|
||||
'claude-haiku-4-5',
|
||||
'claude-haiku-5-5',
|
||||
)
|
||||
)
|
||||
# The advisor tool's valid *executors* (the model driving generation) are Fable/Mythos 5, Opus
|
||||
# 4.6-4.8 and 5, Sonnet 4.6/5, and Haiku 4.5. The executor/advisor pairing itself is validated API-side.
|
||||
# 4.6-4.8 and 5, Sonnet 4.6/5, and Haiku 4.5/5.5. The executor/advisor pairing itself is validated API-side.
|
||||
supports_advisor = model_name.startswith(
|
||||
(
|
||||
'claude-fable-5',
|
||||
@@ -387,6 +439,7 @@ def anthropic_model_profile(model_name: str) -> ModelProfile | None:
|
||||
'claude-sonnet-4-6',
|
||||
'claude-sonnet-5',
|
||||
'claude-haiku-4-5',
|
||||
'claude-haiku-5-5',
|
||||
)
|
||||
)
|
||||
supported_native_tools = _ANTHROPIC_BASE_BUILTINS
|
||||
|
||||
@@ -43,6 +43,7 @@ _INLINE_SYSTEM_PROMPT_MODEL_PREFIXES = (
|
||||
'claude-opus-4-8',
|
||||
'claude-opus-5',
|
||||
'claude-sonnet-5-5',
|
||||
'claude-haiku-5-5',
|
||||
)
|
||||
"""Models that honor a `{'role': 'system'}` entry inside the Messages API's `messages` array.
|
||||
|
||||
@@ -67,6 +68,7 @@ _TOOL_AVAILABILITY_DELTA_MODEL_PREFIXES = (
|
||||
'claude-opus-4-8',
|
||||
'claude-opus-5',
|
||||
'claude-sonnet-5-5',
|
||||
'claude-haiku-5-5',
|
||||
)
|
||||
"""Models that accept `tool_addition` / `tool_removal` blocks on a `{'role': 'system'}` entry.
|
||||
|
||||
@@ -75,8 +77,8 @@ to match `_INLINE_SYSTEM_PROMPT_MODEL_PREFIXES` — the two remain separate sett
|
||||
separate features, one GA and one beta, that could diverge again. Models predating the beta reject
|
||||
the blocks with `requires a model that supports ...`, and `claude-sonnet-5` rejects them outright
|
||||
(`tool_addition/tool_removal is not supported on this model`) rather than accepting and ignoring them
|
||||
the way it does a plain system entry. Verified live per model except `claude-mythos-5`, which isn't
|
||||
reachable with our credentials and is included on the strength of the published list.
|
||||
the way it does a plain system entry. Verified live per model, including `claude-haiku-5-5`, except
|
||||
`claude-mythos-5`, which isn't reachable with our credentials and is included on the strength of the published list.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ class BedrockModelProfile(ModelProfile, total=False):
|
||||
"""
|
||||
|
||||
bedrock_supports_adaptive_thinking: bool
|
||||
"""Whether this model accepts `{'thinking': {'type': 'adaptive'}}` (Sonnet 4.6+, Opus 4.6+).
|
||||
"""Whether this model accepts `{'thinking': {'type': 'adaptive'}}` (Haiku 5.5, Sonnet 4.6+, Opus 4.6+).
|
||||
|
||||
Only meaningful for the `'anthropic'` variant. When False, the variant falls back to
|
||||
`{'type': 'enabled', 'budget_tokens': N}` for pre-4.6 models.
|
||||
@@ -213,7 +213,7 @@ class BedrockModelProfile(ModelProfile, total=False):
|
||||
"""
|
||||
|
||||
bedrock_supports_effort: bool
|
||||
"""Whether this model emits `output_config.effort` on Bedrock Converse (Sonnet 4.6+, Opus 4.6+).
|
||||
"""Whether this model emits `output_config.effort` on Bedrock Converse (Haiku 5.5, Sonnet 4.6+, Opus 4.6+).
|
||||
|
||||
Only meaningful for the `'anthropic'` variant AND only honored alongside
|
||||
`bedrock_supports_adaptive_thinking=True`. Bedrock has not been verified to accept
|
||||
@@ -265,6 +265,7 @@ def bedrock_anthropic_model_profile(model_name: str) -> ModelProfile | None:
|
||||
'claude-opus-4-8',
|
||||
'claude-opus-5',
|
||||
'claude-sonnet-5',
|
||||
'claude-haiku-5-5',
|
||||
'claude-fable-5',
|
||||
)
|
||||
downstream = anthropic_model_profile(model_name)
|
||||
|
||||
@@ -76,7 +76,7 @@ cohere = ["cohere>=5.20.6; platform_system != 'Emscripten'"]
|
||||
google = ["google-genai>=2.25.0"]
|
||||
# `anthropic>=1` is built on `httpx2` and drops legacy HTTPX entirely; it also removes
|
||||
# `temperature`/`top_p`/`top_k` from `messages.create()`, which the model sends via `extra_body`.
|
||||
anthropic = ["anthropic>=1.9.0"]
|
||||
anthropic = ["anthropic>=1.12.1"]
|
||||
# `xai-sdk>=1.16.0` adds the `image_file_id`/`image_file_ids` arguments to `image.sample()` and
|
||||
# `image.sample_batch()`, which carry `UploadedFile` reference images to the image generation API.
|
||||
xai = ["xai-sdk>=1.16.0"]
|
||||
|
||||
+4
-4
@@ -210,10 +210,10 @@ logfire-api = false
|
||||
# Admit termflow-md 1.0.0 for CLAI2's live panel (`termflow.live`); later releases stay quarantined.
|
||||
# TODO: Remove after 2026-10-09 15:06 UTC.
|
||||
termflow-md = "2026-10-02T15:05:25Z"
|
||||
# Anthropic 1.9.0 is the first release listing `claude-sonnet-5-5` and the `between_tools` thinking type.
|
||||
# TODO(dsfaccini): Remove after 2026-10-05 18:04 UTC. https://github.com/pydantic/pydantic-ai/issues/8972
|
||||
# This admits only the reviewed 1.9.0 artifacts; later Anthropic artifacts remain quarantined.
|
||||
anthropic = "2026-09-28T18:04:04Z"
|
||||
# Anthropic 1.12.1 adds `claude-haiku-5-5` to `ModelParam`.
|
||||
# TODO(dsfaccini): Remove after 2026-10-15T03:42:01.588842Z. https://github.com/pydantic/pydantic-ai/issues/9997
|
||||
# This admits the reviewed 1.12.1 artifacts through one second after the last upload; later artifacts remain quarantined.
|
||||
anthropic = "2026-10-08T03:42:01.588842Z"
|
||||
# OpenAI 3.26.0 is the first release with `client.decisions`, and the `openai` floor.
|
||||
# TODO(dsfaccini): Remove after 2026-10-13 21:32 UTC.
|
||||
openai = "2026-10-06T21:31:20Z"
|
||||
|
||||
@@ -185,12 +185,13 @@ async def test_anthropic_sends_no_block_binding_by_default(
|
||||
assert _THINKING_BINDING_BETA not in sent_betas(mock_client)
|
||||
|
||||
|
||||
async def test_anthropic_retries_a_stale_thinking_block_with_drop_block(allow_model_requests: None):
|
||||
@pytest.mark.parametrize('model_name', ['claude-fable-5-1', 'claude-sonnet-5-5', 'claude-haiku-5-5'])
|
||||
async def test_anthropic_retries_a_stale_thinking_block_with_drop_block(allow_model_requests: None, model_name: str):
|
||||
"""A rejected replay is retried once asking Anthropic to drop the block, and the run continues.
|
||||
|
||||
The retried `thinking` object rides in `extra_body`, typed `adaptive` when the request configured
|
||||
no thinking: Claude Sonnet 5.5 rejects a `thinking` object without a `type`, and every binding model
|
||||
thinks adaptively when none is given.
|
||||
The retried `thinking` object rides in `extra_body`, typed `adaptive` when the request had no `thinking` setting.
|
||||
Claude Sonnet 5.5 and Haiku 5.5 reject a `thinking` object without a `type`; Haiku 5.5's adaptive default was
|
||||
verified live with effort `high` and no `thinking` parameter, and the response included thinking tokens.
|
||||
"""
|
||||
mock_client = MockAnthropic.create_mock(
|
||||
[
|
||||
@@ -200,7 +201,7 @@ async def test_anthropic_retries_a_stale_thinking_block_with_drop_block(allow_mo
|
||||
),
|
||||
]
|
||||
)
|
||||
m = AnthropicModel('claude-fable-5-1', provider=AnthropicProvider(anthropic_client=mock_client))
|
||||
m = AnthropicModel(model_name, provider=AnthropicProvider(anthropic_client=mock_client))
|
||||
|
||||
with pytest.warns(AnthropicStaleThinkingBlockWarning, match='rejected a replayed thinking block'):
|
||||
result = await Agent(m).run('What is 2+2?')
|
||||
|
||||
@@ -107,6 +107,11 @@ UNSUPPORTED_GATEWAY_MODEL_NAMES = frozenset(
|
||||
'gateway/bedrock:anthropic.claude-3-opus-20240229-v1:0',
|
||||
'gateway/bedrock:anthropic.claude-3-sonnet-20240229-v1:0',
|
||||
'gateway/bedrock:anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
# Gateway returns HTTP 400 for the bare Haiku 5.5 id and the au./jp./us. profiles.
|
||||
'gateway/bedrock:anthropic.claude-haiku-5-5',
|
||||
'gateway/bedrock:au.anthropic.claude-haiku-5-5',
|
||||
'gateway/bedrock:jp.anthropic.claude-haiku-5-5',
|
||||
'gateway/bedrock:us.anthropic.claude-haiku-5-5',
|
||||
'gateway/bedrock:anthropic.claude-instant-v1',
|
||||
'gateway/bedrock:anthropic.claude-opus-4-20250514-v1:0',
|
||||
'gateway/bedrock:anthropic.claude-sonnet-4-20250514-v1:0',
|
||||
|
||||
@@ -31,7 +31,7 @@ with try_import() as anthropic_available:
|
||||
AnthropicModelSettings,
|
||||
_support_tool_forcing as anthropic_support_tool_forcing, # pyright: ignore[reportPrivateUsage]
|
||||
)
|
||||
from pydantic_ai.profiles.anthropic import AnthropicModelProfile
|
||||
from pydantic_ai.profiles.anthropic import AnthropicModelProfile, anthropic_model_profile
|
||||
from pydantic_ai.providers.anthropic import AnthropicProvider
|
||||
|
||||
with try_import() as bedrock_available:
|
||||
@@ -663,6 +663,27 @@ def test_support_tool_forcing_rejects_unsupported_model_with_adaptive_thinking()
|
||||
)
|
||||
|
||||
|
||||
@skip_if_no_anthropic
|
||||
@pytest.mark.parametrize(
|
||||
'settings',
|
||||
[
|
||||
pytest.param({'anthropic_thinking': {'type': 'adaptive'}, 'tool_choice': 'required'}, id='adaptive'),
|
||||
pytest.param({'anthropic_thinking': {'type': 'disabled'}, 'tool_choice': 'required'}, id='disabled'),
|
||||
],
|
||||
)
|
||||
def test_haiku_5_5_supports_forced_tool_choice_with_adaptive_or_disabled_thinking(settings: AnthropicModelSettings):
|
||||
"""Haiku 5.5 accepts forced tool choice with either adaptive or disabled thinking."""
|
||||
model_profile = anthropic_model_profile('claude-haiku-5-5')
|
||||
assert model_profile is not None
|
||||
profile = AnthropicModelProfile(**model_profile)
|
||||
assert (
|
||||
anthropic_support_tool_forcing(
|
||||
'claude-haiku-5-5', profile, settings, ModelRequestParameters(function_tools=[make_tool('my_tool')])
|
||||
)
|
||||
is True
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'provider_name',
|
||||
[
|
||||
|
||||
@@ -364,6 +364,7 @@ def test_model_profile_fable_5():
|
||||
('claude-sonnet-5', True, False),
|
||||
('claude-opus-5-5', True, True),
|
||||
('claude-sonnet-5-5', True, True),
|
||||
('claude-haiku-5-5', True, False),
|
||||
('claude-fable-5', True, True),
|
||||
('claude-fable-5-1', True, True),
|
||||
('claude-mythos-5-1', True, True),
|
||||
@@ -373,9 +374,12 @@ def test_model_profile_fable_5():
|
||||
def test_model_profile_thinking_defaults(model_name: str, thinks_by_default: bool, always_thinks: bool):
|
||||
"""Which models think without a `thinking` setting, and which reject `{'type': 'disabled'}`.
|
||||
|
||||
Verified live for every model but the Mythos ones, which match Anthropic's docs: with no `thinking` parameter,
|
||||
Verified live for every model except Mythos. Haiku 5.5's adaptive default was verified with
|
||||
`anthropic_effort='high'` and no `thinking` parameter; the response included thinking tokens. Anthropic [documents
|
||||
Haiku 5.5 as adaptive-on by default](https://platform.claude.com/docs/en/models/haiku-5-5/migration-guide#configure-thinking)
|
||||
and accepts `thinking={'type': 'disabled'}` at effort `high` or below. With no `thinking` parameter,
|
||||
Claude Opus 5, Sonnet 5, Opus 5.5, Fable 5 and Fable 5.1 return thinking tokens where Sonnet 4.6 and Opus 4.8
|
||||
return none, and Opus 5.5, Fable 5 and Fable 5.1 answer `thinking={'type': 'disabled'}` with a 400.
|
||||
return none; Opus 5.5, Fable 5 and Fable 5.1 reject `thinking={'type': 'disabled'}` with a 400.
|
||||
"""
|
||||
profile = anthropic_model_profile(model_name)
|
||||
assert profile is not None
|
||||
@@ -397,6 +401,7 @@ def test_model_profile_thinking_defaults(model_name: str, thinks_by_default: boo
|
||||
('claude-opus-5-5', False),
|
||||
('claude-opus-5', True),
|
||||
('claude-sonnet-5-5', False),
|
||||
('claude-haiku-5-5', True),
|
||||
('claude-sonnet-5', True),
|
||||
],
|
||||
)
|
||||
@@ -623,6 +628,37 @@ def test_model_profile_sonnet_5_5():
|
||||
}
|
||||
|
||||
|
||||
def test_model_profile_haiku_5_5():
|
||||
"""Haiku 5.5 uses Opus 5's existing defaults with two model-specific overrides.
|
||||
|
||||
Anthropic documents Haiku 5.5's 128K output, adaptive default, `xhigh`/`max`, manual budget-thinking and
|
||||
non-default sampling rejection, disabled-thinking effort cap, task-budget support, and prefix binding. Fast mode
|
||||
docs list only Opus models; web-search docs cover dynamic filtering on Claude 4.6 and later. Live probes confirmed
|
||||
adaptive `xhigh`/`max` returned signed thinking blocks; adaptive default with no `thinking` setting and effort `high`
|
||||
also returned signed thinking blocks and positive thinking-token usage. Other live probes confirmed forced tool
|
||||
choice, task-budget acceptance, `code_execution_20260120`, BM25 search, Advisor, web-search request acceptance,
|
||||
deferred tool addition, inline system messages, and the `drop_block` transformation after a prefix change. The only
|
||||
Haiku-specific profile overrides are fast speed disabled and thinking-block binding enabled; unchanged fields
|
||||
retain Opus 5's existing profile defaults. See Anthropic's
|
||||
[overview](https://platform.claude.com/docs/en/models/haiku-5-5/overview),
|
||||
[migration guide](https://platform.claude.com/docs/en/models/haiku-5-5/migration-guide),
|
||||
[thinking configuration](https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting),
|
||||
[effort](https://platform.claude.com/docs/en/build-with-claude/effort),
|
||||
[task-budget support](https://platform.claude.com/docs/en/build-with-claude/task-budgets),
|
||||
[fast mode](https://platform.claude.com/docs/en/build-with-claude/fast-mode), and
|
||||
[web search and dynamic filtering](https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool).
|
||||
"""
|
||||
profile = anthropic_model_profile('claude-haiku-5-5')
|
||||
opus_5 = anthropic_model_profile('claude-opus-5')
|
||||
assert profile is not None
|
||||
assert opus_5 is not None
|
||||
assert profile == {
|
||||
**opus_5,
|
||||
'anthropic_supports_fast_speed': False,
|
||||
'anthropic_binds_thinking_blocks': True,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('model_name', 'expected'),
|
||||
[('claude-sonnet-4-5', 'standalone'), ('claude-opus-4-1-20250805', None)],
|
||||
|
||||
@@ -65,9 +65,13 @@ def test_anthropic_provider_with_env_base_url(monkeypatch: pytest.MonkeyPatch) -
|
||||
# Direct Anthropic API ids (with and without date suffix)
|
||||
'claude-haiku-4-5',
|
||||
'claude-haiku-4-5-20251001',
|
||||
'claude-haiku-5-5',
|
||||
# Amazon Bedrock ids: `anthropic.` provider segment, optional geo prefix and `-vN(:M)?` version suffix
|
||||
'anthropic.claude-haiku-4-5',
|
||||
'anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
'anthropic.claude-haiku-5-5',
|
||||
'us.anthropic.claude-haiku-5-5',
|
||||
'global.anthropic.claude-haiku-5-5',
|
||||
'us.anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
'global.anthropic.claude-haiku-4-5',
|
||||
# Anthropic on Vertex AI: `@`-delimited version
|
||||
@@ -102,6 +106,7 @@ def test_anthropic_provider_model_profile_older_model_still_resolves():
|
||||
# Sonnet 5 accepts the entry with a 200 and then ignores it, so it is deliberately out.
|
||||
('claude-sonnet-5', False),
|
||||
('claude-sonnet-5-5', True),
|
||||
('claude-haiku-5-5', True),
|
||||
('claude-opus-4-7', False),
|
||||
('claude-sonnet-4-6', False),
|
||||
('claude-haiku-4-5', False),
|
||||
@@ -127,3 +132,17 @@ def test_anthropic_provider_model_profile_inline_system_prompts(model_name: str,
|
||||
profile = AnthropicProvider.model_profile(model_name)
|
||||
assert isinstance(profile, dict)
|
||||
assert profile.get('supports_inline_system_prompts', False) is supported
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('model_name', 'expected_mode'),
|
||||
[('claude-haiku-5-5', 'by_reference'), ('claude-sonnet-5', None)],
|
||||
)
|
||||
def test_anthropic_provider_model_profile_tool_availability_delta(model_name: str, expected_mode: str | None):
|
||||
"""Haiku 5.5's deferred tool addition was verified live; this checks provider-profile resolution.
|
||||
|
||||
Sonnet 5 does not support the feature.
|
||||
"""
|
||||
profile = AnthropicProvider.model_profile(model_name)
|
||||
assert isinstance(profile, dict)
|
||||
assert profile.get('tool_addition_mode') == expected_mode
|
||||
|
||||
@@ -162,13 +162,19 @@ def test_bedrock_provider_model_profile(env: TestEnv, mocker: MockerFixture):
|
||||
assert anthropic_profile.get('anthropic_supports_xhigh_effort', False) is True
|
||||
|
||||
# These models support structured output directly, but not through Bedrock Converse.
|
||||
for model_name in ('claude-sonnet-5', 'claude-fable-5'):
|
||||
for model_name in ('claude-sonnet-5', 'claude-haiku-5-5', 'claude-fable-5'):
|
||||
anthropic_profile = provider.model_profile(f'global.anthropic.{model_name}')
|
||||
anthropic_model_profile_mock.assert_called_with(model_name)
|
||||
assert isinstance(anthropic_profile, dict)
|
||||
assert anthropic_profile.get('supports_json_schema_output', False) is False
|
||||
assert anthropic_profile.get('bedrock_supports_strict_tool_definition', False) is False
|
||||
|
||||
anthropic_profile = provider.model_profile('us.anthropic.claude-haiku-5-5')
|
||||
anthropic_model_profile_mock.assert_called_with('claude-haiku-5-5')
|
||||
assert isinstance(anthropic_profile, dict)
|
||||
assert anthropic_profile.get('bedrock_supports_adaptive_thinking', False) is True
|
||||
assert anthropic_profile.get('bedrock_supports_effort', False) is True
|
||||
|
||||
anthropic_profile = provider.model_profile('us.anthropic.claude-sonnet-4-5-20250929-v1:0')
|
||||
anthropic_model_profile_mock.assert_called_with('claude-sonnet-4-5-20250929')
|
||||
assert isinstance(anthropic_profile, dict)
|
||||
|
||||
@@ -672,6 +672,7 @@ def test_model_json_schema_with_capabilities():
|
||||
'claude-opus-4-6',
|
||||
'claude-sonnet-5-5',
|
||||
'claude-sonnet-4-6',
|
||||
'claude-haiku-5-5',
|
||||
],
|
||||
'type': 'string',
|
||||
},
|
||||
@@ -843,6 +844,7 @@ def test_model_json_schema_with_capabilities():
|
||||
'anthropic:claude-fable-5-1',
|
||||
'anthropic:claude-haiku-4-5',
|
||||
'anthropic:claude-haiku-4-5-20251001',
|
||||
'anthropic:claude-haiku-5-5',
|
||||
'anthropic:claude-mythos-5',
|
||||
'anthropic:claude-mythos-5-1',
|
||||
'anthropic:claude-mythos-preview',
|
||||
@@ -880,6 +882,7 @@ def test_model_json_schema_with_capabilities():
|
||||
'bedrock:anthropic.claude-3-opus-20240229-v1:0',
|
||||
'bedrock:anthropic.claude-3-sonnet-20240229-v1:0',
|
||||
'bedrock:anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
'bedrock:anthropic.claude-haiku-5-5',
|
||||
'bedrock:anthropic.claude-instant-v1',
|
||||
'bedrock:anthropic.claude-opus-4-20250514-v1:0',
|
||||
'bedrock:anthropic.claude-sonnet-4-20250514-v1:0',
|
||||
@@ -887,6 +890,7 @@ def test_model_json_schema_with_capabilities():
|
||||
'bedrock:anthropic.claude-sonnet-4-6',
|
||||
'bedrock:anthropic.claude-v2',
|
||||
'bedrock:anthropic.claude-v2:1',
|
||||
'bedrock:au.anthropic.claude-haiku-5-5',
|
||||
'bedrock:cohere.command-light-text-v14',
|
||||
'bedrock:cohere.command-r-plus-v1:0',
|
||||
'bedrock:cohere.command-r-v1:0',
|
||||
@@ -894,12 +898,14 @@ def test_model_json_schema_with_capabilities():
|
||||
'bedrock:deepseek.r1-v1:0',
|
||||
'bedrock:deepseek.v3.2',
|
||||
'bedrock:eu.anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
'bedrock:eu.anthropic.claude-haiku-5-5',
|
||||
'bedrock:eu.anthropic.claude-sonnet-4-20250514-v1:0',
|
||||
'bedrock:eu.anthropic.claude-sonnet-4-5-20250929-v1:0',
|
||||
'bedrock:eu.anthropic.claude-sonnet-4-6',
|
||||
'bedrock:global.amazon.nova-2-lite-v1:0',
|
||||
'bedrock:global.anthropic.claude-fable-5',
|
||||
'bedrock:global.anthropic.claude-fable-5-1',
|
||||
'bedrock:global.anthropic.claude-haiku-5-5',
|
||||
'bedrock:global.anthropic.claude-opus-4-5-20251101-v1:0',
|
||||
'bedrock:global.anthropic.claude-opus-4-6-v1',
|
||||
'bedrock:global.anthropic.claude-opus-4-7',
|
||||
@@ -916,6 +922,7 @@ def test_model_json_schema_with_capabilities():
|
||||
'bedrock:google.gemma-3-4b-it',
|
||||
'bedrock:in.openai.gpt-5.6-luna',
|
||||
'bedrock:in.openai.gpt-5.6-terra',
|
||||
'bedrock:jp.anthropic.claude-haiku-5-5',
|
||||
'bedrock:meta.llama3-1-405b-instruct-v1:0',
|
||||
'bedrock:meta.llama3-1-70b-instruct-v1:0',
|
||||
'bedrock:meta.llama3-1-8b-instruct-v1:0',
|
||||
@@ -962,6 +969,7 @@ def test_model_json_schema_with_capabilities():
|
||||
'bedrock:us.anthropic.claude-fable-5',
|
||||
'bedrock:us.anthropic.claude-fable-5-1',
|
||||
'bedrock:us.anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
'bedrock:us.anthropic.claude-haiku-5-5',
|
||||
'bedrock:us.anthropic.claude-opus-4-1-20250805-v1:0',
|
||||
'bedrock:us.anthropic.claude-opus-4-20250514-v1:0',
|
||||
'bedrock:us.anthropic.claude-opus-4-5-20251101-v1:0',
|
||||
@@ -1024,6 +1032,7 @@ def test_model_json_schema_with_capabilities():
|
||||
'gateway/anthropic:claude-fable-5-1',
|
||||
'gateway/anthropic:claude-haiku-4-5',
|
||||
'gateway/anthropic:claude-haiku-4-5-20251001',
|
||||
'gateway/anthropic:claude-haiku-5-5',
|
||||
'gateway/anthropic:claude-opus-4-5',
|
||||
'gateway/anthropic:claude-opus-4-5-20251101',
|
||||
'gateway/anthropic:claude-opus-4-6',
|
||||
@@ -1040,12 +1049,14 @@ def test_model_json_schema_with_capabilities():
|
||||
'gateway/bedrock:deepseek.r1-v1:0',
|
||||
'gateway/bedrock:deepseek.v3.2',
|
||||
'gateway/bedrock:eu.anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
'gateway/bedrock:eu.anthropic.claude-haiku-5-5',
|
||||
'gateway/bedrock:eu.anthropic.claude-sonnet-4-20250514-v1:0',
|
||||
'gateway/bedrock:eu.anthropic.claude-sonnet-4-5-20250929-v1:0',
|
||||
'gateway/bedrock:eu.anthropic.claude-sonnet-4-6',
|
||||
'gateway/bedrock:global.amazon.nova-2-lite-v1:0',
|
||||
'gateway/bedrock:global.anthropic.claude-fable-5',
|
||||
'gateway/bedrock:global.anthropic.claude-fable-5-1',
|
||||
'gateway/bedrock:global.anthropic.claude-haiku-5-5',
|
||||
'gateway/bedrock:global.anthropic.claude-opus-4-5-20251101-v1:0',
|
||||
'gateway/bedrock:global.anthropic.claude-opus-4-6-v1',
|
||||
'gateway/bedrock:global.anthropic.claude-opus-4-7',
|
||||
|
||||
@@ -42,7 +42,7 @@ pydantic-monty-client = false
|
||||
genai-prices = false
|
||||
pydantic-monty = false
|
||||
termflow-md = "2026-10-02T15:05:25Z"
|
||||
anthropic = "2026-09-28T18:04:04Z"
|
||||
anthropic = "2026-10-08T03:42:01.588842Z"
|
||||
openai = "2026-10-06T21:31:20Z"
|
||||
google-genai = "2026-09-22T17:23:02Z"
|
||||
|
||||
@@ -340,7 +340,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "anthropic"
|
||||
version = "1.9.0"
|
||||
version = "1.12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
@@ -351,9 +351,9 @@ dependencies = [
|
||||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/0a/1ab3bf53a672fd9dd42d3208e335940049431b79781b2a9b324203d30c38/anthropic-1.9.0.tar.gz", hash = "sha256:da5191a0af0b853a797fc0c6b09fc5b05cb5d9da063ba80fdd03c736abe81b6c", size = 1243092, upload-time = "2026-09-28T18:04:03.005Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bd/cc/0da23e94020c62c0077c17648252f32d575b187710faaf3b85145e8e555d/anthropic-1.12.1.tar.gz", hash = "sha256:10f720ab6ddf40ef12e0e5743412c3a6dfac1109c244e633c9a2dbd36778ad30", size = 1555908, upload-time = "2026-10-08T03:42:00.588Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/15/2a/67c1b2ddf91be3112856ed74b96d1576f00dd423e4f20bb251aa144fbd6b/anthropic-1.9.0-py3-none-any.whl", hash = "sha256:a7fe05a9e4b72113b934501698df5d56484ac89f05677f0f793f8bfddb15cfff", size = 1360651, upload-time = "2026-09-28T18:04:01.452Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/35/1ccdc3e950dd869b66de15035e15d9614607fcef9664dec403fe639f01df/anthropic-1.12.1-py3-none-any.whl", hash = "sha256:1c1469bb3f4118291756816b7850cc839f17b2be5cdc99c8fde3890a61a49ca2", size = 1919752, upload-time = "2026-10-08T03:41:58.596Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6133,7 +6133,7 @@ zai = [
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "ag-ui-protocol", marker = "extra == 'ag-ui'", specifier = ">=0.1.10,<1" },
|
||||
{ name = "anthropic", marker = "extra == 'anthropic'", specifier = ">=1.9.0" },
|
||||
{ name = "anthropic", marker = "extra == 'anthropic'", specifier = ">=1.12.1" },
|
||||
{ name = "anyio", specifier = ">=4.7.0" },
|
||||
{ name = "argcomplete", marker = "extra == 'cli'", specifier = ">=3.5.0" },
|
||||
{ name = "boto3", marker = "extra == 'bedrock'", specifier = ">=1.42.63" },
|
||||
|
||||
Reference in New Issue
Block a user