TokLedger
# π TokLedger
**[Quick start](#-quick-start) Β· [How it works](#-how-it-works) Β· [MCP](#-mcp) Β· [Data format](docs/data-format.md) Β· [Zero dependencies](pyproject.toml)**
[]() []() []() []() []()
The local inference ledger β every token your home GPU serves, priced in tokens, watts, and dollars
<div align="center">
<img src="assets/hero.png" alt="TokLedger" width="860"/>
</div>
## π‘ The problem this solves
You run a 32B model on your 32 GB rig because API prices hurt. But now **nobody knows what inference actually costs you** β and the tools that try to answer assume a cloud price list, not your electricity bill.
- API observability (Langfuse, Helicone, β¦) wants your traffic in *their* cloud.
- `ollama ps` tells you VRAM, not cost.
- The "$/token" number people quote is a *cloud* number. Your real cost is **watts Γ hours Γ your tariff** β and the comparison that matters is *local $ vs cloud $ for the same tokens*.
TokLedger is a zero-dependency OpenAI-compatible proxy that sits in front of your local engine and writes **one row per request** to a local SQLite ledger:
| You get | From |
| :--- | :--- |
| exact or estimated prompt/completion tokens | engine `usage` (preferred) or a deterministic heuristic |
| TTFT, total latency, tokens/s | measured around the request, streaming-aware |
| energy (kWh) + local $ | NVML GPU power sampling when available, else a configurable draw Γ your `usd_per_kwh` |
| **cloud-equivalent $** | an editable reference price list (gpt-4o-mini by default) |
| savings % vs cloud | the number to show people who say "why bother running local" |
## π Quick start
```bash
pip install git+https://github.com/adithyanraj03/TokLedger # zero runtime deps
# terminal 1: your engine (llama.cpp shown; Ollama/LM Studio/vLLM work too)
./server -m qwen2.5-32b-instruct-q4_K_M.gguf --port 8080
# terminal 2: the ledger (proxy + dashboard on one port)
tokledger serve --port 8081 --upstream http://127.0.0.1:8080/v1
```
Then point any client at the proxy β the *only* change is `base_url`:
```python
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8081/v1", api_key="none")
r = client.chat.completions.create(model="qwen2.5-32b", messages=[...]) # priced automatically
```
Open <http://127.0.0.1:8081/> β the dashboard is live.
No engine handy? There's a deterministic offline mock so the whole pipeline (streaming, usage parsing, costs) runs with zero models:
```bash
tokledger mock --port 8999 & # OpenAI-compatible stub
tokledger serve --port 8081 --upstream http://127.0.0.1:8999/v1
curl -s http://127.0.0.1:8081/v1/chat/completions \
-d '{"model":"mock-7b","messages":[{"role":"user","content":"hello"}]}'
```
## π₯οΈ The dashboard
<div align="center">
<img src="assets/dashboard.png" alt="TokLedger dashboard" width="820"/>
<sub><b>Live dashboard</b> β 556 requests through the mock provider: tokens, TTFT, kWh, local $ vs cloud-equivalent $, per-model rollup.</sub>
</div>
One HTML file, vanilla JS + inline SVG, no CDN, no framework: stats cards, per-day bars with local-vs-cloud cost lines, per-model table, and the recent-requests feed with a per-row `est` pill whenever token counts are heuristic. Auto-refreshes every 5 s; `GET /export.jsonl` downloads the full ledger.
## βοΈ How it works
<div align="center">
<img src="assets/architecture.svg" alt="TokLedger architecture" width="760"/>
</div>
1. **Proxy** β `POST /v1/chat/completions` is forwarded to your upstream (streaming SSE bytes pass through verbatim).
2. **Measure** β TTFT is time-to-first-byte for streams; tokens come from the engine's `usage` when present (Ollama always, llama.cpp/vLLM with stream usage) else from a deterministic ~4-chars/token heuristic, flagged `est`.
3. **Price** β `kWh = watts Γ seconds / 3.6e6` (NVML draw if `pynvml` + NVIDIA GPU present, else configured `load_watts`), `local $ = kWh Γ usd_per_kwh`, `cloud $` from the reference price list.
4. **Record** β one row in `~/.tokledger/ledger.db`; config in `~/.tokledger/config.json` (editable JSON, rate, watts, price list, client tag).
## π CLI
```
tokledger serve --port 8081 --upstream http://127.0.0.1:8080/v1
tokledger dashboard --port 8090 # API + dashboard, no proxy
tokledger mock --port 8999 # offline OpenAI-compatible provider
tokledger stats # totals at a glance
tokledger report -o week.html # static HTML report
tokledger ingest chatlog.jsonl # import (TokLedger or minimal OpenAI usage lines)
tokledger export -o all.jsonl # full ledger out
tokledger mcp # MCP stdio server
tokledger config --usd-per-kwh 0.12 --client swarm-planner
```
## π MCP
`tokledger mcp` speaks MCP over stdio (newline-delimited JSON-RPC, no dependencies) and exposes the ledger to coding agents as four read-only tools β so your agent can answer "what did this week of inference cost?" without you opening a browser:
```jsonc
// mcpServers config (any MCP host)
"tokledger": { "command": "python", "args": ["-m", "tokledger", "mcp"] }
```
| Tool | Returns |
| :--- | :--- |
| `tokledger_stats` | requests, tokens, avg TTFT, kWh, local $, cloud-equiv $, savings % |
| `tokledger_recent` {limit} | latest ledger records |
| `tokledger_model_breakdown` | per-model rollup |
| `tokledger_cloud_savings` | local vs cloud $ + savings |
## π§ͺ Development
```bash
pip install pytest
python -m pytest # 50 tests, no GPU, no network beyond localhost
python examples/seed_demo.py # 550 deterministic demo requests for screenshots
```
The suite covers token math, cost models, the SQLite ledger, the proxy core with an injected fake upstream (streaming + non-streaming + failures), the mock provider over real sockets, the full end-to-end proxyβledger path, ingest/export, the MCP protocol in-process *and* over a subprocess, and CLI smoke tests.
## π€ Honest scope
- **Token counts** are exact when your engine reports `usage` (recommended: enable it); otherwise they're a clearly-labelled heuristic estimate.
- **Energy attribution** assumes the GPU draw during the request window. Multi-GPU and CPU-side energy are out of scope; NVML covers NVIDIA (AMD/Intel fall back to the configured draw).
- **The cloud price list is a reference**, not a quote β edit `cloud_prices` in `config.json` for your region/provider.
- **Single process, localhost-first**: it's not a multi-tenant gateway; bind it to `127.0.0.1` (the default) and it never needs the internet.
## π Privacy
No telemetry, no accounts, no cloud, no CDN. One SQLite file, one JSON config, one process on `127.0.0.1`. The mock provider and the full test suite run entirely offline.
## π License
MIT β see [LICENSE](LICENSE).
## π¬ Contact
**Adithya N Raj** Β· [GitHub](https://github.com/adithyanraj03) Β· [adithyanraj03@gmail.com](mailto:adithyanraj03@gmail.com) Β· [LinkedIn](https://www.linkedin.com/in/adithyanraj03)
---
<div align="center">
**Β© 2026 Adithya N Raj β¨**
</div>
TDQS
Scored across 4 tools
Tools are mostly distinct: stats for aggregate metrics, recent for raw records, model_breakdown for per-model rollup, and cloud_savings for cost comparison. However, tokledger_stats already includes cloud-equivalent cost and percent saved, which overlaps with tokledger_cloud_savings, creating minor ambiguity for cost-focused queries.
All tool names share the 'tokledger_' prefix and use consistent snake_case noun phrases. While not verb_noun, the convention is uniform and predictable.
Four tools is well-scoped for a ledger analytics server, covering summary, recent records, per-model breakdown, and savings. Each tool has a clear role without redundancy.
Core analytical queries are covered, but the surface lacks filtering by date range or model, and no pagination for recent records. These are minor gaps that agents could work around by adjusting N or using aggregate stats.