Observed since 21 Oct, 11:07 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Privacy-first monitoring for LLM apps: sends aggregate metrics to Aikraft, never prompts or responses.
Privacy-first monitoring for LLM applications. Wrap your Anthropic or OpenAI client and Aikraft tracks latency, token usage, refusals, truncations and errors over time, alerts you when they drift, and keeps your EU AI Act post-market monitoring record up to date.
No prompts, responses or personal data ever leave your infrastructure. Only per-minute aggregates do.
pip install aikraft
import aikraft
from anthropic import Anthropic # or: from openai import OpenAI
aikraft.init(workspace="<workspace-id>", system="<system-id>") # API key from AIKRAFT_API_KEY
client = aikraft.wrap(Anthropic())
client.messages.create(model="claude-sonnet-5-5", max_tokens=1024, messages=[...]) # unchanged
Your workspace and system IDs, and a ready-to-paste snippet, are in Aikraft under Monitoring → Set up telemetry. Create an API key with read/write scope in Settings → API keys.
For each minute, per model and optional tag, the SDK sends counts only:
| Sent | Example |
|---|---|
| Number of calls | 128 |
| Latency, input tokens, output tokens, output length: counts per fixed bucket, plus a sum | "latency_ms": {"counts": [0, …, 120, 8, 0], "sum": 91234.5} |
| Stop reasons (fixed vocabulary) | {"end_turn": 120, "max_tokens": 5, "refusal": 3} |
Call status (fixed vocabulary: ok, rate_limited, timeout, connection_error, auth_error, bad_request, server_error, error) | {"ok": 126, "rate_limited": 1, "connection_error": 1} |
| Optional feedback scores: count and sum | {"count": 4, "sum": 3.0} |
Never sent: prompts, system prompts, responses, tool inputs or outputs, user or request IDs, or
anything else from a single request. Response text is only measured (its length) and immediately
discarded. Unknown stop reasons are reported as other, never as raw strings.
A full payload looks like this:
{
"schema": "aikraft.llm.v1",
"batch_id": "3f0c…",
"sdk": {"name": "aikraft-python", "version": "0.2.0"},
"windows": [{
"start": "2026-10-08T14:00:00+00:00", "end": "2026-10-08T14:01:00+00:00",
"model": "claude-sonnet-5-5", "tag": "support-bot", "calls": 128,
"histograms": {"latency_ms": {"counts": [...], "sum": 91234.5}, "output_tokens": {"counts": [...], "sum": 18003}},
"categories": {"stop_reason": {"end_turn": 120, "max_tokens": 5, "refusal": 3}, "status": {"ok": 126, "rate_limited": 2}}
}]
}
aikraft.wrap() works with the sync and async clients of:
messages.create() (including stream=True) and messages.stream()chat.completions.create(), responses.create() (including stream=True) and their
.stream() helpersIt patches the client instance you pass, so other clients are untouched, and returns the same client. Return values and exceptions are exactly what the underlying SDK gives you. Failed calls are counted by status and the original exception is re-raised.
For OpenAI chat streaming, token counts are only available if you request them with
stream_options={"include_usage": True}. Without it, latency, length and stop reasons are still tracked.
Group calls by feature or endpoint:
with aikraft.tag("support-bot"):
client.messages.create(...)
Tags are context-local, so they're safe across threads and asyncio tasks, and must match
[A-Za-z0-9_.-]{1,64}.
aikraft.feedback(1.0, model="claude-sonnet-5-5", tag="support-bot") # e.g. 1.0 thumbs up, 0.0 down
Only the count and sum per minute are sent, never which response they belong to.
| Option | Environment variable | Default |
|---|---|---|
api_key= | AIKRAFT_API_KEY | (required) |
endpoint= | AIKRAFT_ENDPOINT | https://api.aikraft.eu |
enabled=False | AIKRAFT_DISABLED=1 | enabled |
flush_interval= | 60 seconds | |
debug=True | AIKRAFT_DEBUG=1 | off |
dry_run=True | AIKRAFT_DRY_RUN=1 | off |
Without an API key, or when disabled, init() logs a warning and everything becomes a no-op. Your app
keeps working. Logs go to the aikraft logger.
debug prints every payload before it is sent, and the server's reply, to stderr (or to wherever your
app already routes the aikraft logger). dry_run does the same without sending anything and without
needing an API key, which is handy to check the data locally before connecting:
$ AIKRAFT_DRY_RUN=1 python app.py
2026-10-17 22:09:24 aikraft: dry run, not sending batch b03e… (1 windows) to https://api.aikraft.eu/…
{
"schema": "aikraft.llm.v1",
…
"categories": {"status": {"connection_error": 1}}
}
The API key is never logged. In the Aikraft app, the Telemetry tab also lists what was received.
batch_id; retries reuse it and the server
ignores repeats.aikraft.flush() at the end of
each handler (AWS Lambda, Cloud Functions…).aikraft.shutdown() to do it
explicitly.Python 3.10+. The only dependency is httpx. anthropic and openai aren't dependencies; the SDK works
with whichever version you have installed.
Apache-2.0
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.2.0 | 17 Oct 2026 | 1 |
This describes our collected data; it is not a security assessment.
Observed since 21 Oct, 11:07 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:f48d931bd2750b20306b2061c57f56e221e5f7b77af7d3ab4a7a8aa2da8bdb74