Skip to main content

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-After handling
  • 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" raises RetryBudgetExhausted
  • on_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-After normalization
  • 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.request and damper.attempt spans

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

damper-0.1.0.tar.gz (59.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

damper-0.1.0-py3-none-any.whl (39.4 kB view details)

Uploaded Python 3

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

Hashes for damper-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5c4245cec8df3ee6eabfd4062d5d5b05dffbe6f2d60bc7d3ee5ef05ee21852ae
MD5 cef73e7138884c054d2535a8f5a51022
BLAKE2b-256 fc59fbb472da696c21291f63581693d62056d810ab9f711827b43f7929a72909

See more details on using hashes here.

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

Hashes for damper-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2f8fb1c5e9ee2f353829051e608ee61f3b2d654f2f7c77e5a13f810033e76796
MD5 7fde2276646afeb658090f07d7db51ce
BLAKE2b-256 35061bc6ea4dfbd85e5318219d86a51d5822039b17963fc7a47fb34c51a2ac0a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page