Spaces:
Build error
Build error
Commit ·
66ac1c8
1
Parent(s): 22987ea
Updating the README to reflect the right positioning for Headroom
Browse files- README.md +142 -84
- headroom/integrations/asgi.py +2 -2
- headroom/proxy/server.py +9 -26
README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
| 1 |
<p align="center">
|
| 2 |
<h1 align="center">Headroom</h1>
|
| 3 |
<p align="center">
|
| 4 |
-
<strong>
|
| 5 |
</p>
|
| 6 |
<p align="center">
|
| 7 |
-
|
|
|
|
| 8 |
</p>
|
| 9 |
</p>
|
| 10 |
|
|
@@ -27,15 +28,38 @@
|
|
| 27 |
<a href="https://chopratejas.github.io/headroom/">
|
| 28 |
<img src="https://img.shields.io/badge/docs-GitHub%20Pages-blue.svg" alt="Documentation">
|
| 29 |
</a>
|
|
|
|
|
|
|
|
|
|
| 30 |
</p>
|
| 31 |
|
| 32 |
---
|
| 33 |
|
| 34 |
-
##
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
---
|
| 41 |
|
|
@@ -45,7 +69,7 @@
|
|
| 45 |
pip install "headroom-ai[all]"
|
| 46 |
```
|
| 47 |
|
| 48 |
-
###
|
| 49 |
|
| 50 |
```bash
|
| 51 |
headroom proxy --port 8787
|
|
@@ -61,20 +85,6 @@ OPENAI_BASE_URL=http://localhost:8787/v1 cursor
|
|
| 61 |
|
| 62 |
Works with any language, any tool, any framework. One env var. **[Proxy docs](docs/proxy.md)**
|
| 63 |
|
| 64 |
-
### Failure Learning (new)
|
| 65 |
-
|
| 66 |
-
```bash
|
| 67 |
-
headroom learn # Analyze past Claude Code sessions, show recommendations
|
| 68 |
-
headroom learn --apply # Write learnings to CLAUDE.md and MEMORY.md
|
| 69 |
-
headroom learn --all --apply # Learn across all your projects
|
| 70 |
-
```
|
| 71 |
-
|
| 72 |
-
Reads your conversation history, finds every failed tool call, correlates it with what eventually succeeded, and writes specific corrections into your project files. Next session starts smarter. **[Learn docs](docs/learn.md)**
|
| 73 |
-
|
| 74 |
-
<p align="center">
|
| 75 |
-
<img src="headroom_learn.gif" alt="headroom learn demo" width="800">
|
| 76 |
-
</p>
|
| 77 |
-
|
| 78 |
### Python: One function
|
| 79 |
|
| 80 |
```python
|
|
@@ -103,6 +113,14 @@ You don't need to replace it. Drop Headroom into your existing stack:
|
|
| 103 |
|
| 104 |
---
|
| 105 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
## Does It Actually Work?
|
| 107 |
|
| 108 |
**100 production log entries. One critical error buried at position 67.**
|
|
@@ -122,9 +140,18 @@ Both responses: *"payment-gateway, error PG-5523, fix: Increase max_connections
|
|
| 122 |
From 100 log entries, SmartCrusher kept 6: first 3 (boundary), the FATAL error at position 67 (anomaly detection), and last 2 (recency). The error was automatically preserved — not by keyword matching, but by statistical analysis of field variance.
|
| 123 |
</details>
|
| 124 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
### Accuracy Benchmarks
|
| 126 |
|
| 127 |
-
|
| 128 |
|
| 129 |
**Standard Benchmarks** — Baseline (direct to API) vs Headroom (through proxy):
|
| 130 |
|
|
@@ -133,7 +160,7 @@ Headroom is evaluated on real OSS benchmarks — compression preserves accuracy.
|
|
| 133 |
| [GSM8K](https://huggingface.co/datasets/openai/gsm8k) | Math | 100 | 0.870 | 0.870 | **0.000** |
|
| 134 |
| [TruthfulQA](https://huggingface.co/datasets/truthfulqa/truthful_qa) | Factual | 100 | 0.530 | 0.560 | **+0.030** |
|
| 135 |
|
| 136 |
-
**Compression Benchmarks** — Accuracy after
|
| 137 |
|
| 138 |
| Benchmark | Category | N | Accuracy | Compression | Method |
|
| 139 |
|-----------|----------|---|----------|-------------|--------|
|
|
@@ -159,77 +186,40 @@ Full methodology: [Benchmarks](docs/benchmarks.md) | [Evals Framework](headroom/
|
|
| 159 |
|
| 160 |
---
|
| 161 |
|
| 162 |
-
##
|
| 163 |
|
| 164 |
-
|
| 165 |
-
flowchart LR
|
| 166 |
-
App["Your App"] --> H["Headroom"] --> LLM["LLM Provider"]
|
| 167 |
-
LLM --> Resp["Response"]
|
| 168 |
-
```
|
| 169 |
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
```mermaid
|
| 173 |
-
flowchart TB
|
| 174 |
-
subgraph Pipeline["Transform Pipeline"]
|
| 175 |
-
CA["1. CacheAligner\nStabilizes prefix for KV cache"]
|
| 176 |
-
CR["2. ContentRouter\nDetects content type, picks compressor"]
|
| 177 |
-
IC["3. IntelligentContext\nScore-based token fitting"]
|
| 178 |
-
QE["4. Query Echo\nRe-injects user question"]
|
| 179 |
-
CA --> CR --> IC --> QE
|
| 180 |
-
end
|
| 181 |
-
|
| 182 |
-
subgraph Compressors["ContentRouter dispatches to"]
|
| 183 |
-
SC["SmartCrusher\nAny JSON type"]
|
| 184 |
-
CC["CodeCompressor\nAST-aware code"]
|
| 185 |
-
LL["LLMLingua\nML-based text"]
|
| 186 |
-
end
|
| 187 |
-
|
| 188 |
-
subgraph CCR["CCR: Compress-Cache-Retrieve"]
|
| 189 |
-
Store[("Compressed\nStore")]
|
| 190 |
-
Tool["headroom_retrieve"]
|
| 191 |
-
Tool <--> Store
|
| 192 |
-
end
|
| 193 |
-
|
| 194 |
-
CR --> Compressors
|
| 195 |
-
SC -. "stores originals +\nsummary of what's omitted" .-> Store
|
| 196 |
-
QE --> LLM["LLM Provider"]
|
| 197 |
-
LLM -. "retrieves when\nit needs more" .-> Tool
|
| 198 |
-
```
|
| 199 |
|
| 200 |
-
|
| 201 |
-
> When it compresses 500 items to 20, it tells the LLM *what was omitted*
|
| 202 |
-
> ("87 passed, 2 failed, 1 error") so the LLM knows when to ask for more.
|
| 203 |
|
| 204 |
-
|
| 205 |
|
| 206 |
-
|
| 207 |
-
|----------|--------|-------|---------|
|
| 208 |
-
| Code search (100 results) | 17,765 | 1,408 | **92%** |
|
| 209 |
-
| SRE incident debugging | 65,694 | 5,118 | **92%** |
|
| 210 |
-
| Codebase exploration | 78,502 | 41,254 | **47%** |
|
| 211 |
-
| GitHub issue triage | 54,174 | 14,761 | **73%** |
|
| 212 |
|
| 213 |
-
|
| 214 |
|
| 215 |
-
|
| 216 |
|
| 217 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
|
| 219 |
-
|
| 220 |
-
|-------------|--------|------|
|
| 221 |
-
| `compress()` — one function | **Stable** | [Integration Guide](docs/integration-guide.md) |
|
| 222 |
-
| LiteLLM callback | **Stable** | [Integration Guide](docs/integration-guide.md#litellm) |
|
| 223 |
-
| ASGI middleware | **Stable** | [Integration Guide](docs/integration-guide.md#asgi-middleware) |
|
| 224 |
-
| Proxy server | **Stable** | [Proxy Docs](docs/proxy.md) |
|
| 225 |
-
| Agno | **Stable** | [Agno Guide](docs/agno.md) |
|
| 226 |
-
| MCP (Claude Code) | **Stable** | [MCP Guide](docs/mcp.md) |
|
| 227 |
-
| Strands | **Stable** | [Strands Guide](docs/strands.md) |
|
| 228 |
-
| LangChain | **Experimental** | [LangChain Guide](docs/langchain.md) |
|
| 229 |
|
| 230 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
|
| 232 |
-
|
|
|
|
|
|
|
|
|
|
| 233 |
|
| 234 |
| Feature | What it does |
|
| 235 |
|---------|-------------|
|
|
@@ -239,7 +229,6 @@ flowchart TB
|
|
| 239 |
| **LLMLingua-2** | ML-based 20x text compression |
|
| 240 |
| **CCR** | Reversible compression — LLM retrieves originals when needed |
|
| 241 |
| **Compression Summaries** | Tells the LLM what was omitted ("3 errors, 12 failures") |
|
| 242 |
-
| **Query Echo** | Re-injects user question after compressed data for better attention |
|
| 243 |
| **CacheAligner** | Stabilizes prefixes for provider KV cache hits |
|
| 244 |
| **IntelligentContext** | Score-based context management with learned importance |
|
| 245 |
| **Image Compression** | 40-90% token reduction via trained ML router |
|
|
@@ -248,6 +237,69 @@ flowchart TB
|
|
| 248 |
| **Read Lifecycle** | Detects stale/superseded Read outputs, replaces with CCR markers |
|
| 249 |
| **`headroom learn`** | Analyzes past failures, writes project-specific learnings to CLAUDE.md/MEMORY.md |
|
| 250 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
---
|
| 252 |
|
| 253 |
## Cloud Providers
|
|
@@ -297,6 +349,12 @@ Python 3.10+
|
|
| 297 |
|
| 298 |
---
|
| 299 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
## Contributing
|
| 301 |
|
| 302 |
```bash
|
|
|
|
| 1 |
<p align="center">
|
| 2 |
<h1 align="center">Headroom</h1>
|
| 3 |
<p align="center">
|
| 4 |
+
<strong>Compress everything your AI agent reads. Same answers, fraction of the tokens.</strong>
|
| 5 |
</p>
|
| 6 |
<p align="center">
|
| 7 |
+
Every tool call, DB query, file read, and RAG retrieval your agent makes is 70-95% boilerplate.<br>
|
| 8 |
+
Headroom compresses it away before it hits the model.
|
| 9 |
</p>
|
| 10 |
</p>
|
| 11 |
|
|
|
|
| 28 |
<a href="https://chopratejas.github.io/headroom/">
|
| 29 |
<img src="https://img.shields.io/badge/docs-GitHub%20Pages-blue.svg" alt="Documentation">
|
| 30 |
</a>
|
| 31 |
+
<a href="https://discord.gg/QEyNhaGA">
|
| 32 |
+
<img src="https://img.shields.io/badge/Discord-Join%20us-5865F2?logo=discord&logoColor=white" alt="Discord">
|
| 33 |
+
</a>
|
| 34 |
</p>
|
| 35 |
|
| 36 |
---
|
| 37 |
|
| 38 |
+
## Where Headroom Fits
|
| 39 |
|
| 40 |
+
```
|
| 41 |
+
Your Agent / App
|
| 42 |
+
│
|
| 43 |
+
│ tool calls, logs, DB reads, RAG results, file reads, API responses
|
| 44 |
+
▼
|
| 45 |
+
Headroom ← transparent proxy, no code changes needed
|
| 46 |
+
│
|
| 47 |
+
▼
|
| 48 |
+
LLM Provider (OpenAI, Anthropic, Google, Bedrock, 100+ via LiteLLM)
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
Headroom sits between your application and the LLM provider. It intercepts requests, compresses the context, and forwards an optimized prompt. Your app doesn't change — just point it at Headroom.
|
| 52 |
+
|
| 53 |
+
### What gets compressed
|
| 54 |
+
|
| 55 |
+
Headroom optimizes any data your agent injects into a prompt:
|
| 56 |
+
|
| 57 |
+
- **Tool outputs** — shell commands, API calls, search results
|
| 58 |
+
- **Database queries** — SQL results, key-value lookups
|
| 59 |
+
- **RAG retrievals** — document chunks, embeddings results
|
| 60 |
+
- **File reads** — code, logs, configs, CSVs
|
| 61 |
+
- **API responses** — JSON, XML, HTML
|
| 62 |
+
- **Conversation history** — long agent sessions with repetitive context
|
| 63 |
|
| 64 |
---
|
| 65 |
|
|
|
|
| 69 |
pip install "headroom-ai[all]"
|
| 70 |
```
|
| 71 |
|
| 72 |
+
### Proxy (zero code changes)
|
| 73 |
|
| 74 |
```bash
|
| 75 |
headroom proxy --port 8787
|
|
|
|
| 85 |
|
| 86 |
Works with any language, any tool, any framework. One env var. **[Proxy docs](docs/proxy.md)**
|
| 87 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
### Python: One function
|
| 89 |
|
| 90 |
```python
|
|
|
|
| 113 |
|
| 114 |
---
|
| 115 |
|
| 116 |
+
## Demo
|
| 117 |
+
|
| 118 |
+
<p align="center">
|
| 119 |
+
<img src="Headroom-2.gif" alt="Headroom Demo" width="800">
|
| 120 |
+
</p>
|
| 121 |
+
|
| 122 |
+
---
|
| 123 |
+
|
| 124 |
## Does It Actually Work?
|
| 125 |
|
| 126 |
**100 production log entries. One critical error buried at position 67.**
|
|
|
|
| 140 |
From 100 log entries, SmartCrusher kept 6: first 3 (boundary), the FATAL error at position 67 (anomaly detection), and last 2 (recency). The error was automatically preserved — not by keyword matching, but by statistical analysis of field variance.
|
| 141 |
</details>
|
| 142 |
|
| 143 |
+
### Real Workloads
|
| 144 |
+
|
| 145 |
+
| Scenario | Before | After | Savings |
|
| 146 |
+
|----------|--------|-------|---------|
|
| 147 |
+
| Code search (100 results) | 17,765 | 1,408 | **92%** |
|
| 148 |
+
| SRE incident debugging | 65,694 | 5,118 | **92%** |
|
| 149 |
+
| Codebase exploration | 78,502 | 41,254 | **47%** |
|
| 150 |
+
| GitHub issue triage | 54,174 | 14,761 | **73%** |
|
| 151 |
+
|
| 152 |
### Accuracy Benchmarks
|
| 153 |
|
| 154 |
+
Compression preserves accuracy — tested on real OSS benchmarks.
|
| 155 |
|
| 156 |
**Standard Benchmarks** — Baseline (direct to API) vs Headroom (through proxy):
|
| 157 |
|
|
|
|
| 160 |
| [GSM8K](https://huggingface.co/datasets/openai/gsm8k) | Math | 100 | 0.870 | 0.870 | **0.000** |
|
| 161 |
| [TruthfulQA](https://huggingface.co/datasets/truthfulqa/truthful_qa) | Factual | 100 | 0.530 | 0.560 | **+0.030** |
|
| 162 |
|
| 163 |
+
**Compression Benchmarks** — Accuracy after full compression stack:
|
| 164 |
|
| 165 |
| Benchmark | Category | N | Accuracy | Compression | Method |
|
| 166 |
|-----------|----------|---|----------|-------------|--------|
|
|
|
|
| 186 |
|
| 187 |
---
|
| 188 |
|
| 189 |
+
## Key Capabilities
|
| 190 |
|
| 191 |
+
### Lossless Compression
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
|
| 193 |
+
Headroom never throws data away. It compresses aggressively, stores the originals, and gives the LLM a tool to retrieve full details when needed. When it compresses 500 items to 20, it tells the model *what was omitted* ("87 passed, 2 failed, 1 error") so the model knows when to ask for more.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
|
| 195 |
+
### Smart Content Detection
|
|
|
|
|
|
|
| 196 |
|
| 197 |
+
Auto-detects what's in your context — JSON arrays, code, logs, plain text — and routes each to the best compressor. JSON goes to SmartCrusher, code goes through AST-aware compression (Python, JS, Go, Rust, Java, C++), prose goes to LLMLingua-2.
|
| 198 |
|
| 199 |
+
### Cache Optimization
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
|
| 201 |
+
Stabilizes message prefixes so your provider's KV cache actually works. Claude offers a 90% read discount on cached prefixes — but almost no framework takes advantage of it. Headroom does.
|
| 202 |
|
| 203 |
+
### Failure Learning
|
| 204 |
|
| 205 |
+
```bash
|
| 206 |
+
headroom learn # Analyze past Claude Code sessions, show recommendations
|
| 207 |
+
headroom learn --apply # Write learnings to CLAUDE.md and MEMORY.md
|
| 208 |
+
headroom learn --all --apply # Learn across all your projects
|
| 209 |
+
```
|
| 210 |
|
| 211 |
+
Reads your conversation history, finds every failed tool call, correlates it with what eventually succeeded, and writes specific corrections into your project files. Next session starts smarter. **[Learn docs](docs/learn.md)**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
|
| 213 |
+
<p align="center">
|
| 214 |
+
<img src="headroom_learn.gif" alt="headroom learn demo" width="800">
|
| 215 |
+
</p>
|
| 216 |
+
|
| 217 |
+
### Image Compression
|
| 218 |
|
| 219 |
+
40-90% token reduction via trained ML router. Automatically selects the right resize/quality tradeoff per image.
|
| 220 |
+
|
| 221 |
+
<details>
|
| 222 |
+
<summary><b>All features</b></summary>
|
| 223 |
|
| 224 |
| Feature | What it does |
|
| 225 |
|---------|-------------|
|
|
|
|
| 229 |
| **LLMLingua-2** | ML-based 20x text compression |
|
| 230 |
| **CCR** | Reversible compression — LLM retrieves originals when needed |
|
| 231 |
| **Compression Summaries** | Tells the LLM what was omitted ("3 errors, 12 failures") |
|
|
|
|
| 232 |
| **CacheAligner** | Stabilizes prefixes for provider KV cache hits |
|
| 233 |
| **IntelligentContext** | Score-based context management with learned importance |
|
| 234 |
| **Image Compression** | 40-90% token reduction via trained ML router |
|
|
|
|
| 237 |
| **Read Lifecycle** | Detects stale/superseded Read outputs, replaces with CCR markers |
|
| 238 |
| **`headroom learn`** | Analyzes past failures, writes project-specific learnings to CLAUDE.md/MEMORY.md |
|
| 239 |
|
| 240 |
+
</details>
|
| 241 |
+
|
| 242 |
+
---
|
| 243 |
+
|
| 244 |
+
## Headroom vs Alternatives
|
| 245 |
+
|
| 246 |
+
Context compression is a new space. Here's how the approaches differ:
|
| 247 |
+
|
| 248 |
+
| | Approach | Scope | Deploy as | Framework integrations | Data stays local? | Reversible |
|
| 249 |
+
|---|---|---|---|---|---|---|
|
| 250 |
+
| **Headroom** | Multi-algorithm compression | All context (tool outputs, DB reads, RAG, files, logs, history) | Proxy, Python library, ASGI middleware, or callback | LangChain, Agno, LiteLLM, Strands, MCP | Yes (OSS) | Yes (CCR) |
|
| 251 |
+
| **[RTK](https://github.com/rtk-ai/rtk)** | CLI command rewriter | Shell command outputs | CLI wrapper | None | Yes (OSS) | No |
|
| 252 |
+
| **[Compresr](https://compresr.ai)** | Cloud compression API | Text sent to their API | API call | None | No | No |
|
| 253 |
+
| **[Token Company](https://thetokencompany.ai)** | Cloud compression API | Text sent to their API | API call | None | No | No |
|
| 254 |
+
|
| 255 |
+
**Use it however you want.** Headroom works as a standalone proxy (`headroom proxy`), a one-function Python library (`compress()`), ASGI middleware, or a LiteLLM callback. Already using LiteLLM, LangChain, or Agno? Drop Headroom in without replacing anything.
|
| 256 |
+
|
| 257 |
+
**Headroom + RTK work well together.** RTK rewrites CLI commands (`git show` → `git show --short`), Headroom compresses everything else (JSON arrays, code, logs, RAG results, conversation history). Use both.
|
| 258 |
+
|
| 259 |
+
**Headroom vs cloud APIs.** Compresr and Token Company are hosted services — you send your context to their servers, they compress and return it. Headroom runs locally. Your data never leaves your machine. You also get lossless compression (CCR): the LLM can retrieve the full original when it needs more detail.
|
| 260 |
+
|
| 261 |
+
---
|
| 262 |
+
|
| 263 |
+
## How It Works Inside
|
| 264 |
+
|
| 265 |
+
```
|
| 266 |
+
Your prompt
|
| 267 |
+
│
|
| 268 |
+
▼
|
| 269 |
+
1. CacheAligner Stabilize prefix for KV cache
|
| 270 |
+
│
|
| 271 |
+
▼
|
| 272 |
+
2. ContentRouter Route each content type:
|
| 273 |
+
│ → SmartCrusher (JSON)
|
| 274 |
+
│ → CodeCompressor (code)
|
| 275 |
+
│ → LLMLingua (text)
|
| 276 |
+
▼
|
| 277 |
+
3. IntelligentContext Score-based token fitting
|
| 278 |
+
│
|
| 279 |
+
▼
|
| 280 |
+
LLM Provider
|
| 281 |
+
|
| 282 |
+
Needs full details? LLM calls headroom_retrieve.
|
| 283 |
+
Originals are in the Compressed Store — nothing is thrown away.
|
| 284 |
+
```
|
| 285 |
+
|
| 286 |
+
**Overhead**: 15-200ms compression latency (net positive for Sonnet/Opus). Full data: [Latency Benchmarks](docs/LATENCY_BENCHMARKS.md)
|
| 287 |
+
|
| 288 |
+
---
|
| 289 |
+
|
| 290 |
+
## Integrations
|
| 291 |
+
|
| 292 |
+
| Integration | Status | Docs |
|
| 293 |
+
|-------------|--------|------|
|
| 294 |
+
| `compress()` — one function | **Stable** | [Integration Guide](docs/integration-guide.md) |
|
| 295 |
+
| LiteLLM callback | **Stable** | [Integration Guide](docs/integration-guide.md#litellm) |
|
| 296 |
+
| ASGI middleware | **Stable** | [Integration Guide](docs/integration-guide.md#asgi-middleware) |
|
| 297 |
+
| Proxy server | **Stable** | [Proxy Docs](docs/proxy.md) |
|
| 298 |
+
| Agno | **Stable** | [Agno Guide](docs/agno.md) |
|
| 299 |
+
| MCP (Claude Code) | **Stable** | [MCP Guide](docs/mcp.md) |
|
| 300 |
+
| Strands | **Stable** | [Strands Guide](docs/strands.md) |
|
| 301 |
+
| LangChain | **Experimental** | [LangChain Guide](docs/langchain.md) |
|
| 302 |
+
|
| 303 |
---
|
| 304 |
|
| 305 |
## Cloud Providers
|
|
|
|
| 349 |
|
| 350 |
---
|
| 351 |
|
| 352 |
+
## Community
|
| 353 |
+
|
| 354 |
+
Questions, feedback, or just want to follow along? **[Join us on Discord](https://discord.gg/QEyNhaGA)**
|
| 355 |
+
|
| 356 |
+
---
|
| 357 |
+
|
| 358 |
## Contributing
|
| 359 |
|
| 360 |
```bash
|
headroom/integrations/asgi.py
CHANGED
|
@@ -111,7 +111,7 @@ class CompressionMiddleware:
|
|
| 111 |
body_chunks: list[bytes] = []
|
| 112 |
|
| 113 |
async def buffering_receive() -> MutableMapping[str, Any]:
|
| 114 |
-
message = await receive()
|
| 115 |
if message["type"] == "http.request":
|
| 116 |
chunk = message.get("body", b"")
|
| 117 |
if chunk:
|
|
@@ -169,7 +169,7 @@ class CompressionMiddleware:
|
|
| 169 |
if not body_sent:
|
| 170 |
body_sent = True
|
| 171 |
return {"type": "http.request", "body": full_body, "more_body": False}
|
| 172 |
-
result = await receive()
|
| 173 |
return result
|
| 174 |
|
| 175 |
# Wrap send to inject compression headers
|
|
|
|
| 111 |
body_chunks: list[bytes] = []
|
| 112 |
|
| 113 |
async def buffering_receive() -> MutableMapping[str, Any]:
|
| 114 |
+
message: MutableMapping[str, Any] = await receive()
|
| 115 |
if message["type"] == "http.request":
|
| 116 |
chunk = message.get("body", b"")
|
| 117 |
if chunk:
|
|
|
|
| 169 |
if not body_sent:
|
| 170 |
body_sent = True
|
| 171 |
return {"type": "http.request", "body": full_body, "more_body": False}
|
| 172 |
+
result: MutableMapping[str, Any] = await receive()
|
| 173 |
return result
|
| 174 |
|
| 175 |
# Wrap send to inject compression headers
|
headroom/proxy/server.py
CHANGED
|
@@ -250,9 +250,7 @@ def _build_prefix_cache_stats(
|
|
| 250 |
_openai_prefixes = ("gpt", "o1", "o3", "o4")
|
| 251 |
is_match = (
|
| 252 |
(provider == "anthropic" and "claude" in model_name)
|
| 253 |
-
or (provider == "openai" and any(
|
| 254 |
-
p in model_name for p in _openai_prefixes
|
| 255 |
-
))
|
| 256 |
or (provider == "gemini" and "gemini" in model_name)
|
| 257 |
or (provider == "bedrock" and "claude" in model_name)
|
| 258 |
)
|
|
@@ -2132,14 +2130,9 @@ class HeadroomProxy:
|
|
| 2132 |
f"[{request_id}] Memory: Added beta header: {key}={headers[key]}"
|
| 2133 |
)
|
| 2134 |
|
| 2135 |
-
# Query Echo:
|
| 2136 |
-
#
|
| 2137 |
-
|
| 2138 |
-
from headroom.transforms.query_echo import extract_user_query, inject_query_echo
|
| 2139 |
-
|
| 2140 |
-
user_query = extract_user_query(messages) # From original messages
|
| 2141 |
-
if inject_query_echo(optimized_messages, user_query, tokens_saved, original_tokens):
|
| 2142 |
-
logger.debug(f"[{request_id}] Query echo injected after compression")
|
| 2143 |
|
| 2144 |
# Update body
|
| 2145 |
body["messages"] = optimized_messages
|
|
@@ -4475,13 +4468,7 @@ class HeadroomProxy:
|
|
| 4475 |
f"[{request_id}] CCR: Tool already present (MCP?), skipped injection for hashes: {injector.detected_hashes}"
|
| 4476 |
)
|
| 4477 |
|
| 4478 |
-
# Query Echo:
|
| 4479 |
-
if tokens_saved > 0:
|
| 4480 |
-
from headroom.transforms.query_echo import extract_user_query, inject_query_echo
|
| 4481 |
-
|
| 4482 |
-
user_query = extract_user_query(messages)
|
| 4483 |
-
if inject_query_echo(optimized_messages, user_query, tokens_saved, original_tokens):
|
| 4484 |
-
logger.debug(f"[{request_id}] Query echo injected after compression")
|
| 4485 |
|
| 4486 |
body["messages"] = optimized_messages
|
| 4487 |
if tools is not None:
|
|
@@ -5542,13 +5529,7 @@ class HeadroomProxy:
|
|
| 5542 |
tokens_saved = max(0, original_tokens - optimized_tokens)
|
| 5543 |
optimization_latency = (time.time() - start_time) * 1000
|
| 5544 |
|
| 5545 |
-
# Query Echo:
|
| 5546 |
-
if tokens_saved > 0:
|
| 5547 |
-
from headroom.transforms.query_echo import extract_user_query, inject_query_echo
|
| 5548 |
-
|
| 5549 |
-
user_query = extract_user_query(messages)
|
| 5550 |
-
if inject_query_echo(optimized_messages, user_query, tokens_saved, original_tokens):
|
| 5551 |
-
logger.debug(f"[{request_id}] Query echo injected after Gemini compression")
|
| 5552 |
|
| 5553 |
# Convert back to Gemini format if optimized
|
| 5554 |
if optimized_messages != messages:
|
|
@@ -5863,7 +5844,9 @@ class HeadroomProxy:
|
|
| 5863 |
logger.debug(f"[{request_id}] Failed to parse Gemini token count response: {e}")
|
| 5864 |
|
| 5865 |
# Track stats
|
| 5866 |
-
tokens_saved =
|
|
|
|
|
|
|
| 5867 |
|
| 5868 |
await self.metrics.record_request(
|
| 5869 |
provider="gemini",
|
|
|
|
| 250 |
_openai_prefixes = ("gpt", "o1", "o3", "o4")
|
| 251 |
is_match = (
|
| 252 |
(provider == "anthropic" and "claude" in model_name)
|
| 253 |
+
or (provider == "openai" and any(p in model_name for p in _openai_prefixes))
|
|
|
|
|
|
|
| 254 |
or (provider == "gemini" and "gemini" in model_name)
|
| 255 |
or (provider == "bedrock" and "claude" in model_name)
|
| 256 |
)
|
|
|
|
| 2130 |
f"[{request_id}] Memory: Added beta header: {key}={headers[key]}"
|
| 2131 |
)
|
| 2132 |
|
| 2133 |
+
# Query Echo: disabled — hurts prefix caching in long conversations.
|
| 2134 |
+
# The echo changes every turn, invalidating the cached prefix.
|
| 2135 |
+
# To re-enable, uncomment and set query_echo_enabled on ProxyConfig.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2136 |
|
| 2137 |
# Update body
|
| 2138 |
body["messages"] = optimized_messages
|
|
|
|
| 4468 |
f"[{request_id}] CCR: Tool already present (MCP?), skipped injection for hashes: {injector.detected_hashes}"
|
| 4469 |
)
|
| 4470 |
|
| 4471 |
+
# Query Echo: disabled — hurts prefix caching in long conversations.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4472 |
|
| 4473 |
body["messages"] = optimized_messages
|
| 4474 |
if tools is not None:
|
|
|
|
| 5529 |
tokens_saved = max(0, original_tokens - optimized_tokens)
|
| 5530 |
optimization_latency = (time.time() - start_time) * 1000
|
| 5531 |
|
| 5532 |
+
# Query Echo: disabled — hurts prefix caching in long conversations.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5533 |
|
| 5534 |
# Convert back to Gemini format if optimized
|
| 5535 |
if optimized_messages != messages:
|
|
|
|
| 5844 |
logger.debug(f"[{request_id}] Failed to parse Gemini token count response: {e}")
|
| 5845 |
|
| 5846 |
# Track stats
|
| 5847 |
+
tokens_saved = (
|
| 5848 |
+
max(0, original_tokens - compressed_tokens) if compressed_tokens > 0 else 0
|
| 5849 |
+
)
|
| 5850 |
|
| 5851 |
await self.metrics.record_request(
|
| 5852 |
provider="gemini",
|