llm-circuit-breaker
A tiny, readable circuit breaker for LLM calls. When your budget runs out, it doesn't raise an exception or let the bill run — it falls through to a free local model so your agent keeps working.
Why
Agent API costs are unpredictable: most runs cost a few cents, but a stuck loop or an unbounded retry can burn tens of dollars in minutes. Most LLM gateways solve observability (dashboards, logs) or routing (pick the cheapest/best model) — but none of the popular ones hard-stop into free local compute the moment you're about to overspend.
This library does one thing: try providers in order, and once you've hit your budget, skip straight to a local model instead of failing or overspending. No hosted proxy, no dashboard, no vendor lock-in — about 150 lines you can read in five minutes and vendor straight into your own project.
Install
pip install llm-circuit-breaker
# or, with Anthropic support:
pip install "llm-circuit-breaker[anthropic]"
Quickstart
from llm_circuit_breaker import LLMCircuitBreaker, anthropic_tier, ollama_tier
breaker = LLMCircuitBreaker(
tiers=[
anthropic_tier(api_key="sk-ant-...", model="claude-sonnet-4-5"),
ollama_tier(model="gemma:2b"), # local, free, always-on fallback
],
daily_limit_usd=5.0,
ledger_path="llm_spend.jsonl",
)
result = breaker.complete(system="You are terse.", user="2+2?")
print(result.text, result.tier_used, result.cost_usd)
Once llm_spend.jsonl shows $5 spent today, every subsequent call for the
rest of the day skips Anthropic entirely and goes straight to your local
Ollama model — automatically, no code change, no surprise bill.
v0.2 — streaming, async, and a cost CLI
Stream tokens as they arrive (complete_stream) — same tiers, same budget
fallback, just yielded piece by piece. Built-in Anthropic / OpenAI / Ollama
tiers stream natively; any tier without streaming yields its whole result once.
for chunk in breaker.complete_stream(system="You are terse.", user="Explain RAG in 2 lines"):
print(chunk, end="", flush=True)
Async (acomplete) — keep many calls in flight at once:
import asyncio
results = asyncio.run(asyncio.gather(*[
breaker.acomplete(system="s", user=q) for q in questions
]))
See where the money went — a budget breaker you can actually inspect:
llm-cb cost # today / total / per-tier, straight from the ledger
llm-cb cost --ledger path # point at a different ledger, or --json for raw
LLM spend (llm_spend.jsonl)
today : $2.3100
total : $45.8700 (1240 calls)
by tier:
anthropic:claude-sonnet-4-5 980 calls $44.2000
ollama:gemma:2b 260 calls $0.0000
v0.3 — a statusline for your shell prompt
cost is a report you ask for. statusline is one line meant to show up
without asking — every time you open a terminal, not in a separate app
you have to keep running:
llm-cb statusline --daily-limit 5
# LLM $1.20/$5.00 [##--------] 24%
It only speaks up when something's off — OK is silent by design, WARN
appears at 80%, OVER once you've crossed the limit:
LLM $4.90/$5.00 [#########-] 98% WARN
LLM $5.10/$5.00 [##########] 100% OVER
No --daily-limit? Then there's no verdict to give, so it just shows
today's spend: LLM $1.20 today. --total-limit switches the whole line
(spend, percent, verdict) to all-time instead of today; --json gives the
same numbers as data. Wire it into PowerShell, bash/zsh, or a
Starship module in
docs/statusline.md — five minutes, no background
process, no new credentials read from other tools.
How it decides
- Check today's + all-time spend against your limits (a plain JSONL ledger file — human-readable, no database).
- Under budget: try each tier in order, return the first success.
- Over budget: skip every paid tier and try only the tiers marked
is_local=True. - If every attempted tier fails (or the budget is blown with no local tier configured), raise a clear exception instead of failing silently.
Built-in tiers
anthropic_tier, openai_tier, gemini_tier, openrouter_tier, and
ollama_tier (local, via Ollama) — or write your
own in about 5 lines; a Tier is just a name and a
(system, user, max_tokens) -> (text, cost_usd) function:
from llm_circuit_breaker import Tier
def _call(system, user, max_tokens):
text = my_provider_sdk.chat(system, user, max_tokens)
return text, my_own_cost_estimate
my_tier = Tier(name="my-provider", call=_call, is_local=False)
What this is not
Not a hosted gateway, not a dashboard, not a replacement for LiteLLM or Portkey if you need enterprise routing, guardrails, or a proxy server in front of a team's traffic. This is the opposite trade-off: minimal, in-process, one job — for solo builders and small agent projects who want a hard stop before overspend without standing up another service.
Pricing table
pricing.py ships an approximate per-model $/1M-token table used only to
estimate spend for the budget check. Prices change and vary by
region/tier; override PRICING["your-model"] = {"input": ..., "output": ...}
for accuracy, or return your own cost from a custom tier.
Development
pip install -e ".[dev,anthropic]"
pytest
License
MIT
Metadata
Release files for llm-circuit-breaker 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| llm_circuit_breaker-0.3.0.tar.gz | 18.9 kB | Without provenance | Details for llm_circuit_breaker-0.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| llm_circuit_breaker-0.3.0-py3-none-any.whl | Python 3 | none | any | Details for llm_circuit_breaker-0.3.0-py3-none-any.whl |
Total release size: 33.4 kB
Release files / llm_circuit_breaker-0.3.0.tar.gz
| Download URL | llm_circuit_breaker-0.3.0.tar.gz |
|---|---|
| Size | 18.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
68fab6356470ff8dc6f8b1714fd3e9f4b8c0fe421c0029911767517a5b99a095
|
|
BLAKE2b-256 checksum How to use checksums |
57980d61815ec6b596371fa527c17f15d0f3c9dd249c7bf89a53b8c06aca44b7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|
Release files / llm_circuit_breaker-0.3.0-py3-none-any.whl
| Download URL | llm_circuit_breaker-0.3.0-py3-none-any.whl |
|---|---|
| Size | 14.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
79e44f12d52d58bdda1ce3a8c2b73c7dae780ffd2082582c384d3f0cb5011386
|
|
BLAKE2b-256 checksum How to use checksums |
606f089e2ef0465fa8fac9b1ab2ea2e53e56cdcabaa04b9de9d8a1852393c365
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|
Security for llm-circuit-breaker 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.