Skip to main content

cendor-tokenguard

Stop runaway LLM bills, and get per-feature / per-user cost attribution for free. One decorator, one context manager. No dashboard, no account, no infra.

Cap a runaway loop before it overspends — and see which feature or user spent the rest.

PyPI license · pip install cendor-tokenguard

Using an AI coding assistant? npx @cendor/init (TS) / uvx cendor-init (Python) wires it up — or point it at cendor.ai/docs/for-ai-assistants.

from cendor.core import instrument
from cendor.tokenguard import budget, track, report

client = instrument(openai_client)              # wrap once; tokenguard subscribes, never patches

@budget(usd=0.50, on_exceed="downgrade", downgrade={"gpt-4o": "gpt-4o-mini"})
def answer(q: str) -> str:
    with track(feature="support_bot", user_id="alice"):   # ambient attribution, zero bookkeeping
        resp = client.chat.completions.create(model="gpt-4o", messages=[{"role": "user", "content": q}])
        return resp.choices[0].message.content

for row in report(group_by=["feature", "user_id"]):       # where did the money go?
    print(row["tags"], row["usd"], row["calls"])

Highlights

  • Pre-flight circuit breakeron_exceed="block" raises before an over-budget call runs; "downgrade" reroutes to a cheaper model pre-flight; "clamp" caps one call server-side via the provider's own token ceiling (next bullet); "break" cuts a running stream mid-flight (see Streaming timing); "truncate" degrades; "raise" stops a runaway loop; or call your own function.
  • Reasoning models, handled — you can't predict a thinking model's hidden reasoning pre-flight, so on_exceed="clamp" injects the provider's own token ceiling (max_completion_tokens/max_tokens) sized to the remaining budget — the call is capped server-side instead of overspending. report() breaks out reasoning_tokens, and the cumulative gate enforces on exact usage (which already includes reasoning). See docs/tokenguard.md → Reasoning models.
  • Decorator and context manager — budgets nest (an inner downgrade never masks an outer hard cap); config is validated at creation (a typo'd on_exceed or a map-less downgrade is a ValueError, never a silent no-op).
  • Cost attribution, freetrack(feature=…, user_id=…) tags ambient spend via contextvars (sync + async); report(group_by=[…]) shows where the money went, reasoning tokens included.
  • Cost as a test assertionreport().assert_under(usd=0.05, feature="search").
  • Pre-flight projectionestimate(model, messages) prices a call without making it.
  • Spend reaches your backend on its own (1.6.0) — with OpenTelemetry installed and a provider configured by your app, every priced row is also written to gen_ai.client.token.usage / .cost.usd counters through an internal additive tap, dimensioned by model + your track(...) tags. No use_sink line needed; CENDOR_TELEMETRY=off disables it. Your sink slot stays yours — the tap never displaces it, and it stands down if your own sink already is an OTelSink.
  • Durable + bounded — pluggable use_sink(tokenguard.sinks.SQLiteSink / OTelSink); FIFO-bounded in-memory buffer (configure(max_records=…), dropped()). For long runs, wrap a durable sink in sinks.QueueSink(SQLiteSink(path)) so its I/O runs on a background thread and adds no per-call latencyflush()/close() guarantee durability at shutdown.
  • No silent USD blind spots — a call whose model isn't in the price table records $0, so a USD cap can't bite. tokenguard warns once per model (UnpricedModelWarning) and counts these in unpriced_calls() / report()'s unpriced_calls; configure(on_unpriced="raise") makes on_exceed="block" reject them. A token cap is unaffected — tokens are counted regardless of price.
  • Thread-safe, with one caveat — the spend buffer and SQLiteSink are lock-guarded for concurrent emits, but budgets/tags are ContextVar-based: asyncio tasks inherit them, a plain threading.Thread does not (carry them with contextvars.copy_context()).

Streaming timing — post-flight raise/truncate fire when a stream is consumed, not when it's launched (the call is accounted once the chunk iterator drains). A loop that launches many streams before draining them can overspend — drain each stream before the next, or use a pre-flight mode (block/downgrade/clamp), which is unaffected. For a single stream that would blow past the cap mid-generation, on_exceed="break" is the breaker built for exactly this: it recounts the stream as it arrives and cuts it the instant its running output estimate crosses the remaining budget — you keep the partial output already yielded, and the provider still bills to the cut (it stops the meter, it does not un-bill).

Wrap-around — it rides the call you already make. Offline and standalone — bundled prices, no account.

See docs/tokenguard.md · CHANGELOG. Part of the Cendor stack — github.com/cendorhq/cendor-libs. Powered by PowerAI Labs. Apache-2.0; provided "as is", without warranty — use at your own risk (LICENSE §7–8).

Download files

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

Source Distribution

cendor_tokenguard-1.8.0.tar.gz (57.9 kB view details)

Uploaded Source

Built Distribution

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

cendor_tokenguard-1.8.0-py3-none-any.whl (35.1 kB view details)

Uploaded Python 3

File details

Details for the file cendor_tokenguard-1.8.0.tar.gz.

File metadata

  • Download URL: cendor_tokenguard-1.8.0.tar.gz
  • Upload date:
  • Size: 57.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for cendor_tokenguard-1.8.0.tar.gz
Algorithm Hash digest
SHA256 93d362030e9890ce52c97c9f8fb7e843e896b0612bc8fff5877803a1a674d2df
MD5 fe3b618a252b735748b32df4469c1a0e
BLAKE2b-256 497c4a326f7ddd5ba52f7bd355e43818a4adf00caaeaaf87ce78009c2c8c7c36

See more details on using hashes here.

Provenance

The following attestation bundles were made for cendor_tokenguard-1.8.0.tar.gz:

Publisher: release.yml on cendorhq/cendor-libs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cendor_tokenguard-1.8.0-py3-none-any.whl.

File metadata

File hashes

Hashes for cendor_tokenguard-1.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 94ce702676cb00a946e7933afd2b8e4d22c6419f7dad58465a659b894bbc2682
MD5 f2f2755e09966d38a10632cf0595e7b8
BLAKE2b-256 969bd43cd62cbd6954bad30dcf08f0c077484009fb0adc43f4d5fd0f51b281c7

See more details on using hashes here.

Provenance

The following attestation bundles were made for cendor_tokenguard-1.8.0-py3-none-any.whl:

Publisher: release.yml on cendorhq/cendor-libs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.8.0 This release

2 files

1.7.0

2 files

1.6.3

2 files

1.6.2

2 files

1.6.1

2 files

1.6.0

2 files

1.5.1

2 files

1.5.0

2 files

1.4.0

2 files

1.3.0

2 files

1.2.0

2 files

1.1.3

2 files

1.1.2

2 files

1.1.1

2 files

Supported by

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