latenzy
Documentation: amitpatole.github.io/latenzy
Per-model LLM latency monitoring for enterprises. latenzy is a synthetic prober and
Prometheus exporter that measures what the lab-level status pages can't: the latency
your account gets from each model — claude-sonnet-4-6 vs gpt-4o vs
gemini-2.0-flash, not "Anthropic is up".
Latency is tenant-specific: it depends on your rate-limit tier, your region, and the path you take to the model (direct API, Bedrock, Vertex). latenzy runs inside your network on your keys and exports per-model metrics your existing Prometheus + Grafana stack can alert on.
What it measures
Every probe cycle, for each configured (provider, model, endpoint, prompt_class):
| Metric | Meaning |
|---|---|
latenzy_ttft_seconds |
time to first streamed token (histogram) |
latenzy_request_duration_seconds |
total request duration (histogram) |
latenzy_output_tokens_per_second |
streaming throughput over the generation span (histogram) |
latenzy_probes_total{outcome=...} |
probe count by ok / rate_limited / timeout / error |
latenzy_last_success_timestamp_seconds |
staleness signal for alerting |
All metrics are labelled source, provider, model, endpoint, prompt_class.
Histograms are observed only for successful probes, so failures never skew latency
percentiles. Prompts are deterministic per prompt_class (small/medium/large) —
comparing models on unequal inputs is meaningless.
Every metric carries a source label: synthetic for the prober's canaries and
live for real application traffic (see below). One dashboard shows both.
Live traffic (passive instrumentation)
The prober answers "is this model slow right now"; to also chart your own traffic's
p95, wrap real LLM calls with the framework-agnostic LiveRecorder. It emits the same
metric names under source="live", so the same dashboards work — no separate pipeline.
from latenzy import LiveRecorder, Metrics, classify_prompt, measure_stream
recorder = LiveRecorder(Metrics()) # shares your app's Prometheus registry
with recorder.observe(
provider="openai", model="gpt-4o", prompt_class=classify_prompt(text=prompt)
) as obs:
for chunk in measure_stream(client.stream(prompt), obs): # marks first-token timing
handle(chunk)
obs.output_tokens = n_tokens
Label values are charset-validated (they may come from user input), so a host app can't
explode metric cardinality. A raised exception is recorded as an error outcome and
re-raised. A runnable, key-free walkthrough is in
examples/demo_live.py:
$ python examples/demo_live.py
recorded live call: model=gpt-4o prompt_class=small tokens=4
recorded live call: model=gpt-4o prompt_class=large tokens=9
--- /metrics (live source) ---
latenzy_ttft_seconds_count{...,prompt_class="small",...,source="live"} 1.0
latenzy_ttft_seconds_sum{...,prompt_class="small",...,source="live"} 0.0553...
latenzy_ttft_seconds_sum{...,prompt_class="large",...,source="live"} 0.3202...
latenzy_probes_total{...,outcome="ok",prompt_class="small",...,source="live"} 1.0
OpenTelemetry
Emit to OpenTelemetry as well as (or instead of) Prometheus with the otel extra
(pip install 'latenzy[otel]'). The prober and LiveRecorder take any
RecordSink, so FanoutSink(Metrics(), OTelBridge(meter)) sends to both;
instrument names follow the OTel GenAI conventions
(gen_ai.client.operation.duration, gen_ai.client.token.usage). For
latenzy run, enable it in config:
otel:
enabled: true
endpoint: https://collector.internal:4318/v1/metrics # omit → console
Quick start
pip install latenzy
export ANTHROPIC_API_KEY=... OPENAI_API_KEY=... GEMINI_API_KEY=...
latenzy doctor -c latenzy.yaml # validate config, check keys are present
latenzy once -c latenzy.yaml # one probe cycle, human-readable results
latenzy run -c latenzy.yaml # probe on an interval + serve /metrics
See latenzy.example.yaml for the full configuration.
API keys are read from environment variables only — they have no place in config files.
Grafana + Prometheus
- Dashboard — Grafana dashboard library ID
25642 (Dashboards → Import →
25642), or importdashboards/latenzy-model-comparison.json: the model-comparison view (TTFT p95, total-latency p95, tokens/sec, failure and 429 ratio, staleness) filterable by provider, model, endpoint, and prompt class. Import it into any Grafana; it prompts for your Prometheus datasource. Thedashboards-share/copy is the same dashboard in Grafana's "export for sharing externally" format for grafana.com uploads (generated byscripts/share_dashboard.py; a test keeps the two in sync). - Recording rules —
prometheus/recording_rules.yml: hourly and daily p50/p95/p99 series (latenzy:ttft_seconds:p95_1h, ...), so dashboards and alerts never recompute histogram quantiles. - Alert rules —
prometheus/alert_rules.yml: probe staleness, TTFT SLO breach, rate-limit pressure, failure ratio.
Standalone bundle (no existing Grafana needed)
cd deploy
mkdir -p secrets && openssl rand -hex 32 > secrets/latenzy_token
export ANTHROPIC_API_KEY=... OPENAI_API_KEY=... GEMINI_API_KEY=...
export GRAFANA_ADMIN_PASSWORD=... # no default password ships with the bundle
docker compose up -d # prober + Prometheus + Grafana, pre-provisioned
Grafana serves the comparison dashboard read-only at http://localhost:3000
(loopback-published only). The bundle mounts the same dashboards/ and
prometheus/ files from the repo, so the bundled and published copies cannot drift.
Security posture
- Binds
127.0.0.1by default. Binding a routable interface refuses to start unlessexporter.auth_token_envis set; the token is checked in constant time. - API keys are sent in request headers only and never appear in logs, URLs, metrics, or error output.
- Probe cost is bounded:
max_output_tokensdefaults to 16.
Status
v0.1.0 — first real release: prober + exporter, Grafana dashboard (library ID
25642), recording/alert rules, standalone bundle, security-hardened through a
four-round red-team loop (see SECURITY.md). Now also: passive
live-traffic instrumentation (LiveRecorder, source="live") and an
OpenTelemetry meter bridge (latenzy[otel]). Docs:
amitpatole.github.io/latenzy.
License: AGPL-3.0-only. Dual licensing available for enterprises — contact the author.
— amitpatole
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 latenzy-0.2.0.tar.gz.
File metadata
- Download URL: latenzy-0.2.0.tar.gz
- Upload date:
- Size: 223.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Rocky Linux","version":"9.5","id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
75067b63dd02c40eb1446786de8dab376df211e21eefb18e0660f8b6af49f524
|
|
| MD5 |
cd8b0f3e8fdd5e6e78247ac55f3ec786
|
|
| BLAKE2b-256 |
894edb39750f0831fbf00f7a4df654cbe222bbf06839379fdc55b0766d1f4b59
|
File details
Details for the file latenzy-0.2.0-py3-none-any.whl.
File metadata
- Download URL: latenzy-0.2.0-py3-none-any.whl
- Upload date:
- Size: 37.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Rocky Linux","version":"9.5","id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d03ec17bc27b8b331da188a3ab394a886a1ae736f92cf45de2be36cf506a73bb
|
|
| MD5 |
6a6a2023674e14a67887431daf4d52fc
|
|
| BLAKE2b-256 |
1bf0db6d7d27e5fd113b1872d00080162c1dc55629cf256a084b77ac3f0aba4f
|