Which AI agent spent the money on your OpenAI/Anthropic bill
Spaturzu is an open-source SDK that wraps existing LLM clients (OpenAI, Anthropic, etc.) with a single import change to enable per-agent cost attribution, budget enforcement, and cross-provider fallback, without altering call sites.
Notifications You must be signed in to change notification settings
Fork 0
Star 3
BranchesTags
Open more actions menu
Folders and files
NameName
Last commit message
Last commit date
Latest commit
History
17 Commits
17 Commits
.github/workflows
.github/workflows
openclaw
openclaw
python
python
typescript
typescript
.gitignore
.gitignore
.npmrc
.npmrc
LICENSE
LICENSE
README.md
README.md
package-lock.json
package-lock.json
package.json
package.json
pnpm-workspace.yaml
pnpm-workspace.yaml
tsconfig.base.json
tsconfig.base.json
Repository files navigation
Open-source client SDKs for spaturzu — per-agent LLM cost attribution, budget enforcement, and cross-provider fallback. Wrap your existing provider client (OpenAI, Anthropic, Bedrock, Gemini, Mistral) and every call is metered, attributed to the agent that made it, and optionally budget-capped — without changing how you call the model.
Migrate an existing agent in one line
Already calling OpenAI, Anthropic, Bedrock, Gemini, or Mistral? Change a single import — from import OpenAI from "openai" to import OpenAI from "@spaturzu/sdk/openai". Construction and call sites stay exactly the same, and you can attribute each call to the agent that made it:
import OpenAI from "@spaturzu/sdk/openai";
// Swap one import — reads SPATURZU_API_KEY + OPENAI_API_KEY from env. const openai = new OpenAI();
// Tag any call with the agent that made it — one line, no closure. await openai.withAgent("support-triage").chat.completions.create({ /* … */ });
That's the whole migration — no new objects, no wrappers around your call sites. Python is identical: from spaturzu.openai import OpenAI, then client.with_agent("support-triage").chat.completions.create(...). The same one-import swap works for every provider — @spaturzu/sdk/anthropic, /bedrock, /google, /mistral (Python: spaturzu.anthropic, and so on).
Running multi-step workflows? Group several calls under one agent with run() instead — see What you can do below.
SDKs
SDK Package Docs
TypeScript / Node @spaturzu/sdk README
Python spaturzu README
OpenClaw plugin ⚠️ experimental @spaturzu/openclaw README
⚠️ @spaturzu/openclaw is a work in progress and not yet ready for production use. APIs may change without notice.
Both the TypeScript and Python SDKs treat the underlying provider clients as optional dependencies — install only the ones you actually call.
Installation
Python — published on PyPI as spaturzu (Python 3.10+):
pip install spaturzu # core pip install "spaturzu[openai]" # with the OpenAI integration pip install "spaturzu[all]" # every provider integration
TypeScript / Node — published on npm as @spaturzu/sdk (ESM-only):
npm install @spaturzu/sdk
…then whichever provider clients you call (these are normal npm packages):
npm install openai @anthropic-ai/sdk @aws-sdk/client-bedrock-runtime @google/genai @mistralai/mistralai
Once installed, every import in this README works as written. pnpm / yarn accept the same package name (pnpm add @spaturzu/sdk).
Documentation
Full docs & guides: https://spaturzu.superchiu.org/docs
TypeScript / Node API: typescript/README.md
Python API: python/README.md
OpenClaw plugin (experimental): openclaw/README.md
For AI tools / LLMs: https://spaturzu.superchiu.org/llms.txt
What you can do
The examples below all build on this one-time setup. Every later snippet reuses spaturzu/sp, openai, and messages from here.
// TypeScript import { Spaturzu } from "@spaturzu/sdk"; import OpenAI from "openai";
const spaturzu = new Spaturzu({ apiKey: process.env.SPATURZU_API_KEY }); const openai = spaturzu.wrapOpenAI(new OpenAI());
const messages = [{ role: "user", content: "Summarize the latest sales report." }];
Python
import os from spaturzu import spaturzu from openai import OpenAI
sp = spaturzu(api_key=os.environ["SPATURZU_API_KEY"]) openai = sp.wrap_openai(OpenAI())
messages = [{"role": "user", "content": "Summarize the latest sales report."}]
Wrapping is transparent: the wrapped client has the same methods and return types as the original. You keep calling the provider exactly as before — spaturzu just meters each call in the background.
- Attribute cost to a named agent
Wrap a block of work in run("name", …) and every model call inside it is billed to that agent — so the dashboard shows cost per agent, not one undifferentiated total.
// TypeScript await spaturzu.run("researcher", async () => { await openai.chat.completions.create({ model: "gpt-4o", messages }); });
Python
with sp.run("researcher"): openai.chat.completions.create(model="gpt-4o", messages=messages)
- Nest sub-agents into a cost tree
Nested run() calls share one run id and extend the agent path, so a multi-step workflow shows up as a tree (research › synthesize).
// TypeScript await spaturzu.run("research", async () => { await openai.chat.completions.create({ model: "gpt-4o", messages }); // path: research
await spaturzu.run("synthesize", async () => { await openai.chat.completions.create({ model: "gpt-4o", messages }); // path: research › synthesize }); });
Python
with sp.run("research"): openai.chat.completions.create(model="gpt-4o", messages=messages) # path: research with sp.run("synthesize"): openai.chat.completions.create(model="gpt-4o", messages=messages) # path: research › synthesize
- Slice cost by team, customer, or environment with tags
Set tags globally on the client, or per-frame on a run(). Frame tags merge with the global ones (inner wins on conflict), so you can break spend down by any dimension you like.
// TypeScript const spaturzu = new Spaturzu({ apiKey: process.env.SPATURZU_API_KEY, tags: { env: "prod", team: "growth" }, // on every call });
await spaturzu.run("billing-agent", { tags: { customer: "acme" } }, async () => { await openai.chat.completions.create({ model: "gpt-4o", messages }); });
Python
sp = spaturzu( api_key=os.environ["SPATURZU_API_KEY"], tags={"env": "prod", "team": "growth"}, # on every call )
with sp.run("billing-agent", tags={"customer": "acme"}): openai.chat.completions.create(model="gpt-4o", messages=messages)
- Stop runaway spend with a hard-cap budget
When an agent's budget is exhausted, the wrapped call raises BudgetExceededError before it reaches the provider — so a refused call costs nothing. (Use onBreach: "warn" / "on_breach": "warn" to log and proceed instead of throwing.)
// TypeScript import { BudgetExceededError } from "@spaturzu/sdk";
const capped = spaturzu.wrapOpenAI(new OpenAI(), { budget: { hardCap: true, onBreach: "throw" }, });
try { await capped.chat.completions.create({ model: "gpt-4o", messages }); } catch (err) { if (err instanceof BudgetExceededError) { // budget hit — the request never left your process, so no tokens were spent } }
Python
from spaturzu import BudgetExceededError
capped = sp.wrap_openai(OpenAI(), budget={"hard_cap": True, "on_breach": "throw"})
try: capped.chat.completions.create(model="gpt-4o", messages=messages) except BudgetExceededError: pass # call never reached OpenAI — no spend
- Survive a provider outage with cross-provider fallback
Give a wrap a fallback chain. On a retryable error (429 / 5xx / connection), spaturzu transparently retries the next provider — and translates the response back to your primary provider's shape, so your code is unchanged.
// TypeScript import Anthropic from "@anthropic-ai/sdk";
const resilient = spaturzu.wrapOpenAI(new OpenAI(), { fallback: [ { provider: "anthropic", client: new Anthropic(), model: "claude-3-5-haiku-20241022" }, ], });
// If OpenAI is down, this is served by Anthropic — still returns an OpenAI-shaped response. const r = await resilient.chat.completions.create({ model: "gpt-4o", messages });
Python
from anthropic import Anthropic
resilient = sp.wrap_openai(OpenAI(), fallback=[ {"provider": "anthropic", "client": Anthropic(), "model": "claude-3-5-haiku-20241022"}, ])
r = resilient.chat.completions.create(model="gpt-4o", messages=messages)
All 20 directional provider pairs are supported. v1 fallback is non-streaming, text-only (no tools / response_format).
One wrap method per provider
The same five providers, the same shape, in both languages. Streaming and sync/async calls are metered automatically — no extra configuration.
Provider TypeScript Python
OpenAI (+ OpenAI-compatible) wrapOpenAI wrap_openai
Anthropic wrapAnthropic wrap_anthropic
Amazon Bedrock wrapBedrock wrap_bedrock
Google Gemini wrapGemini wrap_gemini
Mistral wrapMistral wrap_mistral
Short-lived processes (CLIs, serverless): call spaturzu.flush() / sp.flush() before exit so queued metering rows are sent.
For the complete API — every option, streaming details, and per-provider notes — see the TypeScript and Python READMEs, or the full docs at https://spaturzu.superchiu.org/docs.
Repository layout
sdks/ ├── typescript/ @spaturzu/sdk — Node/TS SDK (5 providers, 20 fallback pairs) ├── python/ spaturzu — Python SDK (parity with the TS surface) └── openclaw/ @spaturzu/openclaw — OpenClaw metering/budget plugin (WIP)
Development
TypeScript packages (managed as a pnpm workspace):
pnpm install pnpm build # build all packages pnpm test # run all test suites pnpm typecheck
Python SDK:
cd python python3 -m venv .venv .venv/bin/pip install -e ".[dev,all]" .venv/bin/pytest
License
MIT © Superchiu Ltd
spaturzu is a product of Superchiu Ltd.
About
Know which AI agent spent the money.
spaturzu.superchiu.org
Topics
bedrock
openai
ai-agents
finops
llms
genai
llm-tools
mistralai
antrophic
Resources
Readme
License
MIT license
Uh oh!
There was an error while loading. Please reload this page.
Activity
Stars
3 stars
Watchers
0 watching
Forks
0 forks
Report repository
Releases 6
python-v0.1.7
Latest
Jun 11, 2026
+ 5 releases
Packages 0
Uh oh!
There was an error while loading. Please reload this page.
Contributors 1
Nu11P01nt3r3xc3pt10n Nu11P01nt3r3xc3pt10n
Languages
Python 53.7%
TypeScript 46.3%