Local capture client for Kadari -- record your LLM calls to a local log for cost analysis.
Project description
kadari — the local capture client
kadari is the only thing Kadari ships to you. It's a thin, zero-dependency Python
library that records the LLM calls you already make to a local log. Kadari's
engine (which stays on Kadari's side) then analyses that log and shows you, with proof,
where you could safely route to a cheaper in-family model — without ever re-running a
call or crossing a quality bar you set.
This package carries none of Kadari's analysis engine — no scoring, no classifier model, no benchmarks. It is capture glue, by design. Read every line.
Install
Zero runtime dependencies (stdlib only):
pip install kadari
Or install from a checkout / a wheel your Kadari contact hands you:
pip install ./client
# or: pip install kadari-0.1.0-py3-none-any.whl
Use
from kadari import LiveRecorder, wrap
rec = LiveRecorder("kadari_capture.jsonl") # a local path you control
# Option A — record explicitly, right after a call you already make:
resp = client.messages.create(model="claude-opus-4-8", messages=[{"role": "user", "content": text}])
rec.record_anthropic(id=req_id, input=text, response=resp.to_dict())
# OpenAI: rec.record_openai(id=req_id, input=text, response=resp.to_dict())
# Option B — wrap the call once and capture every call automatically:
create = wrap(client.chat.completions.create, provider="openai", recorder=rec)
resp = create(model="gpt-5.4-nano", messages=[{"role": "user", "content": text}])
Then hand the resulting kadari_capture.jsonl to Kadari (or, later, point the hosted
client at it) to get your savings report.
Safety contract
- Fail open on the host, fail closed on the data. Recording is a side-channel: a bug
here will never raise into your production call path —
record()warns and returnsFalseinstead of throwing (unless you passstrict=True). What does get written is always valid and loadable. - Local-first. This library never opens a network connection. Your prompts and outputs
stay in the local file you named; nothing is uploaded by
kadari. - Privacy controls.
redact=scrubs each input before it is written;sample=records only a fraction of calls to bound log size.
rec = LiveRecorder("kadari_capture.jsonl", sample=0.1, redact=my_scrubber)
Wire format — the stable SDK contract
The on-disk JSONL log is the contract between this client and Kadari's engine. It is near-immutable: backward-compatible within a major version; a breaking change requires a new major version and a documented migration path (Kadari rule 6). One JSON object per line:
{"id": "req-123", "model": "gpt-5.4-nano", "input": "...", "output": "Electronics",
"usage": {"input_tokens": 64, "output_tokens": 2}}
| field | type | required | meaning |
|---|---|---|---|
id |
string | yes | unique per log — a captured log is rejected wholesale on a duplicate id. If you don't pass one, a uuid is generated, and a reused id is refused at write time so one retry can't make the whole log unloadable. |
model |
string | yes | the premium provider model string you actually called (e.g. gpt-5.5, claude-opus-4-8) — priced verbatim. |
input |
string | yes (may be empty) | the request text that gets scored/re-classified cheaply. |
output |
string | yes | the label the premium model already returned. The provider adapters (record_openai/record_anthropic/wrap) unwrap a single-field structured object {"category": "X"} to X; the raw record(output=...) path writes the string verbatim, so pass a bare label there. |
usage |
object | optional | {"input_tokens", "output_tokens"}, both non-negative integers. Attached only when both are present — a half block is dropped, since the engine honours usage only if both are set. When omitted, Kadari estimates spend from text length. |
Comment lines (//…) and blank lines are ignored by the reader.
Compatibility guarantees within a major version:
- the five fields above keep their names and meaning;
- new optional fields may be added; readers ignore unknown fields, so a newer client log still loads in an older engine and vice-versa;
- no required field is removed or repurposed, and no field type changes.
This is enforced, not promised: the client never imports engine code, but a contract test in
the engine repo (tests/test_kadari_client.py) writes a log with this client and asserts
it loads unchanged through the engine's reader — so the two halves can never silently drift.
The engine accepts one additional input shape (a nested Anthropic Messages
{"id","request","response"} transcript); this client always emits the flattened shape above.
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 kadari-0.1.0.tar.gz.
File metadata
- Download URL: kadari-0.1.0.tar.gz
- Upload date:
- Size: 17.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1bdbf45344051ed9f02b5a939f9bc926d2286b9d8dfa0c1be112a0706bcd86f9
|
|
| MD5 |
80cb0772d23545c2942884cc2954856d
|
|
| BLAKE2b-256 |
0e4037927361c24266fc245cf7098f55e55d74ea6adb93b3bfd9a30d43fd6b69
|
File details
Details for the file kadari-0.1.0-py3-none-any.whl.
File metadata
- Download URL: kadari-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4ea23b26fa14b60c98c443686c5a2defe9b2758674580207ef42c69040f988cd
|
|
| MD5 |
f9cec02c59ce9392f5c6d737f6229e97
|
|
| BLAKE2b-256 |
a03ea99122d9272bb932cc9cee41f4c88c1de7b9a838cb77b1c0836ed119b7e7
|