{"id":47263392,"url":"https://github.com/arieradle/shekel","last_synced_at":"2026-04-01T17:44:41.846Z","repository":{"id":343040712,"uuid":"1176032435","full_name":"arieradle/shekel","owner":"arieradle","description":"LLM budget control and cost governance for AI agents. Python library for token budgets, usage limits and guardrails for OpenAI, Anthropic, LangChain, LangGraph and agentic systems.","archived":false,"fork":false,"pushed_at":"2026-03-15T16:02:29.000Z","size":2913,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-16T04:50:28.160Z","etag":null,"topics":["agentic-ai","agents","ai-cost-control","ai-governance","ai-guardrails","anthropic","budget","cost-control","cost-tracking","crewai","langchain","langfuse","langgraph","llm","llm-budget-control","llm-fallback","llmops","openai","python","tokencost"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/shekel/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/arieradle.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-08T14:24:52.000Z","updated_at":"2026-03-15T17:09:33.000Z","dependencies_parsed_at":null,"dependency_job_id":"94d81ac4-642b-456a-b244-28fd75ca89a3","html_url":"https://github.com/arieradle/shekel","commit_stats":null,"previous_names":["arieradle/shekel"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/arieradle/shekel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arieradle%2Fshekel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arieradle%2Fshekel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arieradle%2Fshekel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arieradle%2Fshekel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arieradle","download_url":"https://codeload.github.com/arieradle/shekel/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arieradle%2Fshekel/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31290571,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agentic-ai","agents","ai-cost-control","ai-governance","ai-guardrails","anthropic","budget","cost-control","cost-tracking","crewai","langchain","langfuse","langgraph","llm","llm-budget-control","llm-fallback","llmops","openai","python","tokencost"],"created_at":"2026-03-15T01:01:52.067Z","updated_at":"2026-04-01T17:44:41.840Z","avatar_url":"https://github.com/arieradle.png","language":"Python","funding_links":[],"categories":["Observability \u0026 Cost Tracking"],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# 🪙 shekel\n\n**Stop your AI agent from bankrupting you.**\n\n[![PyPI version](https://img.shields.io/pypi/v/shekel?color=blue\u0026label=PyPI)](https://pypi.org/project/shekel/)\n[![Python versions](https://img.shields.io/pypi/pyversions/shekel)](https://pypi.org/project/shekel/)\n[![CI](https://github.com/arieradle/shekel/actions/workflows/ci.yml/badge.svg)](https://github.com/arieradle/shekel/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/arieradle/shekel/branch/main/graph/badge.svg)](https://codecov.io/gh/arieradle/shekel)\n[![Downloads](https://img.shields.io/pypi/dm/shekel?color=green)](https://pypi.org/project/shekel/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://pypi.org/project/shekel/)\n[![GitHub Stars](https://img.shields.io/github/stars/arieradle/shekel?style=social)](https://github.com/arieradle/shekel/stargazers)\n[![Docs](https://img.shields.io/badge/docs-arieradle.github.io%2Fshekel-blue)](https://arieradle.github.io/shekel/latest/)\n\n\u003c/div\u003e\n\n```python\nwith budget(max_usd=5.00):\n    run_my_agent()       # hard stop at $5. no SDK changes. no config. just works.\n```\n\n```python\nwith budget(max_usd=5.00, loop_guard=True, max_velocity=\"$1/min\"):\n    run_my_agent()       # also stops infinite loops and bursty burn\n```\n\n```bash\nshekel run agent.py --budget 5   # or enforce without touching code at all\n```\n\n---\n\nI woke up to a **$47 OpenAI bill** from a LangGraph agent that spent the night retrying a failed tool call. I built shekel so you don't have to learn that lesson yourself.\n\n---\n\n## Install\n\n```bash\npip install shekel[openai]       # OpenAI\npip install shekel[anthropic]    # Anthropic\npip install shekel[all]          # OpenAI + Anthropic + LiteLLM + Gemini + HuggingFace\npip install shekel[cli]          # shekel run — enforce budgets without touching code\n```\n\n---\n\n## Works with everything\n\nIf it calls OpenAI or Anthropic under the hood, shekel sees it — **zero integration code needed.**\n\n| Provider | Framework | |\n|---|---|---|\n| OpenAI · Anthropic · Gemini | LangChain · LangGraph | Auto-patched |\n| HuggingFace · LiteLLM · Groq | CrewAI · OpenAI Agents SDK | Auto-patched |\n| MCP · AutoGen · LlamaIndex | Any custom wrapper | Auto-patched |\n| — | OpenAI Agents SDK (per-agent caps) | `b.agent(\"name\", max_usd=X)` |\n\n---\n\n## Every pattern you'll actually use\n\n### Hard cap — the one that saves you\n\n```python\nfrom shekel import budget\n\nwith budget(max_usd=5.00):\n    run_my_agent()\n# raises BudgetExceededError the moment spend crosses $5\n```\n\nNo wrapping your OpenAI client. No decorators. No SDK replacement. shekel monkey-patches the provider on context entry and restores it on exit. Your existing code runs unchanged.\n\n### Warn before the limit hits\n\n```python\nwith budget(max_usd=5.00, warn_at=0.8) as b:\n    run_my_agent()\n# logs a warning at $4.00, raises at $5.00\n```\n\n### Track spend without enforcing\n\n```python\nwith budget() as b:\n    run_my_agent()\nprint(f\"that cost ${b.spent:.4f}\")\n```\n\n### Switch to a cheaper model instead of crashing\n\n```python\nwith budget(max_usd=1.00, fallback={\"at_pct\": 0.8, \"model\": \"gpt-4o-mini\"}) as b:\n    run_my_agent()\n# switches gpt-4o → gpt-4o-mini at $0.80, hard stops at $1.00\n```\n\n### Cap tool calls — stop the infinite search loop\n\n```python\nfrom shekel import tool\n\n@tool(price=0.01)               # charge $0.01 per call + count toward the cap\ndef web_search(query: str) -\u003e str: ...\n\n@tool                           # free — just count calls\ndef read_file(path: str) -\u003e str: ...\n\nwith budget(max_usd=5.00, max_tool_calls=20) as b:\n    run_my_agent()\n# ToolBudgetExceededError on call 21 — before the tool runs\nprint(b.summary())              # LLM spend + tool spend broken out by tool name\n```\n\nAuto-intercepted with zero config: **LangChain, MCP, CrewAI, OpenAI Agents SDK**.\n\n### Per-stage budget control\n\n```python\nwith budget(max_usd=10.00, name=\"pipeline\") as pipeline:\n    with budget(max_usd=2.00, name=\"research\"):\n        results = search_web(query)        # capped at $2\n\n    with budget(max_usd=5.00, name=\"analysis\"):\n        report = analyze(results)          # capped at $5\n\nprint(pipeline.tree())\n# pipeline: $4.80 / $10.00\n#   research:  $1.20 / $2.00\n#   analysis:  $3.60 / $5.00\n```\n\nChildren auto-cap to the parent's remaining balance. `b.tree()` gives you a live visual breakdown.\n\n### LangGraph — per-node circuit breaking\n\n```python\nwith budget(max_usd=10.00, name=\"graph\") as b:\n    b.node(\"fetch_data\", max_usd=0.50)   # NodeBudgetExceededError before node runs\n    b.node(\"summarize\",  max_usd=1.00)\n\n    app = graph.compile()\n    app.invoke({\"query\": \"...\"})\n\nprint(b.tree())\n# graph: $0.84 / $10.00\n#   [node] fetch_data: $0.12 / $0.50  (24%)\n#   [node] summarize:  $0.72 / $1.00  (72%)\n```\n\nShekel patches `StateGraph.add_node()` transparently — no graph changes needed.\n\n### LangChain — per-chain circuit breaking\n\n```python\nwith budget(max_usd=5.00, name=\"pipeline\") as b:\n    b.chain(\"retriever\",  max_usd=0.20)   # ChainBudgetExceededError before chain runs\n    b.chain(\"summarizer\", max_usd=1.00)\n\n    retriever_chain.invoke({\"query\": \"...\"})\n    summarizer_chain.invoke({\"doc\": \"...\"})\n```\n\nShekel patches `Runnable._call_with_config` and `RunnableSequence.invoke` — zero changes to your chains.\n\n### CrewAI — per-agent and per-task circuit breaking\n\n```python\nfrom shekel.exceptions import AgentBudgetExceededError, TaskBudgetExceededError\n\ntry:\n    with budget(max_usd=5.00, name=\"crew\") as b:\n        b.agent(researcher.role,       max_usd=2.00)  # use agent.role directly\n        b.agent(writer.role,           max_usd=1.00)\n        b.task(research_task.name,     max_usd=1.50)  # use task.name directly\n        b.task(write_task.name,        max_usd=0.80)\n        crew.kickoff(inputs={\"topic\": \"AI\"})\nexcept TaskBudgetExceededError as e:\n    print(f\"Task '{e.task_name}' over budget: ${e.spent:.4f} / ${e.limit:.2f}\")\nexcept AgentBudgetExceededError as e:\n    print(f\"Agent '{e.agent_name}' over budget\")\n\nprint(b.tree())\n# crew: $2.84 / $5.00\n#   [agent] Senior Researcher: $1.92 / $2.00  (96.0%)\n#   [agent] Content Writer:    $0.92 / $1.00  (92.0%)\n#   [task]  research:          $1.92 / $1.50  (128.0%)\n#   [task]  write:             $0.92 / $0.80  (115.0%)\n```\n\nShekel patches `Agent.execute_task` transparently. Gate order: task cap → agent cap → global (most specific first).\n\n### Loop \u0026 Velocity Protection\n\n```python\nwith budget(\n    max_usd=50.00,\n    loop_guard=True,           # AgentLoopError if any tool repeats 5x in 60s\n    max_velocity=\"$1/min\",     # SpendVelocityExceededError if burn \u003e $1/min\n) as b:\n    run_my_agent()\n\nprint(b.loop_guard_counts)   # {'web_search': 3, 'read_file': 1}\n```\n\n### Distributed budgets — enforce across multiple processes\n\n```python\nfrom shekel.backends.redis import RedisBackend\n\nbackend = RedisBackend()   # reads REDIS_URL from env; fail-closed by default\n\nwith budget(\"$5/hr + 100 calls/hr\", name=\"api-tier\", backend=backend) as b:\n    response = client.chat.completions.create(...)\n# Atomic Lua-script enforcement — one Redis round-trip per call\n# BudgetConfigMismatchError if the same name is reused with different limits\n```\n\nWorks with `AsyncRedisBackend` for async workflows. Circuit breaker built in — configurable threshold + cooldown. Fail-open or fail-closed.\n\n### Rolling-window rate limits\n\n```python\nwith budget(\"$5/hr\", name=\"api-tier\") as b:\n    response = await client.chat.completions.create(...)\n# BudgetExceededError carries retry_after so callers know when the window resets\n```\n\nMulti-cap: `budget(\"$5/hr + 100 calls/hr\")` — USD and call-count windows are independent.\n\n### Accumulate across sessions\n\n```python\nsession = budget(max_usd=20.00, name=\"session\")\n\nwith session: run_step_1()   # $3.20\nwith session: run_step_2()   # $8.10\nwith session: run_step_3()   # raises at $20\n\nprint(f\"total: ${session.spent:.2f}\")\n```\n\n---\n\n## Enforce from the CLI — zero code changes\n\nDon't want to touch the code at all? Don't.\n\n```bash\npip install shekel[cli]\n\nshekel run agent.py --budget 5\n# exit 0 = under budget  |  exit 1 = budget exceeded  ← CI-friendly\n```\n\nDrop it into any pipeline:\n\n```bash\n# Shell / cron / Docker\nAGENT_BUDGET_USD=5 shekel run agent.py\n\n# GitHub Actions\n- run: shekel run agent.py --budget 5\n\n# Docker — operator sets budget at runtime, no rebuild needed\nENTRYPOINT [\"shekel\", \"run\", \"agent.py\"]\n# docker run -e AGENT_BUDGET_USD=5 my-agent-image\n```\n\nKey flags:\n\n```bash\n--budget 5          # hard stop in USD\n--warn-at 0.8       # log warning at 80%, hard stop at 100%\n--max-llm-calls 20  # cap by call count instead of spend\n--max-tool-calls 50 # cap agent tool calls\n--warn-only         # log but never exit 1  (soft guardrail)\n--dry-run           # track costs, no enforcement\n--output json       # machine-readable spend summary for log pipelines\n--budget-file shekel.toml  # load limits from config file\n```\n\n---\n\n## What the spend summary looks like\n\n```python\nwith budget(max_usd=5.00) as b:\n    run_my_agent()\n\nprint(b.summary())\n```\n\n```\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nshekel spend summary\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nTotal: $1.2450 / $5.00 (25%)\n\ngpt-4o:       $1.1320  (5 calls)\n  Input:  45.2k tokens → $0.1130\n  Output: 11.3k tokens → $1.1320\n\nTool spend:   $0.1130  (9 tool calls)\n  web_search  $0.090  (9 calls)  [langchain]\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n```\n\nOr machine-readable:\n\n```bash\nshekel run agent.py --budget 5 --output json\n# {\"spent\": 1.245, \"limit\": 5.0, \"calls\": 5, \"tool_calls\": 9, \"status\": \"ok\", \"model\": \"gpt-4o\"}\n```\n\n---\n\n## The decorator\n\n```python\nfrom shekel import with_budget\n\n@with_budget(max_usd=0.10)\ndef summarize(text: str) -\u003e str:\n    return client.chat.completions.create(...).choices[0].message.content\n# budget enforced independently on every call\n```\n\n---\n\n## How it works\n\nshekel monkey-patches `openai.chat.completions.create` and `anthropic.messages.create` on `__enter__` and restores originals on `__exit__`. Spend is tracked in a `ContextVar` — concurrent agents in the same process never share state. Nested `with budget()` blocks form a tree; child spend rolls up automatically.\n\n**No background threads. No external services. No API keys. Nothing leaves your machine.**\n\n---\n\n## Observability\n\n- **Langfuse** — cost streaming, circuit-break events, budget hierarchy in Langfuse spans\n- **OpenTelemetry** — 9 instruments: `shekel.llm.cost_usd`, `shekel.budget.utilization`, `shekel.budget.spend_rate`, `shekel.tool.calls_total`, and more\n\n```python\nfrom shekel.otel import ShekelMeter\nmeter = ShekelMeter()  # attaches to global MeterProvider; silent no-op if OTel absent\n```\n\n---\n\n## Supported models\n\nBuilt-in pricing for GPT-4o, GPT-4o-mini, o1, o3, Claude 3.5/3/3.7 Sonnet, Claude 3 Haiku/Opus, Gemini 2.0/2.5 Flash/Pro, and more.\n\n```bash\npip install shekel[all-models]   # 400+ models via tokencost\nshekel models                    # list all bundled models and pricing\nshekel estimate --model gpt-4o --input-tokens 1000 --output-tokens 500\n```\n\n---\n\n## API quick reference\n\n```python\nbudget(\n    max_usd=5.00,           # hard USD cap\n    warn_at=0.8,            # warn at 80%\n    max_llm_calls=50,       # cap by call count\n    max_tool_calls=100,     # cap tool dispatches\n    tool_prices={\"web_search\": 0.01},  # charge per tool\n    fallback={\"at_pct\": 0.8, \"model\": \"gpt-4o-mini\"},  # switch instead of crash\n    name=\"my-agent\",        # required for nesting + temporal budgets\n    backend=RedisBackend(), # distributed enforcement across processes\n)\n\nbudget(\"$5/hr + 100 calls/hr\", name=\"api-tier\")  # multi-cap rolling-window\n```\n\n**Component caps** — all chainable, all raise before the component executes:\n\n```python\nb.node(\"fetch_data\", max_usd=0.50)   # LangGraph node  → NodeBudgetExceededError\nb.chain(\"retriever\", max_usd=0.20)   # LangChain chain → ChainBudgetExceededError\nb.agent(\"researcher\", max_usd=1.00)  # CrewAI agent    → AgentBudgetExceededError\nb.task(\"summarize\", max_usd=0.50)    # CrewAI task     → TaskBudgetExceededError\n```\n\n**Exceptions** — all subclass `BudgetExceededError`, so one `except` catches everything:\n\n| Exception | Raised when | Key fields |\n|---|---|---|\n| `BudgetExceededError` | Global cap hit | `spent`, `limit`, `model`, `retry_after` |\n| `NodeBudgetExceededError` | LangGraph node cap hit | `node_name`, `spent`, `limit` |\n| `AgentBudgetExceededError` | CrewAI / OAI Agents agent cap hit | `agent_name`, `spent`, `limit` |\n| `TaskBudgetExceededError` | CrewAI task cap hit | `task_name`, `spent`, `limit` |\n| `ChainBudgetExceededError` | LangChain chain cap hit | `chain_name`, `spent`, `limit` |\n| `ToolBudgetExceededError` | Tool call cap hit | `tool_name`, `calls_used`, `calls_limit` |\n| `AgentLoopError` | Tool called too many times in window | `tool_name`, `call_count`, `window_seconds` |\n| `SpendVelocityExceededError` | Burn rate exceeds `max_velocity` | `velocity_per_min`, `limit_per_min` |\n| `BudgetConfigMismatchError` | Redis name reused with different limits | — |\n\n---\n\n## Security\n\nEvery PR and push to `main` runs CodeQL, Trivy, Bandit, and pip-audit. See the [Security tab](https://github.com/arieradle/shekel/security/code-scanning) for results.\n\n---\n\n## Documentation\n\n**[arieradle.github.io/shekel](https://arieradle.github.io/shekel/latest/)**\n\n- [Quick Start](https://arieradle.github.io/shekel/latest/quickstart/)\n- [CLI Reference](https://arieradle.github.io/shekel/latest/cli/)\n- [Docker \u0026 Containers](https://arieradle.github.io/shekel/latest/docker/)\n- [Nested Budgets](https://arieradle.github.io/shekel/latest/usage/nested-budgets/)\n- [Tool Budgets](https://arieradle.github.io/shekel/latest/usage/tool-budgets/)\n- [Temporal Budgets](https://arieradle.github.io/shekel/latest/usage/temporal-budgets/)\n- [LangGraph Integration](https://arieradle.github.io/shekel/latest/integrations/langgraph/)\n- [CrewAI Integration](https://arieradle.github.io/shekel/latest/integrations/crewai/)\n- [API Reference](https://arieradle.github.io/shekel/latest/api-reference/)\n\n---\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md). PRs welcome — especially new framework adapters.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farieradle%2Fshekel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farieradle%2Fshekel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farieradle%2Fshekel/lists"}