TraceFlow Compress
by ashritkvs
README.md
# Distil
A **serverless prompt-compression MCP connector** that compresses prompts fast
and returns **Distil-style metrics** — tokens, cost, latency, compute-load,
energy, and carbon — where every number is either **measured** or a
**clearly-labeled estimate**. See [SPEC.md](SPEC.md) for the full design.
Built around the source whitepaper's *Prompt Intelligence* + token/cost/
compute/energy/carbon layer (the buildable slice — no GPU hardware required).
## Highlights
- **Browser extension:** compresses what you type directly into claude.ai,
chatgpt.com, and gemini.google.com — no API key needed, works inside your
normal logged-in chat session. See [extension/README.md](extension/README.md).
- **LLM Gateway:** drop-in proxy for OpenAI/Anthropic/Gemini — point your
`base_url` at Distil and every request is compressed (optionally governed)
before it reaches the real provider, streaming included. See below.
- **Fast + serverless:** default heuristic compression is pure Python (~3 ms,
no model, no API key). Optional `gpt-4o-mini` mode for higher quality.
- **MCP connector:** exposes 5 tools + a metrics resource over streamable HTTP.
- **Distil metrics:** token/cost/latency (measured) + energy/carbon/GPU-load
(estimated, labeled). GPU intent preserved via a compute-load model, not faked.
- **Live dashboard** + public `/metrics` endpoint.
- **Honest by design:** every estimate flagged `estimated: true`; closed-model
params flagged `params_known: false`.
## LLM Gateway (drop-in proxy) — the business product
Point your existing OpenAI/Anthropic/Gemini client at Distil instead of the
provider directly. Distil compresses the prompt, forwards it to the real
provider **using your own API key**, and streams the answer straight back —
same request/response shape, so your code doesn't change beyond the base URL.
```
your app → Distil (/v1/...) → compress + optional governance → real provider → same answer back to you
```
**One-line change (OpenAI SDK):**
```python
from openai import OpenAI
client = OpenAI(
api_key="YOUR_OWN_OPENAI_KEY", # unchanged — sent straight through, never stored
base_url="https://getdistil.vercel.app/v1",
)
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Could you please possibly explain, in a very detailed way, what a REST API is?"}],
)
```
**curl (proves compression + a normal answer + savings headers):**
```bash
curl -i https://getdistil.vercel.app/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"messages": [{"role": "user", "content": "Could you please possibly explain, in a very detailed way, what a REST API is?"}]
}'
# Response body is a normal OpenAI chat.completion object.
# Response headers include:
# x-distil-original-tokens, x-distil-sent-tokens, x-distil-tokens-saved
```
Anthropic and Gemini work the same way — only the base URL/path and auth
header change (your existing client library handles that):
| Provider | Base URL you point at | Your key goes in |
|---|---|---|
| OpenAI | `https://getdistil.vercel.app/v1` | `Authorization: Bearer sk-...` |
| Anthropic | `https://getdistil.vercel.app/v1/messages` | `x-api-key: sk-ant-...` (+ `anthropic-version`) |
| Gemini | `https://getdistil.vercel.app/v1beta/models/{model}:generateContent?key=...` | `?key=...` or `x-goog-api-key` |
### Behavior
- **Your key, your bill.** Distil forwards the Authorization/`x-api-key`/`key`
you send on every request straight to the real provider. Distil never
stores it — only a one-way hash is kept in memory, used solely as a
rate-limit/metering identity.
- **What's compressed by default:** the text of every `user`-role message
(OpenAI/Anthropic) or `user`-role `contents` entry (Gemini) — covers both
"the latest message" and any large context/documents pasted into it.
`system`/`system_instruction` and prior `assistant`/`model` turns are left
untouched. Function/tool schemas (`tools`, `tool_calls`, `tool_result`
blocks) are never touched.
- **Fail-safe:** if compression or governance throws for any reason, Distil
forwards your original, uncompressed request rather than breaking the call.
- **Streaming:** `"stream": true` is compressed once up front, then the
provider's SSE response is relayed back chunk-by-chunk, unbuffered
(verified locally against a slow test source — chunks arrive on the
provider's own cadence, not batched).
- **Governance modes** via `x-distil-govern`: `off` (default `log`) never
blocks; `log` runs classify/PII/injection/moderation checks and records
violations but still forwards the request; `enforce` returns a
provider-shaped 4xx error instead of forwarding when the verdict is `block`.
### Config headers (all optional)
| Header | Default | Effect |
|---|---|---|
| `x-distil-ratio` | `0.5` | Target fraction of tokens to *keep* (0.05–1.0) |
| `x-distil-govern` | `log` | `off` / `log` / `enforce` |
| `x-distil-compress` | `on` | `on` / `off` — governance still runs independently of this |
| `x-distil-compress-system` | `off` | also compress `system`/`systemInstruction` text |
| `x-distil-enforcement` | `block` | on an `enforce`-mode block: `block` (stop the request) or `redact` (mask detected PII/secrets and forward the masked text instead). Quarantine/approval aren't offered here — see [Governance workflow](#governance-workflow) for why a live proxy call can't support them. |
### Honesty notes
- Compression is **heuristic only** in the gateway (no per-request LLM call
to compress — that would double your latency and cost). It can read
slightly choppy; tune `x-distil-ratio` up (e.g. `0.7`) if answer quality
degrades on your prompts, and test before relying on it in production.
- **Verified against the live provider APIs**, not guessed: OpenAI and
Anthropic request/response/error/SSE shapes were confirmed by sending real
requests to `api.openai.com` and `api.anthropic.com` (with an invalid key,
to observe the real error envelope) and inspecting the response byte-for-
byte. Gemini's `generateContent` request/response/error shape was verified
the same way; its streaming framing (`:streamGenerateContent?alt=sse`) is
the SSE mode documented in Google's REST examples, but was **not** verified
live against a valid Gemini key — test this path before depending on it.
- The `usage`/token-count fields inside the provider's own response body are
the provider's real, authoritative numbers (Distil doesn't touch them).
The `x-distil-*` headers are Distil's own count of what it compressed.
## Quick start (local)
```bash
pip install -r requirements.txt
python demo.py # try the core on a sample
python eval/run_eval.py # measured eval over sample prompts
pytest tests/ # test suite
python mcp_server.py # run the MCP server over stdio
uvicorn api.index:app --port 8000 # run the HTTP server + dashboard
# → open http://localhost:8000/ (dashboard) and /mcp (connector)
```
## MCP tools
| Tool | Purpose |
|---|---|
| `compress_prompt(text, target_ratio?, quality?, target_model?, use_cache?)` | Compress + full metrics. `target_model="auto"` routes by complexity |
| `route_prompt(text)` | Recommend a small/large model by complexity + cost transparency |
| `analyze_prompt(text)` | Tokens, fillers, redundancy (no compression) |
| `estimate_savings(text, calls_per_day?, target_model?)` | Projected monthly cost/carbon savings |
| `get_metrics()` | Aggregate Distil metrics incl. cache hit rate |
| `get_top_prompts(n?)` | Most compressible prompts seen |
| `detect_anomalies()` | AIOps: flag low-compression / token / cost spikes (IQR baseline) |
| `route_provider_prompt(text)` | Recommend a specific **provider + model** across every configured provider (data-sensitivity-aware, health-aware, cost-ranked) — see [Governance workflow](#governance-workflow) |
| `redact_text(text)` | Mask detected PII/secrets with `[REDACTED:<type>]` |
| `check_model_policy(model, tenant?)` | Check a model against the allow/deny policy, honoring exceptions |
| `scan_licenses(text)` | Classify packages referenced in `text` by license category |
| `get_audit_log(n?)` / `export_audit_log(n?, fmt?)` | Evidence-grade audit trail (every governance decision, not just violations) |
| `list_review_queue(kind?, n?)` / `resolve_review(review_id, decision, ...)` | Quarantine/approval queue — list held prompts, approve or reject one |
| `grant_exception(scope, value, tenant?, ttl_hours?, reason?, granted_by?)` / `list_exceptions()` / `revoke_exception(id)` | Scoped, time-limited overrides of a package/model policy block |
| `send_test_alert()` | Fire a test alert to `DISTIL_ALERT_WEBHOOK_URL` |
Resource: `metrics://summary`.
Each `compress_prompt` result also carries **distributed-trace spans** (§2.2) —
measured sub-step timings (`route`, `cache_lookup`, `compress`, `token_metrics`,
`estimates`).
### Semantic caching (§8.2) & multi-model routing (§8.4)
- **Cache** — two-tier, serverless-friendly: exact (normalized hash) + similarity
(lexical-cosine, `DISTIL_CACHE_THRESHOLD`, default 0.92) so near-identical prompts
reuse a prior compression. Namespaced by (ratio, quality, model). Per warm
instance. Hit rate is shown on the dashboard.
- **Routing** — `route_prompt` / `target_model="auto"` scores prompt complexity
(reasoning verbs, code, structure, length) and picks a small vs large model,
with per-model cost estimates so the choice is transparent.
## Governance workflow
Beyond the allow/warn/block verdict from `govern`, Distil supports:
- **Model policy** — `DISTIL_MODEL_POLICY_MODE` (`denylist` default | `allowlist`)
+ `DISTIL_DENIED_MODELS` / `DISTIL_ALLOWED_MODELS`. Checked on the gateway
(`model` in the request body → `403 model_not_allowed`) and in `process_prompt`.
- **Redact / quarantine / require-approval** — `process_prompt(..., enforcement=)`
is `"block"` (default), `"redact"` (mask PII/secrets and continue),
`"quarantine"` (hold for security review), or `"approval"` (hold pending
sign-off). Quarantine/approval return a review id immediately — nothing is
compressed until `resolve_review` approves or rejects it. **The live LLM
Gateway only supports `block`/`redact`** (`x-distil-enforcement` header) —
a synchronous proxy call has no way to pause for a human, so quarantine/
approval are `/process` + MCP-only.
- **Exception workflow** — `grant_exception(scope, value, tenant?, ttl_hours?, reason?)`
grants a narrow, expiring override of a package or model block instead of
disabling the whole policy. Checked automatically by `check_packages` /
`check_model_policy`.
- **License scanning** — `scan_licenses(text)` classifies referenced packages
(permissive / weak_copyleft / copyleft / unknown) against a small offline
registry; a copyleft hit escalates governance to `warn` (a legal-review
flag, not a hard block). Unknown packages are flagged, not guessed.
- **Audit trail** — every `govern` call (allow included) writes an
evidence-grade entry — decision id, tenant, verdict, reasons, a prompt hash
+ 60-char preview (never full prompt content) — separate from the
violation log so audit volume doesn't pollute `/metrics`.
`export_audit_log(fmt="csv")` for handing to an auditor.
- **Alerts** — `DISTIL_ALERT_WEBHOOK_URL` (+ `DISTIL_ALERT_MIN_SEVERITY`,
default `high`) fires a webhook on a governance block or a quarantine/
approval submission. Dual-shaped payload: a Slack-compatible `text` field
plus a structured `distil_event` for PagerDuty/Jira automation or a
generic ticketing ingest. Fail-safe — a broken webhook never affects the
request that triggered it.
- **Cross-provider routing** — `route_provider_prompt(text)` (vs. `route_prompt`'s
tier-only recommendation) picks a real provider + model: a prompt with
detected PII/secrets is restricted to `DISTIL_TRUSTED_PROVIDERS` (default
`local`) when one is configured; candidates are ranked by recent health
(`core.availability`, fed by real gateway traffic) then cost across every
provider with a key configured, not just OpenAI's small/large tiers.
Admin endpoints (`/audit`, `/review-queue/*`, `/exceptions/*`, `/alerts/test`)
are gated the same way as the rest of the API — set `DISTIL_ADMIN_KEY` for a
dedicated `x-admin-key` requirement; Distil has no role separation beyond
that yet, so without it any valid Distil key can call them.
## Deploy (serverless, Vercel)
1. Push to GitHub, import into Vercel (Python / Fluid Compute — auto-detected).
2. Set env vars: `CONNECTOR_API_KEY` (gates `/mcp`), optional `OPENAI_API_KEY`
(quality mode), optional `UPSTASH_REDIS_REST_URL` + `_TOKEN` (persistent
metrics; a local JSON file is used otherwise).
3. Add to Claude via connector settings → `https://<app>.vercel.app/mcp`.
Metrics dashboard: `https://<app>.vercel.app/`.
## Metrics reference
| Measured (real) | Estimated (labeled) |
|---|---|
| tokens in/out/saved, reduction % | cost saved (USD) |
| latency (ms) | energy saved (Wh) |
| CPU time, peak RAM | carbon saved (g CO₂) |
| fillers removed, redundancy % | GPU-ms load + reduction % (`2×params×tokens`) |
## Layout
```
core/ compression + intelligence + estimates + metrics store
core/gateway.py LLM Gateway request rewriting (no networking; pure logic)
mcp_server.py FastMCP tools/resource
api/index.py serverless ASGI entrypoint (MCP + dashboard + /metrics + auth)
api/gateway_routes.py LLM Gateway HTTP routes (/v1/chat/completions, /v1/messages, /v1beta/...)
dashboard/ static metrics page
eval/ measured evaluation
tests/ unit tests (tests/test_gateway.py covers the gateway)
```
## Reused from the Prompt Compression Agent
tiktoken counting, the filler list + analysis logic, the metrics dataclass
pattern, and the OpenAI wiring (for the optional LLM path).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues