saive
Zero-friction LLM footprint reporting. Patches the OpenAI, Anthropic and Google GenAI Python clients to report token usage to saive asynchronously, in the background, without ever seeing your prompts or completions.
pip install saive-client
import saive
saive.init() # reads SAIVE_API_KEY from the environment
from openai import OpenAI
client = OpenAI() # unchanged from here on — sync, async, and streaming all work
That's the entire integration. If SAIVE_API_KEY isn't set, saive.init() is a complete
no-op — safe to call unconditionally, including from library code that doesn't know whether
its caller uses saive.
What this does and does not do
- Not a proxy. Your calls to OpenAI/Anthropic/Google go straight to them, exactly as before. saive never sits in the request path, never sees your API keys for those providers, and a saive outage is invisible to your application — every failure path here degrades to a silent no-op plus one warning-level log line, never an exception.
- Never sends prompt or completion content. Only the model string, input/output/
reasoning token counts, a timestamp, and latency are extracted from each response's
usageobject. Nothing else is read from your requests or responses. - Never blocks. Usage is buffered in memory and flushed from a background thread on a size/time trigger, plus once more at interpreter exit. Buffered events are dropped (not retried indefinitely, never written to disk) if saive can't be reached — an acceptable loss for a component whose numbers are estimates with wide uncertainty bands already.
Configuration
All via environment variables — there is no config object to construct.
| Variable | Default | |
|---|---|---|
SAIVE_API_KEY |
(none) | Required. Without it, init() patches nothing. |
SAIVE_DISABLED |
(unset) | Set to 1 to force saive off regardless of SAIVE_API_KEY. |
SAIVE_BASE_URL |
production saive endpoint | Override for self-hosted saive deployments. |
SAIVE_FLUSH_INTERVAL_SECONDS |
5 |
How often the background thread flushes. |
SAIVE_MAX_BUFFER_SIZE |
500 |
Max events buffered before new ones are dropped. |
SAIVE_MAX_RETRIES |
2 |
Retries (with backoff) per flush before discarding the batch. |
Tagging
with saive.tags(feature="summarise", customer_id="acme"):
client.chat.completions.create(...)
Tags apply to every tracked call made inside the with block, on whichever thread or
asyncio task is inside it (tags use contextvars, so they follow async control flow
correctly — a plain thread-local would not). Nested tags() blocks merge, innermost wins on
key collision. These merge server-side with whatever default_tags your saive API key
carries; the event's own tags win on collision there too.
Serverless
Call saive.flush() at the end of your handler, before the process might be frozen or
killed:
def handler(event, context):
...
saive.flush()
Streaming
Streaming is supported for all three providers. Usage typically only arrives in (or after) the final chunk, so if you stop consuming a stream early, saive will not have data to report for that call.
OpenAI specifically: the API only includes usage in a streamed response if you pass
stream_options={"include_usage": True} to create(). saive does not add this for you
(saive does not modify your outgoing requests, ever) — without it, saive has no token counts
to report for that streamed call and will silently skip it.
Supported call surfaces (v1)
- OpenAI:
client.chat.completions.create()(sync, async, streaming). The newerclient.responses.create()API is not yet instrumented. - Anthropic:
client.messages.create()(sync, async, streaming). Theclient.messages.stream()context-manager helper is a separate code path and is not yet instrumented — usecreate(stream=True)if you want saive to see it. - Google GenAI:
client.models.generate_content()andgenerate_content_stream(), sync and async (client.aio.models...).
What gets sent
For every tracked call, exactly this shape is POSTed (batched) to saive:
{
"request_id": "generated-uuid4",
"provider": "openai",
"model": "gpt-4o-mini",
"input_tokens": 123,
"output_tokens": 45,
"reasoning_tokens": 12,
"max_tokens": 500,
"timestamp": "2026-01-01T00:00:00+00:00",
"latency_ms": 842.3,
"tags": {"env": "prod"}
}
max_tokens is the ceiling your code passed to the SDK call (max_tokens for
OpenAI/Anthropic, max_completion_tokens taking priority over max_tokens for OpenAI,
config.max_output_tokens for Google GenAI) — not a usage figure. It powers one of the
reduction insights (calls that request far more headroom than they use).
reasoning_tokens, max_tokens, latency_ms and tags are omitted entirely when not applicable rather
than sent as null/{}.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file saive_client-0.1.0.tar.gz.
File metadata
- Download URL: saive_client-0.1.0.tar.gz
- Upload date:
- Size: 13.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9ff7bfe92f5af8ce3d8068e44a7c8cbdd4aebe05091971556f1f60db1bfbbbbd
|
|
| MD5 |
56e63195612d288e9b68f982e97b3b51
|
|
| BLAKE2b-256 |
d828713e6aab0d2a374baa4c6a9876843e4177ef8203d2a701f545fcd4e698e9
|
File details
Details for the file saive_client-0.1.0-py3-none-any.whl.
File metadata
- Download URL: saive_client-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b4e972fcccd4f8a3251634c6d5a67fe11de287357b6bb63449801c39bf272a5f
|
|
| MD5 |
e56d83088b830b37eefc44743e593b6b
|
|
| BLAKE2b-256 |
9955a865e822c6bafc1ee61ecd9f8d651cdcea00d6823541ef93288cbed21012
|