One-line, fail-open wrapper for LLM calls — silent-breakage detection for indie AI apps.
Project description
chirppal (Python)
One-line, fail-open wrapper for LLM calls — a smoke alarm for the AI features in your app. Wrap a call and ChirpPal watches it for silent breakage (refusals, malformed JSON, empty output, latency spikes) and fire-and-forget reports metadata only to the ChirpPal backend. It never changes what your call returns, raises, or how long it takes.
- Fail-open — any error inside the wrapper (a check bug, a network blip) is
swallowed.
track()'s return value, exception, and effective timing always match calling your function directly. - Privacy-first — by default only metadata leaves your process (pass/fail, latency, model, which check failed, and a short error signature rather than the exception's message). Prompt/response content is sent only if you explicitly opt into semantic-judge sampling (an experimental feature, not generally available yet).
- Zero dependencies — standard library only.
Install
pip install chirppal
Quickstart
project= is the one thing you always pass — it selects that project's
checks, default model, sampling rate, and API key from chirppal.config.json
(see below). Create the config first, then wrap the call:
{
"config_version": 2,
"projects": {
"prod-support-bot": {
"key_env": "CHIRPPAL_KEY_SUPPORT",
"checks": { "no_error": true, "not_empty": true }
}
}
}
CHIRPPAL_KEY_SUPPORT=cp_live_... # this project's key, from the dashboard
from chirppal import track
resp = track(
lambda: client.responses.create(model="gpt-4.1", input=prompt),
model="gpt-4.1", # pass the exact model id so deprecation radar can flag it
project="prod-support-bot",
)
track() returns exactly what the wrapped call returns (and re-raises its
exceptions unchanged). No config file at all? track() still works — it just
monitors nothing and reports under a dev-only fallback key, exactly like
before you had a config.
# Optional: override the endpoint. Defaults to https://api.chirppal.com/api/ingest.
# For local development against your own backend:
CHIRPPAL_ENDPOINT=http://localhost:8000/api/ingest
Config: one project = one policy
A chirppal.config.json at your repo root declares one or more projects
— each is a full policy: which checks to run, which environment variable
holds its key (key_env), an optional default model, and an optional
sampling_rate for passing calls (failures always send regardless). Full
format spec, shared with the JS/TS client: CONFIG.md.
{
"config_version": 2,
"projects": {
"support-bot": {
"key_env": "CHIRPPAL_KEY_SUPPORT",
"model": "gpt-4.1",
"sampling_rate": 0.25,
"checks": {
"max_latency_ms": 3000,
"no_refusal": true,
"valid_json": { "required_keys": ["status"] }
}
},
"billing-bot": {
"key_env": "CHIRPPAL_KEY_BILLING",
"checks": { "no_error": true, "must_contain": ["invoice"] }
}
}
}
# reads checks + model + sampling_rate + key from the "support-bot" entry above
track(lambda: call_llm(), project="support-bot")
Running two projects (each with its own key) from the same process just means
using each project's name in its own track() calls — no extra setup:
track(lambda: call_support_bot(), project="support-bot")
track(lambda: call_billing_bot(), project="billing-bot")
A code-level checks=[...] list still works as an escape hatch — it replaces
just the checks for that call; key_env/model/sampling_rate from the
named project still apply:
from chirppal import track, checks
track(
lambda: call_llm(),
checks=[checks.valid_json(required_keys=["status"]), checks.no_refusal()],
project="support-bot",
)
model=/sampling_rate= passed to track() likewise override that
project's JSON defaults for a single call (e.g. a dynamically-chosen model).
Reading the config is itself fail-open: a missing or broken config, an unset
key_env variable, or an unknown project name all warn once and fall back to
monitoring less (never checks, or a dev-only fallback key) — never breaks
track().
Matching an event back to your own system
Pass metadata= to tag an event with your own identifiers (e.g. a user or
request id), and check the dashboard's per-event detail against your own logs:
track(
lambda: call_llm(),
project="support-bot",
metadata={"user_id": "u_123", "api_call_id": req.id},
)
You can also set a static default per project in chirppal.config.json
("metadata": {"env": "production"}) — a per-call value merges with it and
wins on a shared key. Bounded to 10 keys / string or number values / 200-char
strings; anything past that is dropped or truncated (never an error) and
surfaces on the dashboard's Integration health panel. Not encrypted —
never put API keys, passwords, or personal data in it.
Every failed check also reports a safe-subset detail (e.g. which keyword
was missing, or that JSON parsing failed at a given line/column) — visible on
the dashboard's event list — and every event carries a small Integration
health snapshot (is the response shape recognized, was anything clamped or
truncated, what config is actually in effect) that the dashboard's project
page shows as green/yellow. See CONFIG.md and
CONTRACT.md §6 for the full details.
Privacy
The default payload is metadata only — no conversation content. prompt and
output are sent only on a call you opted into judge-sampling for
(judge_sampling_rate > 0 — an experimental feature, not generally available
yet). No other path ever sends your users' prompts or your model's output.
error defaults to a short signature (the exception's type, plus a numeric
status/error code when the provider exposes one) rather than its message —
this is a technical guarantee, not just a convention: the status/code are
validated against a strict format before they're included, so a real
prompt/response fragment can't slip through as one. Set
"send_error_messages": true on a project in chirppal.config.json if you
want the full exception message instead (truncated to 500 chars) — only do
this if you're confident that message can't echo back your own content.
metadata is a separate, unrelated opt-in (see above) that's sent on every
event once you set it — it's not encrypted, so treat it like a log line, not
a vault.
Publishing (maintainers)
Requires a PyPI account + API token — this step is done by hand, not by CI:
cd sdk/chirppal
python -m build # produces dist/*.whl and dist/*.tar.gz
python -m twine upload dist/*
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
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 chirppal-0.2.0.tar.gz.
File metadata
- Download URL: chirppal-0.2.0.tar.gz
- Upload date:
- Size: 19.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c691d619dfe2ef23e39370d89491a2d641c351d91a09c4b4df5ba9a4f18d5b3
|
|
| MD5 |
b9eaef81886afce4b84359e5484659d3
|
|
| BLAKE2b-256 |
f8e27d135505d92305ea504a37faa63e55ed4772441f8e4f25c78e25e3e0ba1d
|
File details
Details for the file chirppal-0.2.0-py3-none-any.whl.
File metadata
- Download URL: chirppal-0.2.0-py3-none-any.whl
- Upload date:
- Size: 22.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9546f68a3644a9bc374d174bbe7204ff3fbbe25c547deaea4e1e6cd64b048ba3
|
|
| MD5 |
11f399ac92307dd974846e67cab914e1
|
|
| BLAKE2b-256 |
cf536887365f2716fdfcccf367f0a3fe7a6fd68a84c3d4d6c1121bdf6d09e753
|