Skip to content

About

Reference implementation for an Anthropic provider for the Amplifier project

Resources

Code of conduct

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

Amplifier Anthropic Provider Module

Claude model integration for Amplifier via Anthropic API.

Prerequisites

Offline contracts execute without real keys. Provision with uv sync --all-extras --dev, then uv pip install "amplifier-foundation @ git+https://github.com/microsoft/amplifier-foundation@a34b770ca604b88230449ac728d41dc23a2863a9". Run uv run python -I -S tests/run_offline.py -q. The runner sanitizes inherited credentials, supplies a fresh owned test home (including Windows profile variables), and creates the default pytest temp parent on a clean checkout. The test home is separate from pytest's temp tree and does not reuse inherited home/config locations. It stays alive through later dependency finalizers; shutdown then removes it and their home-scoped cache writes before final accounting. After isolation and environment sanitation, Windows first performs the real stdlib OS-version query to populate its own platform cache. Its completion and process count are separate bootstrap facts, not vendor requests or a zero-process guarantee; no OS identity or command output is recorded. The runner then installs a Python audit deny-and-record hook before site initialization, real Core/Foundation imports and pytest collection. From that boundary, audited DNS/socket attempts and unguarded subprocesses fail the run, even when provider fallback catches the exception. The sole INET connect exception is Windows' exact stdlib socketpair implementation connecting its own IPv4 stream socket to its live loopback listener's exact address (needed for asyncio's self-pipe). Receipts count this as internal_socketpair_connect, not a vendor request. Ordinary loopback connects, DNS and datagram sends remain denied; Linux predicate or fallback checks do not establish native Windows qualification. Pure Windows predicate tests use explicit socket facts; native fallback observations retain safe rejecting-clause/exception-type diagnostics and fail closed on missing listener capabilities. Guarded children use an explicit exact Python executable; Windows serialized audit commands must round-trip canonically with the same isolated no-site runner prefix. Shell or ambiguous commands remain denied. This is isolation for trusted tests, not a tamper-proof security sandbox for hostile Python, forks or native code outside Python's audit surface. Receipts are written to ai_working/tmp/offline-receipt.json; they contain local source paths and are private diagnostics, not public CI artifacts. Messages-only unit mocks receive explicitly synthetic, unspecified Models metadata only with the SDK-constructed default HTTP client and unchanged transports. Explicit caller clients (even the same SDK wrapper/transport types), SDK transport overrides and metadata/error mocks remain authoritative. Inherited mount contracts use scoped nonfunctional credentials.

The real image-understanding test is a separate required live gate, not a synthetic test. Offline inventory deselects it and records it as UNMET. After separate authorization for a paid vendor call and credential provisioning, run uv run pytest --required-live -q. Missing credentials fail closed, rather than silently skipping. Offline green does not satisfy that live requirement. Family discovery sorts semantic versions before snapshots, so multi-digit minor releases cannot be hidden from routing by lexical menu filtering.

  • Python 3.11+
  • UV - Fast Python package manager

Installing UV

# macOS/Linux/WSL
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"

Purpose

Provides access to Anthropic's Claude models (Claude 4 series: Sonnet, Opus, Haiku) as an LLM provider for Amplifier.

Contract

Module Type: Provider Mount Point: providers Entry Point: amplifier_module_provider_anthropic:mount

Supported Models

Canonical model listing

await provider.list_models() returns list[ModelInfo] from the bound endpoint/account's Models API. The default recommendations select the latest active numeric version in each Opus, Sonnet, Haiku and Fable family, plus a distinct newer preview. Matching aliases win over snapshots; snapshot-only ties use the newest date. Both YYYYMMDD and YYYY-MM-DD suffixes are recognized. Unknown families/spellings are conservatively omitted from recommendations.

await provider.list_models()  # Frozen provider defaults
await provider.list_models(filters={})  # All actual IDs, including old/unknown IDs
await provider.list_models(filters={"include": ["CLAUDE-HAIKU-*"]})
await provider.list_models(filters={"include": []})  # No recommendations

An explicit mapping replaces, not merges with, configured defaults. Common keys are include, exclude, and hide_dated; this provider also has latest_per_family and lifecycle. Patterns use case-insensitive, OS-independent globs; exclusion wins, original IDs remain exact. hide_dated suppresses a snapshot only if its matching alias survived the include/exclude selection. config.model_filters takes precedence over legacy filtered; otherwise filtered: false means unfiltered and filtered: true uses the recommendation defaults. Config and call filters are copied immutably. get_model_list_info() exposes fresh v1 metadata, including filters_schema, default_filters, unfiltered: {}, and override: "replace".

The SDK floor and lock are anthropic==1.12.0 (dependency range remains <2). Its native lifecycle filter accepts active, deprecated, and retired; its limit range is 1–1000. Defaults request lifecycle=["active"], limit=1000, and follow every validated after_id cursor. {} explicitly requests all three stages: omitting the native argument would hide retired records. Custom mappings also default to all stages unless they specify lifecycle. Raw lifecycle values are validated and checked locally before recommendations, even if a proxy ignores the native filter. These are catalog lifecycle claims, not proof of account inference entitlement. The original bound client and SDK header behavior are preserved, including configured workspace headers.

Each call fetches afresh. Genuine empty is []; malformed raw JSON, inconsistent or repeated cursors, and failures raise typed, sanitized kernel errors, never a partial result or cached/static fallback. Raw validation precedes SDK coercion. The candidate-only discover_models() API is superseded by list_models. The existing fallback-family cache updates only after complete success, using active stable known-family entries independently of include/exclude filters; listing never reads that cache. Selection-only inference settings and explicitly chosen older models remain usable and unchanged.

Known-model capability mapping and enable_1m_context policy remain intact. source: "provider-api" describes inventory provenance, not the source of every metadata value. For opaque IDs with no known capability mapping, supplied API limits are retained; absent limits use 0 to denote unknown, not a usable budget, with empty capabilities/defaults. No Sonnet capabilities are inferred.

Model support

  • claude-sonnet-5-5 - Claude Sonnet 5.5 (default; explicit model pins remain unchanged)
  • claude-sonnet-5 - Claude Sonnet 5 (previous default; still supported)
  • claude-opus-5 - Claude Opus 5 (most capable)
  • claude-opus-5-5 - Claude Opus 5.5 (adaptive thinking; $4/$20 input/output per MTok)
  • claude-haiku-4-5 - Claude Haiku 4.5 (previous Haiku; manual thinking)
  • claude-haiku-5-5 - Claude Haiku 5.5 (adaptive thinking; exact fixed model ID)

Haiku 5.5 support boundary

Haiku 5.5 supports text, vision, ordinary function tools, streaming, a native 1M context ceiling, 128K normal output, and a 512-token cache minimum. The existing enable_1m_context policy remains opt-in: without it the provider advertises a 200K context budget; it does not change the vendor's native limit. The Sonnet 5.5 global default and all explicit model/effort pins remain unchanged. No dated Haiku 5.5 alias is assumed.

Thinking defaults to adaptive on, with the vendor's medium effort when effort is omitted. low, medium, high, xhigh, and max reach output_config.effort without a manual budget. extended_thinking: false sends genuine thinking: {type: disabled} at high or below; xhigh/max with disabled thinking fails locally. Manual thinking_type: enabled, thinking_budget_tokens, and Sonnet's between_tools mode are unsupported. Normal sampling is omitted. Expert overrides reintroducing sampling or forbidden thinking controls fail before transport. Assistant prefill also fails locally: use structured outputs or system instructions and append a user turn, rather than changing signed history.

Forced required/any and named ordinary function tool choices are permitted and suppress thinking for that turn; a later auto/default turn resumes adaptive thinking. Ordered thinking (including empty signed blocks), tool calls/results, and images are retained. Synthetic offline signatures prove storage fidelity, not vendor/account replay acceptance.

The model's native computer capability is computer_toolset_20260801, not Haiku 4.5's legacy computer type. Explicit neutral v1 intent can compile to this GA toolset on the exact first-party HTTPS endpoint. Legacy native declarations still fail locally on Haiku 5.5; ordinary function tools, even one named computer, are unaffected. Offline adapter contracts do not establish live vendor/account replay or installed executor readiness.

Haiku pricing and cost metadata

Published first-party USD/MTok rates, checked 2026-11-14:

Prompt length Input Output Cache write 5m / 1h Cache read
Up to 100,000 tokens $0.10 $0.50 $0.125 / $0.20 $0.01
Above 100,000 tokens $0.50 $2.50 $0.625 / $1.00 $0.05

The tier counts fresh input + cache reads + cache writes once. Over 100,000 prompt tokens, the whole request (including output) uses the higher prices. Each request is priced independently; earlier requests are not repriced. Runtime uses the response usage and model, never a preflight token estimate. A complete TTL split controls 5m/1h write billing. Aggregate-only writes need an unambiguous TTL in the final request; inconsistent/incomplete splits or unresolved modifiers leave cost unavailable rather than falsely precise.

Usage.cost_usd carries the Decimal token cost; JSON and llm:response expose a decimal string or null. Unknown costs include a machine-readable metadata.anthropic_cost_unavailable reason (for example, cache_write_ttl_unavailable, response_speed_unavailable, or model_rates_unavailable). True zero is not unknown. The provider's registered session.cost contribution reports cost_usd: null after any unknown-cost call, including when later calls have known costs. Its known subtotal is not reported as a complete total. External consumers that sum only known costs may still show an unchanged or incomplete total: provider accounting alone does not establish consumer/UI completeness or a free call. An interrupted or cancelled stream that has emitted an SDK event also records an unknown-cost attempt, even if a later retry succeeds. Validation failures and requests rejected before a stream starts do not fabricate a billable attempt. Costs do not determine model discovery ranking. Sonnet 5 now uses its permanent $2/$10 input/output rates, with $0.20 reads and $2.50/$4 writes. Fable 5.2 uses $8/$40, $0.20 reads, and $10/$16 writes. Confirmed response fast mode doubles eligible model token costs, including Fable 5.2; US inference multiplies all categories by1.1. Geography uses the recognized actual usage.inference_geo first, then an explicit effective final-wire value (including extra_body precedence) if actual geo is absent. An invalid nonnull actual geo remains unknown. When both are absent, current geo-capable models return inference_geo_unavailable: an omitted parameter uses the workspace default, which can be US. Legacy geo-incapable models retain standard prices. This does not change request defaults or force global routing. The standalone compute_cost() helper retains its documented global default; runtime conversion does not assume that workspace setting. Pricing support does not enable new request capabilities. These are standard first-party token charges, not invoices, negotiated discounts, or separate server-tool fees.

list_models() derives fixed Core Pricing and runtime rates from the same provider-local Decimal source. Haiku has pricing=None: a flat rate would be misleading. Provider-local AnthropicModelInfo(ModelInfo) adds optional pricing_details (not inference defaults), retained by model_dump():

{
  "version": 1,
  "kind": "tiered",
  "currency": "USD",
  "unit": "per_million_tokens",
  "basis": "standard_global",
  "modifiers": {"us_inference": "1.1"},
  "threshold_tokens": 100000,
  "threshold_basis": "input_tokens+cache_read_input_tokens+cache_creation_input_tokens",
  "tiers": [
    {"up_to": 100000, "rates": {"input": "0.10", "output": "0.50", "cache_read": "0.01", "cache_write_5m": "0.125", "cache_write_1h": "0.20"}},
    {"above": 100000, "rates": {"input": "0.50", "output": "2.50", "cache_read": "0.05", "cache_write_5m": "0.625", "cache_write_1h": "1.00"}}
  ]
}

Fixed models use kind:"fixed" and rates instead of tier fields. Rates and multipliers are decimal strings; eligible models include modifiers.confirmed_fast:"2". Consumers should validate the version/shape, preserve unknown as unknown, never insert descriptive fields into inference configuration, and use actual usage cost instead of recomputing from menu rates. Old Core without the flat pricing field still receives the provider-local extension without importing a new Core symbol. Unsupported IDs, including Mythos, receive no guessed rates.

Sources: Haiku overview, migration, thinking, and pricing. Haiku 4.5's manual-thinking behavior is unchanged.

Opus 5.5 support boundary

Opus 5.5 supports normal text, vision, function tools, streaming, effort, and prompt-cost tracking. Its thinking is always adaptive: an unset effort uses the vendor's medium effort, and extended_thinking: false cannot disable model thinking (the provider warns and preserves any selected effort and output cap). Only on Anthropic's first-party endpoint, Opus 5.5 adapts one explicit native computer declaration (the documented legacy computer_20241022, computer_20250124, or computer_20251124, or computer_toolset_20260801) to computer_toolset_20260801. The adapter preserves the ToolSpec name as the executor dispatch alias and maps the native action member into input.action; ordinary function tools, including one named computer, remain ordinary function tools. The actual request assembly guards this first-party-only boundary: custom/proxy endpoints fail locally rather than claiming vendor-native toolset support, even though recognized Opus 5.5 capabilities advertise the native path. This adapter is not a claim that the new toolset is fully equivalent to prior native-computer contracts. Native computer actions are single-action responses: the request uses tool_choice: auto with the request-wide disable_parallel_tool_use: true; forced/named choice remains unsupported on Opus 5.5. key.repeat is passed through when the model emits it. An old executor without repeat support presses once and reports success even when repeat > 1. Update the computer-action executor to honor key.repeat before, or together with, activating this native Opus 5.5 path. Provider-derived headers do not add a legacy computer-use beta for the new toolset; an explicitly configured beta header is retained. As elsewhere in this provider, expert extra_request_params.tool_choice is an explicit wire-level override and wins over this derived default. This does not change the default model.

Provider-neutral computer-use v1

Tools opt in through an explicit ToolSpec.computer_use extra, transported by the existing native-tool-spec mechanism. Names do not opt in. ToolSpec.name remains the local dispatch alias; ToolSpec.parameters remains the canonical function argument schema.

ToolSpec(
    name="desktop",
    parameters=canonical_schema,
    type="function",
    computer_use={
        "version": 1,
        "operations": ["screenshot", "mouse_move", "left_click", "key"],
        "image_space": {"width": 1280, "height": 720, "unit": "pixel"},
        "unavailable_operations": {},
    },
)

Supply actual cached screenshot dimensions, not physical monitor dimensions or estimates. Only the exact Sonnet/Haiku 5.5 IDs and the existing recognized Opus 5.5 IDs use this provider's GA native compiler. It explicitly disables every member not listed in operations; unavailable operations cannot also be enabled. No computer-use beta header, legacy display declaration, concrete computer-bundle import, peer-provider import or native capability discovery is needed.

Unknown image dimensions (image_space: None), oversized screenshots, unverified models and unsupported endpoints use the original ordinary function schema with intent/diagnostics removed. Optional computer_use.native_required: True refuses those cases instead of falling back. Malformed intent, ambiguous aliases and mixed neutral/legacy declarations always fail locally. Model-only expert overrides are compiled using the final effective model; overrides of tools/messages/system/tool choice are refused for neutral requests. Use public request fields instead.

The native path uses auto/none only and disables parallel tool use request-wide. One vendor member becomes one local-alias singleton canonical batch:

{"version":1,"actions":[{"action":"left_click","coordinate":[426,87]}],"capture_after":true}

Coordinates stay in full-screenshot pixels; durations stay in seconds and scroll amounts in wheel notches. The adapter supports the 17 documented GA member names when explicitly enabled, but refuses unqualified modifiers, repeated key presses other than repeat: 1, path variants, missing drag starts, noninteger native durations/amounts and invalid geometry. It does not guess alternative semantics. Pending safety requirements or unknown native member metadata are refused before returning executable calls; no acknowledgement is fabricated. Native replies without the declared family are refused unless they name a separately declared ordinary function (for example, an ordinary screenshot tool).

Both canonical content and compatibility tool calls retain provider-owned provenance. Replay reconstructs the original vendor input, never the executor batch, and binds tool results by call ID. Alias/image-space/provenance changes refuse replay rather than silently downgrading it. Text, images and is_error survive count/create/stream and second-turn projection without mutating the caller. Old installed tools without neutral intent keep their existing flat native inputs; they do not receive the new batch contract.

Protocol sources: computer tool reference, toolset reference, and Haiku migration. Repository-native offline tests use real Core and SDK types with transport fakes. Paid native vendor checks, signed/account replay, actual final screenshots, platform/backend input and the required live-vision gate remain separate qualification requirements. Offline pricing checks do not establish billed cost.

Configuration

[[providers]]
module = "provider-anthropic"
name = "anthropic"
config = {
    default_model = "claude-sonnet-5-5",
    max_tokens = 8192,
    temperature = 1.0,   # Silently ignored by models without sampling support (sonnet-5, opus-4.7+)
    raw = false          # Include full request/response payloads in llm:* events
}

See the Configuration Reference table below for every surviving key.

Signed-thinking continuation

Anthropic binds preserved thinking to its preceding system, tools and messages. Compaction, a replaced observation, changed tools or resumed history can legally change that prefix. The provider never replays old observations, rewrites canonical history, re-executes tools, or guesses validity from a local cache. It preserves native thinking/redacted blocks and tool results in order; Anthropic validates prefix, model and account compatibility.

thinking_prefix_mismatch_behavior defaults to auto. On the exact first-party HTTPS endpoint and documented preserved-thinking models (Fable/Mythos 5.1–5.2, Opus/Sonnet/Haiku 5.5), it sends thinking.block_binding.prefix_mismatch_behavior: drop_block with thinking-binding-controls-2026-08-01. This lets the server remove only failing thinking and its dependent chain; unchanged-prefix reasoning survives. Fable 5.2 accepts this control without specifying a thinking mode. Sonnet/Haiku 5.5 require adaptive mode; the provider does not change disabled/between-tools modes to enable the control. Unsupported modes/endpoints retain their native behavior, including nonretryable errors rather than speculative stripping or retries.

Set this config key (or per-call option) to error for strict diagnostics, drop_block to explicitly opt a compatible custom endpoint into the native contract, or YAML null to leave vendor defaults unchanged. An explicit native block_binding in extra_request_params.thinking or extra_request_params.extra_body.thinking wins, including error, {} or null. Explicit null thinking and an explicitly emptied beta header are not repopulated. No API compatibility is claimed for custom endpoints.

Responses retain the vendor's input_transformations in metadata.anthropic_input_transformations, including empty arrays. Streaming captures message_start and any replacement report on a final message_delta. Paths/reasons distinguish prefix drops, model/account incompatibility and allowed mismatches. Count preflight uses the same effective thinking, content and headers as dispatch; it never registers generation provenance or triggers tool effects. Tampered/undecryptable signatures remain nonretryable errors.

Selective dropping is compatibility recovery, not a cure for avoidable prefix churn. Repeated drops can increase output tokens, latency and change quality. Keep stable context unchanged, append new instructions when safe, and monitor the transformation report. See Anthropic's preserved-thinking contract.

New native blocks carry internal thinking_provenance: {version: 1, provider: anthropic} extras. Explicitly foreign provider signatures are never submitted as native Anthropic signatures: readable summaries become text and foreign opaque redacted data is omitted. Legacy untagged signed blocks are replay candidates, not authenticated origin evidence; the server still validates them. Provenance is stripped from the native wire and does not lock otherwise compatible Anthropic model switches.

Reasoning Effort

The reasoning_effort config key (canonical — it matches the kernel's portable request.reasoning_effort field) sets a session-level default reasoning effort applied to every request, so you can opt into stronger reasoning once instead of supplying it per-request. The legacy effort key remains a working alias; when both are set, reasoning_effort wins (a warning is logged).

providers:
  - module: provider-anthropic
    config:
      default_model: claude-opus-4-8
      reasoning_effort: xhigh  # legacy alias: effort

This enables extended thinking. In this provider, effort (like the kernel's portable request.reasoning_effort) deliberately maps to Anthropic extended thinking — setting it engages thinking and controls its depth, the same way OpenAI's reasoning effort engages its reasoning. At the Anthropic API level effort and thinking are independent primitives; coupling them is Amplifier's "reason harder" product semantics. Because it turns thinking on for every call, leave effort blank unless you want stronger (and more expensive) reasoning by default.

Accepted values:

Value Meaning Availability
low Minimal thinking, most token-efficient All thinking-capable models
medium Balanced All thinking-capable models
high High intensity (not the omitted default on Haiku 5.5 or Opus 5.5) All thinking-capable models
xhigh Extended capability for long-horizon agentic/coding work Opus 4.7+, Haiku 5.5
max Maximum capability, no effort-based token constraint Opus 4.8+, Haiku 5.5 (normal output caps still apply)

Precedence (highest wins): per-call effort kwarg → request.reasoning_effort (set by the orchestrator) → this effort config default. Note the per-call effort kwarg is an output_config.effort-only override; the reasoning_effort chain is what enables thinking.

Notes:

  • Invalid values (e.g. ultra, EXTRA HIGH) are normalised (trimmed/lowercased) and, if still unrecognised, ignored with a warning — they never silently turn thinking on.
  • output_config.effort is only emitted for models the capability matrix marks as supporting it (including Opus 4.7+ and Haiku 5.5). On other thinking-capable models the extended-thinking mapping still applies. Broadening this to Sonnet 4.6 and Opus 4.5/4.6 (which Anthropic also supports) is tracked as a follow-up.
  • xhigh/max are omitted from output_config.effort on models whose capability matrix doesn't list them (a warning is logged), falling back to adaptive thinking.
  • This key is exposed through amplifier provider use (shown for thinking-capable models), so it can be set interactively without hand-editing YAML.

Thinking Budget

thinking.budget_tokens is the entire reasoning dial on models without output_config.effort support -- Haiku 4.5 among them, where effort never reaches the wire at all. Two config keys control it:

providers:
  - module: provider-anthropic
    config:
      default_model: claude-haiku-4-5
      extended_thinking: true       # turn thinking on WITHOUT choosing an effort
      thinking_budget_tokens: 8000  # explicit budget, honored as written

Precedence (highest wins): per-call thinking_budget_tokens kwarg -> thinking_budget_tokens config -> the budget implied by reasoning_effort (low -> 4096, everything else -> the model default) -> the model default.

An explicit budget outranks the effort-implied one, because the effort ladder is a derived default and a configured number is caller intent. Setting only reasoning_effort is unchanged: you still get 4096 for low and the model default otherwise.

Turning thinking on: reasoning_effort enables thinking as a side effect, but it also selects a thinking depth and, on Opus 4.7+, sets output_config.effort. extended_thinking: true enables thinking and nothing else, which is what you want when the budget is the only dial you care about. extended_thinking: false is an explicit opt-out that overrides a configured reasoning_effort; a per-call extended_thinking kwarg overrides both.

It is never silently discarded. If an explicitly requested budget does not reach thinking.budget_tokens, a warning names the key, the value you asked for, the value actually sent, and why. That happens when:

Situation What is sent Remedy
Thinking is not enabled no thinking block at all set reasoning_effort or extended_thinking: true
Model has no thinking support (e.g. Haiku 3.5) no thinking block at all use a thinking-capable model
Resolved thinking_type is adaptive {"type": "adaptive"} -- the API forbids budget_tokens here; your value sizes max_tokens instead set thinking_type: enabled
Value is outside the model's limits the clamped value (min 1024, max the model's output ceiling) pick a value inside the range

A non-integer value is ignored with a warning and the resolved default is used -- a typo does not raise out of every request.

Debug / Raw Payload Capture

debug and raw_debug never existed as real keys -- this section documented them in error for a long time. The real key is raw (boolean, default false): when true, full request/response payloads are attached to the llm:request / llm:response events for inspection.

Retry and Error Handling

The provider disables SDK built-in retries (max_retries=0) and manages retries itself via amplifier_core.utils.retry.retry_with_backoff(). This gives the provider full control over backoff timing, retry-after header honoring, and per-error-class delay scaling.

Error Translation

SDK exceptions are translated to kernel errors before the retry loop sees them. All translations preserve the original exception as __cause__ for debugging.

SDK Exception Condition Kernel Error Status Retryable
RateLimitError 429 RateLimitError 429 Yes
OverloadedError 529 ProviderUnavailableError 529 Yes (10× backoff)
InternalServerError 5xx ProviderUnavailableError 5xx Yes
AuthenticationError 401 AuthenticationError 401 No
BadRequestError prompt/context window overflow (e.g. prompt is too long: ... tokens > ... maximum) ContextLengthError 400 No
BadRequestError safety / content filter / blocked ContentFilterError 400 No
BadRequestError other InvalidRequestError 400 No
APIStatusError 403 AccessDeniedError 403 No
APIStatusError 404 NotFoundError 404 No
APIStatusError other non-5xx LLMError — No
asyncio.TimeoutError — LLMTimeoutError — Yes
Other — LLMError — Yes

Backoff Formula

Each retry delay is computed as follows:

base_delay   = min_retry_delay × 2^(attempt - 1)
capped_delay = min(base_delay, max_retry_delay)
scaled_delay = capped_delay × delay_multiplier          # 1.0 for most errors, 10.0 for 529
final_delay  = max(scaled_delay, retry_after)            # server retry-after as floor
sleep        = final_delay ± (final_delay × jitter)      # randomised ± jitter fraction

Example: 529 Overloaded (10× multiplier, defaults)

Attempt base_delay capped ×10 Sleep
1 1s 1s 10s 10s
2 2s 2s 20s 20s
3 4s 4s 40s 40s
4 8s 8s 80s 80s
5 16s 16s 160s 160s

Total wait ≈ 310s (~5 min) before the request is abandoned.

Retry Configuration

providers:
  - module: provider-anthropic
    config:
      max_retries: 5
      min_retry_delay: 1.0
      max_retry_delay: 60.0
      retry_jitter: true
      overloaded_delay_multiplier: 10.0
Key Default Description
max_retries 5 Maximum retry attempts before giving up
min_retry_delay 1.0 Base delay in seconds for the first retry
max_retry_delay 60.0 Cap on the base delay (before multiplier)
retry_jitter true Randomise each retry delay to avoid thundering-herd retries. Boolean. A numeric value such as 0.2 is parsed as false and disables jitter -- use true/false
overloaded_delay_multiplier 10.0 Multiplier applied to delays for 529 Overloaded errors

Events

A provider:retry event is emitted before each retry sleep with the following fields:

Field Description
provider Provider name ("anthropic")
model Model being called
attempt Current retry attempt number
max_retries Configured maximum retries
delay Computed sleep duration in seconds
retry_after Server retry-after value (or null)
error_type Kernel error class name
error_message Error description

Adaptive Input Budgeting and Overflow Recovery

The provider exposes optional asynchronous request_budget() and recover_context_overflow() methods for a retention-aware context manager. request_budget() uses Anthropic's count endpoint for the existing shared assembled request, then adds a fixed 4,096-token safety reserve. It projects only count-endpoint-compatible fields: the resolved model, messages, system, tools, tool choice, thinking, output configuration, cache control, and required beta headers. The dispatch-only max_tokens value is never sent to count.

Count calls have a five-second deadline and no retry. Unknown model windows, count errors (including 429), timeouts, and malformed count responses return None; no byte-ratio, character heuristic, cached count, or other estimate is used. Each unavailable reason emits one safe warning per resolved model, without request or response data. An explicit ChatRequest.max_output_tokens remains the wire output cap even when adaptive thinking would otherwise enlarge an implicit cap.

When this endpoint returns a valid count, request_budget() keeps its existing budget fields and additively includes:

"measurement": {
    "kind": "provider_count",
    "source": "anthropic.messages.count_tokens",
    "input_tokens": 12345,  # raw count-endpoint input_tokens value
}

The provider advertises this optional provenance through request_budget:provider_count. Consumers must negotiate that capability and still treat an absent or malformed measurement as unavailable; existing budget-only callers continue to use the unchanged budget fields.

Recovery is narrower still. Only a structured Anthropic invalid_request_error HTTP 400 whose request id and exact input-only overflow counts are present can authorize it. A strict documented combined input/max_tokens grammar is also eligible only when its positive arithmetic derives a lower input limit. The private feedback is bound to the same unchanged request, options, and assembled wire payload; it is consumed once and permits one smaller context retry. Other joint input/output limit errors, malformed responses, and any stream that has produced its first SDK event are never eligible. The provider never retries a context error by itself.

Conversation-Region Prompt Caching

The provider places up to four Anthropic cache breakpoints per request: one on the system prompt, one on the last tool definition, and up to two rolling ones inside the conversation. The conversation breakpoints are the ones that matter for a long agent loop, because they are the only ones that grow with the transcript.

Placing them safely requires knowing which trailing messages are regenerated per request (a <system-reminder> with a live clock, a git-status blob) rather than appended to history. A cached prefix ending in regenerated content can never be reproduced, so it is written every turn and never read -- strictly worse than not caching, because writes are billed at 1.25x.

The provider answers that question two ways, in this order:

  1. Declared -- Message.metadata = {"ephemeral": True} (and "persisted" for content that is written into canonical history). Orchestrators that populate this get the most precise placement.
  2. Observed -- cache_infer_stability_from_history (default true). The provider fingerprints each request's message array and, on the next request in the same conversation, takes len(previous) - longest_common_prefix as the measured unstable-suffix length.

The observation is used as a floor with the declared value, never a replacement: it can only ever make placement more conservative.

providers:
  - module: provider-anthropic
    config:
      enable_prompt_caching: true                 # default: true
      cache_infer_stability_from_history: true    # default: true

Why the default matters. Before observation existed, a deployment whose orchestrator never populated Message.metadata got no conversation-region breakpoint at all -- ever. Measured on one coding-agent node visit (164 provider calls, 60 minutes): cache_read_input_tokens frozen at exactly 10,995 (the system + tools blocks) on every single call while the prefix grew from 24K to 228K tokens. 1.79M cache_read against 24.7M billed input -- a 6.8% hit ratio.

Reproduced and fixed under a 12-call tool-using loop against claude-haiku-4-5 (scripts/prompt_cache_live_probe.py, no metadata anywhere, regenerated tail):

calls 2-12 hit ratio uncached tokens, calls 2-12
before 71.5% (cache_read frozen at 11,180) 44,208
after 95.0% 411

Two things follow from measuring rather than declaring:

  • The first request in a conversation places nothing. There is no prior request to compare against, so there is no evidence yet. Caching engages from the second request and the first hit lands on the second or third. That warm-up is the price of not guessing.
  • A conversation whose leading message is regenerated per request is detected and reported. Anthropic matches a cached prefix from the start of the request, so nothing downstream of a volatile first message can ever hit. The provider logs a warning naming the likely cause and places no conversation breakpoint, rather than billing a write every turn for a prefix that can never be read.

Set cache_infer_stability_from_history: false to revert to strict metadata-only behaviour.

Minimum cacheable prefix. Anthropic silently ignores a breakpoint whose prefix is below the model's minimum. Measured for claude-haiku-4-5 (2026-09-06): a 4,369-token system+tools prefix produced no cache write on two consecutive identical requests, while 8,659 tokens cached immediately -- i.e. the effective floor is nearer 4096 than the 2048 the docs list for Haiku. A small system prompt therefore gets no system-block caching at all, in any version of this provider.

Prompt Cache TTL

By default, prompt-cache breakpoints use Anthropic's standard 5-minute TTL. The cache_stable_region_ttl_1h config key opts into a 1-hour TTL for the two most stable cache breakpoints only:

providers:
  - module: provider-anthropic
    config:
      enable_prompt_caching: true          # required -- see below
      cache_stable_region_ttl_1h: true

What it covers: the system prompt and tool-definition breakpoints only. It deliberately never applies to conversation-region breakpoints, which move every turn on a rolling basis and would pay the higher write premium (below) on content that's about to be superseded anyway. Extending the conversation region to a longer TTL is a separate, currently-unimplemented idea tracked upstream in microsoft/amplifier#337. See the design comment above self.cache_stable_region_ttl_1h in amplifier_module_provider_anthropic/__init__.py for the full rationale.

The economics: Anthropic bills 1-hour TTL cache writes at 2x the base input-token rate, versus 1.25x for the default 5-minute TTL. That's a real up-front cost -- but a longer TTL means the write survives more calls before it needs to be repeated. Once a cache entry is reused across roughly two or more reads inside the 1-hour window that a 5-minute TTL would have missed (because the previous call was more than 5 minutes ago), the 1h TTL comes out ahead. It helps most for:

  • Long-running sessions with gaps between calls longer than 5 minutes (e.g. a human pausing between turns, a scheduled/cron-triggered agent).
  • Stable system prompts and tool sets that don't change turn-to-turn -- exactly the two regions this knob targets.

If your calls are consistently more frequent than every 5 minutes, the default 5-minute TTL already keeps the cache warm at the cheaper 1.25x write rate, and enabling this knob only adds cost.

Requires prompt caching itself. Because this knob only affects existing cache breakpoints, it is a no-op when enable_prompt_caching is false (or left at its false default) -- the provider will not append the extended-cache-ttl-2025-04-11 beta header in that case, and logs a one-line notice explaining why. Enable both together.

Left unset by default (rather than defaulting to false): this makes "no opinion, use the provider's own 5-minute default" a real, distinguishable third state in the config wizard, separate from an explicit opt-out.

Beta Headers

Anthropic provides experimental features through beta headers. Enable these features by adding the beta_headers configuration field.

Configuration

Single beta header:

providers:
  - module: provider-anthropic
    config:
      default_model: claude-sonnet-4-5
      beta_headers: "context-1m-2025-08-07"  # Enable 1M token context window

Multiple beta headers:

providers:
  - module: provider-anthropic
    config:
      default_model: claude-sonnet-4-5
      beta_headers:
        - "context-1m-2025-08-07"
        - "future-feature-header"

1M Token Context Window

1M context is generally available, on by default, and billed at standard pricing on the previously supported models that have it (Opus 5/4.8/4.7/4.6, Sonnet 5/4.6, Fable 5/5.1, Mythos 5/Preview). No beta header is required, and there is no long-context price premium (Anthropic: Context windows, verified 2026-08-29). Those models cap output at 128K tokens per request regardless of context size.

Haiku 5.5 also has a native 1M window, but has prompt-length pricing tiers; see its per-request pricing boundary above.

The enable_1m_context config key does not change what the API accepts. It only sets the context window this provider advertises to Amplifier's context manager (200K vs 1M), which determines how much conversation history is kept per request -- a cost decision. It defaults to false.

Notes

  • Beta features are experimental and subject to change
  • Check Anthropic's documentation for available beta headers
  • Beta headers are optional - existing configurations work unchanged
  • Invalid beta headers will cause API errors (fail fast)
  • Beta header usage is logged at initialization for observability

Configuration Reference

House-style key reference. ✅ = wizard-visible ConfigField, ⚙️ = settings-only (fully functional, not asked in the wizard).

Key Default Surface Description
api_key (env ANTHROPIC_API_KEY) ✅ Anthropic API key
base_url https://api.anthropic.com ✅ Custom endpoint
default_model claude-sonnet-5-5 (model picker) Model used when a request does not name one
reasoning_effort (unset) ✅ low|medium|high|xhigh|max. Enables extended thinking. Legacy alias: effort (deprecated)
extended_thinking (unset) ⚙️ Turn extended thinking on/off without choosing an effort. Overrides the reasoning_effort implication; a per-call kwarg overrides this
thinking_budget_tokens (model default) ⚙️ Explicit thinking.budget_tokens. Outranks the effort-implied budget; warns if it can't reach the wire
thinking_budget_buffer 8192 ⚙️ Headroom added to the budget when sizing max_tokens
thinking_type adaptive ⚙️ adaptive|enabled. adaptive lets the model manage its own budget (and forbids budget_tokens); falls back to enabled on models without adaptive support
thinking_prefix_mismatch_behavior auto config / per-call auto, drop_block, error, or null. Selective server recovery for legal prefix edits; see signed-thinking continuation. Does not change explicit thinking modes or native controls
enable_1m_context false ✅ Advertise the 1M context window (more history kept = higher cost)
cache_stable_region_ttl_1h (unset) ✅ 1h cache TTL for system prompt + tools. 2x write cost, fewer writes
enable_prompt_caching true ⚙️ Place cache breakpoints
cache_infer_stability_from_history true ⚙️ Measure the regenerated-per-request tail by comparing consecutive requests, when Message.metadata is not populated. Used as a floor with the declared value, never a replacement. false = strict metadata-only
max_tokens (model ceiling) ⚙️ Output token cap
temperature 0.7 ⚙️ Ignored by non-sampling models (Sonnet 5, Opus 4.7+)
timeout null ⚙️ Optional request deadline, seconds; default waits for completion
close_timeout 5.0 ⚙️ Hard bound on closing the HTTP client at session teardown, seconds. httpx's aclose() has no deadline of its own and blocks forever on a half-closed (CLOSE-WAIT) connection; on timeout the client is abandoned with a WARNING and the socket is reclaimed at process exit
priority 100 ⚙️ Provider selection priority (lower wins)
raw false ⚙️ Include full request/response payloads in llm:request/llm:response events
fallback_on_overload false ⚙️ Downgrade one ladder rung (fable/mythos → opus → sonnet → haiku) after persistent 529s
fallback_retry_count 2 ⚙️ 529 retries before downgrading
fallback_cooldown_seconds 300 ⚙️ How long a downgrade window stays open
fallback_models (unset) ⚙️ Per-family target overrides, e.g. {opus: claude-opus-4-8}
persist_fallback_state false ⚙️ Share downgrade windows across processes
refusal_fallback_enabled true ⚙️ Retry once on finish_reason="refusal", via the same ladder as overload
extra_request_params (unset) ⚙️ Escape hatch for Messages API params this provider doesn't model; merged last, user-wins

An instance selected for Haiku may legitimately inherit fallback_on_overload from a higher-tier parent. Haiku has no lower tier, so overload downgrade is unavailable for that request; its normal retry policy is unchanged. This is an informational diagnostic, not a warning to remove the parent's setting. The option remains available for higher-tier requests on the same instance.

Removed keys (still recognized, warn with a migration message): fallback_sonnet_model, fallback_haiku_model (use fallback_models), refusal_fallback_model (refusal fallback now follows the same ladder as overload -- see Fallback section).

Environment Variables

export ANTHROPIC_API_KEY="your-api-key-here"

Usage

# In amplifier configuration
[provider]
name = "anthropic"
default_model = "claude-sonnet-5-5"

Features

  • Streaming support
  • Tool use (function calling)
  • Vision capabilities (on supported models)
  • Token counting and management
  • Message validation before API calls (defense in depth)

Graceful Error Recovery

The provider implements automatic repair for incomplete tool call sequences:

The Problem: If tool results are missing from conversation history (due to context compaction bugs, parsing errors, or state corruption), the Anthropic API rejects the entire request, breaking the user's session.

The Solution: The provider automatically detects and repairs missing tool_results by injecting synthetic results:

  1. Repair before validation - Detects missing tool_results and injects synthetic ones
  2. Make failures visible - Synthetic results contain [SYSTEM ERROR: Tool result missing] messages
  3. Maintain conversation validity - API accepts repaired messages, session continues
  4. Enable recovery - LLM acknowledges error and can ask user to retry
  5. Provide observability - Emits provider:tool_sequence_repaired event with repair details
  6. Validate remaining - After repair, strict validation catches any remaining inconsistencies

Example:

# Anthropic format (after _convert_messages)
messages = [
    {
        "role": "assistant",
        "content": [
            {"type": "tool_use", "id": "toolu_123", "name": "get_weather", "input": {...}}
        ]
    },
    # MISSING: {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "toolu_123", ...}]}
    {"role": "user", "content": "Thanks"}
]

# Provider repairs by injecting synthetic result:
# Either appends to existing user message or inserts new one
{
    "role": "user",
    "content": [{
        "type": "tool_result",
        "tool_use_id": "toolu_123",
        "content": "[SYSTEM ERROR: Tool result missing]\n\nTool: get_weather\n..."
    }]
}

Observability: Repairs are logged as warnings and emit provider:tool_sequence_repaired events for monitoring.

Philosophy: This is graceful degradation following kernel philosophy - errors in other modules (context management) don't crash the provider or kill the user's session

Dependencies

  • amplifier-core>=1.0.0
  • anthropic>=1.0.0,<2.0.0

Contributing

Note

This project is not currently accepting external contributions, but we're actively working toward opening this up. We value community input and look forward to collaborating in the future. For now, feel free to fork and experiment!

Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit Contributor License Agreements.

When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.

Trademarks

This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.

Waiting for model work

Completion and streaming requests have no elapsed-time or read-idle deadline by default. They wait for completion, explicit cancellation, or a provider/transport error. Set timeout (seconds) to opt into a request deadline; null leaves model work unbounded. Connection and pool acquisition remain bounded to 5 seconds, and existing close_timeout cleanup limits are unchanged.

About

Reference implementation for an Anthropic provider for the Amplifier project

Resources

Code of conduct

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages