Budgeted, cost-aware, streaming-safe retries for Anthropic LLM calls.
Project description
Damper
Reliability control for Anthropic LLM clients.
Damper is an LLM reliability library. v0.1 starts with retry control for the Anthropic Python SDK.
It supports Anthropic and AsyncAnthropic. It intercepts
messages.create() and messages.stream(). Other client methods pass through
unchanged.
from anthropic import Anthropic
from damper import Policy, resilient
client = resilient(Anthropic(), policy=Policy())
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
messages=[{"role": "user", "content": "Explain retry storms"}],
)
print(response.damper.attempts)
print(response.damper.retry_cost_usd)
Damper v0.1.0 supports wrapped Anthropic clients only. The API may continue to evolve while the project is below 1.0.
Why Damper exists
An LLM call has a different failure profile from a typical HTTP request. It may take a long time to produce output, fail partway through a stream, or become slow across many requests at once. Retrying also repeats some amount of token cost.
The Anthropic SDK already handles retries for an individual request. That is useful for isolated failures. The problem appears when many requests fail together and each one starts its own retry sequence.
10,000 logical requests
x 3 attempts
= 30,000 provider attempts
During a provider brownout, that extra traffic can make the situation worse. Damper adds retry admission, cost limits, streaming rules, and telemetry around calls made through the same wrapped client.
What v0.1 handles
Damper v0.1 provides:
- a client-local retry budget
- a cumulative retry cost ceiling
- Anthropic-specific error classification
- exponential backoff with full jitter
- Anthropic
Retry-Afterhandling - safe retry behavior for streaming calls
- OpenTelemetry request and attempt spans
- response metadata
- a deterministic outage demo
The scope is intentionally narrow. v0.1 is about retry discipline for Anthropic calls.
Retry budget
Each wrapped client has its own fixed-window retry budget.
A new window starts with retry_budget_min_tokens units of retry capacity.
Successful first attempts add capacity according to retry_budget_ratio.
An authorized retry consumes one unit. Unused capacity does not carry into the
next window.
Within one window, the number of authorized retries is bounded by:
retry_budget_min_tokens
+ retry_budget_ratio * successful first-attempt successes
With the default policy, each window starts with 10 units. The ratio is 0.1,
so 100 successful first attempts in the same window add 10 more units.
When the provider degrades, first attempts stop succeeding. The budget then stops growing, existing capacity drains, and further retries are denied. This limits retry amplification without requiring a separate gateway or service.
Streaming behavior
Damper retries a streaming call only while no output content delta has been received.
Once the first output content delta arrives, later failures are surfaced and the stream is not replayed. The caller may already have consumed partial output, so replaying the request could duplicate work or produce inconsistent results.
Retry cost ceiling
A retry repeats part or all of the request cost. This matters for large prompts and long output reservations.
Damper can block another retry when the cumulative estimated retry cost would cross a configured limit:
from damper import Policy
policy = Policy(max_retry_cost_usd=0.05)
Damper's built-in model prices are a versioned snapshot of Anthropic's published pricing. They are used only for retry-cost estimates and are not billing data.
Anthropic may change model pricing between Damper releases. Applications that
need stricter control can provide an updated price_table through Policy
without waiting for a new Damper release.
If a retry cost ceiling is configured and Damper cannot determine the model
price, the retry is denied and RetryCostCeilingHit is raised. Applications
can provide a price_table through Policy for models that are not included
in Damper's built-in table.
A custom price_table replaces the built-in table rather than merging with it.
Configuration
Policy contains the public configuration for v0.1:
from damper import Policy
policy = Policy(
max_attempts=3,
per_attempt_timeout=60.0,
max_retry_cost_usd=None,
retry_budget_ratio=0.1,
retry_budget_window=60.0,
retry_budget_min_tokens=10,
backoff_base=1.0,
backoff_max=30.0,
respect_retry_after=True,
on_budget_exhausted="raise",
price_table=None,
classifier=None,
)
When the retry budget blocks a retry:
on_budget_exhausted="raise"raisesRetryBudgetExhaustedon_budget_exhausted="passthrough"surfaces the last provider error
Policy is frozen. Create a new instance when you need different settings.
Async clients
resilient() also supports AsyncAnthropic:
import asyncio
from anthropic import AsyncAnthropic
from damper import Policy, resilient
async def main() -> None:
client = resilient(AsyncAnthropic(), policy=Policy())
response = await client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
messages=[{"role": "user", "content": "Explain retry storms"}],
)
print(response.damper.attempts)
asyncio.run(main())
Telemetry
Damper emits one damper.request span for the logical request and one
damper.attempt span for each provider attempt.
Stable Damper attributes include:
damper.attempts
damper.outcome
damper.retry_budget.balance
damper.retry_budget.ratio
damper.cost.estimate_usd
damper.cost.retry_usd
damper.attempt.error_class
damper.attempt.backoff_s
damper.provider
damper.model
Some attributes are conditional. Provider and model attributes require those
values to be present on the request. Cost estimates require a priced model.
damper.attempt.backoff_s is added only when another retry follows the
attempt.
If no OpenTelemetry SDK or exporter is configured, these calls become no-ops.
How is Damper different from Tenacity?
Tenacity is a mature general-purpose retry library. It gives developers flexible primitives for retrying Python functions.
Damper is focused on reliability around LLM calls. In v0.1 it provides Anthropic-specific retry control directly:
- fixed-window retry budgets shared across calls on one wrapped client
- cumulative retry cost ceilings
- one Damper-owned retry loop for intercepted Anthropic calls
- no retry after the first output content delta
- Anthropic-specific error classification and
Retry-Afternormalization - response metadata and OpenTelemetry spans for retry decisions
Use Tenacity when you need general-purpose retry primitives or want to build your own policy.
Use Damper when you want these controls around Anthropic calls without assembling the policy and SDK integration yourself.
How is Damper different from the Anthropic SDK retries?
The Anthropic SDK handles retries inside an individual request.
Damper adds admission control across calls made through the same wrapped client. It also adds:
- client-local retry budgeting
- retry cost estimation
- response metadata
damper.requestanddamper.attemptspans
For intercepted calls, Damper disables the SDK retry loop and runs one Damper-owned loop. The two retry layers do not stack.
Install
pip install damper
For local development:
git clone https://github.com/damper-ai/damper.git
cd damper
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
On Windows PowerShell:
git clone https://github.com/damper-ai/damper.git
cd damper
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -e ".[dev]"
Examples
examples/01_basic.py # basic client wrapping and response metadata
examples/02_outage_demo.py # deterministic retry amplification demo
examples/03_telemetry.py # console output for Damper spans
The outage demo is deterministic and does not use the network:
python examples/02_outage_demo.py --fake
In the demo, every request fails twice before it succeeds. A plain three-attempt retry loop therefore makes 3,000 provider attempts for 1,000 logical requests. With the configured Damper policy, the retry budget drains and the run makes 1,010 provider attempts.
Naive per-request retries:
logical requests: 1000
provider attempts: 3000
amplification: 3.00x
Damper:
logical requests: 1000
provider attempts: 1010
amplification: 1.01x
budget exhausted events: 995
These numbers belong to this deterministic simulation. They are not a claim
about every outage or every policy. The demo uses 1.1x as its pass or fail
threshold.
Non-goals for v0.1
Damper v0.1 does not include:
- caching
- routing
- prompt management
- evals
- guardrails
- a standalone proxy
- multi-provider support
- distributed retry budgets
- circuit breakers
- hedging
- adaptive timeouts
- fallback chains
The current release focuses on budgeted, cost-aware, streaming-safe retries for Anthropic calls.
Roadmap
v0.1: retry discipline for Anthropic
v0.2: request hedging
Each version should be useful on its own before the next milestone starts.
Development
Run the same checks used by CI:
ruff check .
mypy .
pytest
python examples/02_outage_demo.py --fake
Tests must not require network access, provider API keys, or external services.
See CONTRIBUTING.md for setup and contribution guidelines.
CLAUDE.md contains additional guidance for coding agents.
Repository layout
damper/
__init__.py
_wrapper.py
_executor.py
budget.py
cost.py
classify.py
backoff.py
telemetry.py
prices.py
py.typed
tests/
examples/
License
Apache-2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file damper-0.1.0.tar.gz.
File metadata
- Download URL: damper-0.1.0.tar.gz
- Upload date:
- Size: 59.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c4245cec8df3ee6eabfd4062d5d5b05dffbe6f2d60bc7d3ee5ef05ee21852ae
|
|
| MD5 |
cef73e7138884c054d2535a8f5a51022
|
|
| BLAKE2b-256 |
fc59fbb472da696c21291f63581693d62056d810ab9f711827b43f7929a72909
|
File details
Details for the file damper-0.1.0-py3-none-any.whl.
File metadata
- Download URL: damper-0.1.0-py3-none-any.whl
- Upload date:
- Size: 39.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2f8fb1c5e9ee2f353829051e608ee61f3b2d654f2f7c77e5a13f810033e76796
|
|
| MD5 |
7fde2276646afeb658090f07d7db51ce
|
|
| BLAKE2b-256 |
35061bc6ea4dfbd85e5318219d86a51d5822039b17963fc7a47fb34c51a2ac0a
|