Spaces:
Build error
Build error
Commit ·
313fe01
1
Parent(s): dd832fe
Fix ruff formatting
Browse files- devto-article.md +295 -0
- headroom/cache/compression_store.py +2 -5
- headroom/telemetry/models.py +8 -14
- headroom/telemetry/toin.py +1 -3
- headroom/transforms/smart_crusher.py +8 -3
- tests/test_cache/test_client_integration.py +6 -11
- tests/test_proxy_ccr.py +4 -3
- tests/test_toin_field_learning.py +12 -9
devto-article.md
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# I Was Wasting 85% of My LLM Tokens on JSON Boilerplate
|
| 2 |
+
|
| 3 |
+
I recently built an agent to handle some SRE tasks—fetching logs, querying databases, searching code. It worked, but when I looked at the traces, I was annoyed.
|
| 4 |
+
|
| 5 |
+
It wasn't just that it was expensive (though the bill was climbing). It was the sheer **inefficiency**.
|
| 6 |
+
|
| 7 |
+
I looked at a single tool output—a search for Python files. It was 40,000 tokens.
|
| 8 |
+
About 35,000 of those tokens were just `"type": "file"` and `"language": "python"` repeated 2,000 times.
|
| 9 |
+
|
| 10 |
+
We are paying premium compute prices to force state-of-the-art models to read standard JSON boilerplate.
|
| 11 |
+
|
| 12 |
+
I couldn't find a tool that solved this without breaking the agent, so I wrote one. It's called **Headroom**. It's a context optimization layer that sits between your app and your LLM. It compresses context by ~85% without losing semantic meaning.
|
| 13 |
+
|
| 14 |
+
It's open source (Apache-2.0). If you just want the code:
|
| 15 |
+
**[github.com/chopratejas/headroom](https://github.com/chopratejas/headroom)**
|
| 16 |
+
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
## Why Truncation and Summarization Don't Work
|
| 20 |
+
|
| 21 |
+
When your context window fills up, the standard industry solution is **truncation** (chopping off the oldest messages or the middle of the document).
|
| 22 |
+
|
| 23 |
+
But for an agent, truncation is dangerous.
|
| 24 |
+
|
| 25 |
+
* If you chop the middle of a log file, you might lose the one error line that explains the crash.
|
| 26 |
+
* If you chop a file list, you might lose the exact config file the user asked for.
|
| 27 |
+
|
| 28 |
+
I tried **summarization** (using a cheaper model to summarize the data first), but that introduced hallucination. I had a summarizer tell me a deployment "looked fine" because it ignored specific error codes in the raw log.
|
| 29 |
+
|
| 30 |
+
I needed a third option: **Lossless compression.** Or at least, "intent-lossless."
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
## The Core Idea: Statistical Analysis, Not Blind Truncation
|
| 35 |
+
|
| 36 |
+
I realized that 90% of the data in a tool output is just schema scaffolding. The LLM doesn't need to see `status: active` repeated a thousand times. It needs the **anomalies**.
|
| 37 |
+
|
| 38 |
+
Headroom's SmartCrusher runs statistical analysis before touching your data:
|
| 39 |
+
|
| 40 |
+
**1. Constant Factoring**
|
| 41 |
+
If every item in an array has `"type": "file"`, it doesn't repeat that 2,000 times. It extracts constants once.
|
| 42 |
+
|
| 43 |
+
**2. Outlier Detection**
|
| 44 |
+
It calculates standard deviation of numerical fields. It preserves the spikes—the values that are >2σ from the mean. Those are usually what matters.
|
| 45 |
+
|
| 46 |
+
**3. Error Preservation**
|
| 47 |
+
Hard rule: never discard strings that look like stack traces, error messages, or failures. Errors are sacred.
|
| 48 |
+
|
| 49 |
+
**4. Relevance Scoring**
|
| 50 |
+
If you searched for "auth", items containing "auth" get preserved. Uses BM25 + semantic embeddings (hybrid scoring) to match items against the user's query context.
|
| 51 |
+
|
| 52 |
+
**5. First/Last Retention**
|
| 53 |
+
Always keeps first few and last few items. The LLM expects to see some examples, and recency matters.
|
| 54 |
+
|
| 55 |
+
The result: 40,000 tokens → 4,000 tokens. Same information density. No hallucination risk.
|
| 56 |
+
|
| 57 |
+
---
|
| 58 |
+
|
| 59 |
+
## CCR: Making Compression Reversible
|
| 60 |
+
|
| 61 |
+
Here's the insight that changed everything: **compression should be reversible**.
|
| 62 |
+
|
| 63 |
+
I call the architecture **CCR** (Compress-Cache-Retrieve):
|
| 64 |
+
|
| 65 |
+
### 1. Compress
|
| 66 |
+
SmartCrusher compresses the tool output from 2,000 items to 20.
|
| 67 |
+
|
| 68 |
+
### 2. Cache
|
| 69 |
+
The original 2,000 items are cached locally (5-minute TTL, LRU eviction).
|
| 70 |
+
|
| 71 |
+
### 3. Retrieve
|
| 72 |
+
Headroom injects a tool called `headroom_retrieve()` into the LLM's context. If the model looks at the compressed summary and decides it needs more data—maybe the user asked a follow-up question—it can call that tool. Headroom fetches from the cache and returns the relevant items.
|
| 73 |
+
|
| 74 |
+
This changes the risk calculus. You can compress aggressively (90%+) because **nothing is ever truly lost**. The model can always "unzip" what it needs.
|
| 75 |
+
|
| 76 |
+
I've had conversations like this:
|
| 77 |
+
|
| 78 |
+
```
|
| 79 |
+
Turn 1: "Search for all Python files"
|
| 80 |
+
→ 1000 files returned, compressed to 15
|
| 81 |
+
|
| 82 |
+
Turn 5: "Actually, what was that file handling JWT tokens?"
|
| 83 |
+
→ LLM calls headroom_retrieve("jwt")
|
| 84 |
+
→ Returns jwt_handler.py from cached data
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
No extra API calls. No "sorry, I don't have that information anymore."
|
| 88 |
+
|
| 89 |
+
---
|
| 90 |
+
|
| 91 |
+
## TOIN: The Network Effect
|
| 92 |
+
|
| 93 |
+
Here's where it gets interesting. Headroom learns from compression patterns.
|
| 94 |
+
|
| 95 |
+
**TOIN** (Tool Output Intelligence Network) tracks—anonymously—what happens after compression:
|
| 96 |
+
- Which fields get retrieved most often?
|
| 97 |
+
- Which tool types have high retrieval rates?
|
| 98 |
+
- What query patterns trigger retrievals?
|
| 99 |
+
|
| 100 |
+
This data feeds back into compression recommendations. If TOIN learns that users frequently retrieve `error_code` fields after compression, it tells SmartCrusher to preserve `error_code` more aggressively next time.
|
| 101 |
+
|
| 102 |
+
Privacy is built in:
|
| 103 |
+
- No actual data values stored
|
| 104 |
+
- Tool names are structure hashes
|
| 105 |
+
- Field names are SHA256[:8] hashes
|
| 106 |
+
- No user identifiers
|
| 107 |
+
|
| 108 |
+
The network effect: more users → more compression events → better recommendations for everyone.
|
| 109 |
+
|
| 110 |
+
---
|
| 111 |
+
|
| 112 |
+
## Memory: Cross-Conversation Learning
|
| 113 |
+
|
| 114 |
+
Agents often need to remember things across conversations. "I prefer dark mode." "My timezone is PST." "I'm working on the auth refactor."
|
| 115 |
+
|
| 116 |
+
Headroom has a memory system that extracts and stores these facts automatically.
|
| 117 |
+
|
| 118 |
+
Two approaches:
|
| 119 |
+
|
| 120 |
+
**Fast Memory (Recommended)**
|
| 121 |
+
Zero extra latency. The LLM outputs a `<memory>` block inline with its response. Headroom parses it out and stores the memory.
|
| 122 |
+
|
| 123 |
+
```python
|
| 124 |
+
from headroom.memory import with_fast_memory
|
| 125 |
+
client = with_fast_memory(OpenAI(), user_id="alice")
|
| 126 |
+
|
| 127 |
+
# Memories extracted automatically from responses
|
| 128 |
+
# Injected automatically into future requests
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
**Background Memory**
|
| 132 |
+
Separate LLM call extracts memories asynchronously. More accurate but adds latency.
|
| 133 |
+
|
| 134 |
+
```python
|
| 135 |
+
from headroom import with_memory
|
| 136 |
+
client = with_memory(OpenAI(), user_id="alice")
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
Memories are stored locally (SQLite) and injected into future conversations. The model remembers that Alice prefers dark mode without you managing state.
|
| 140 |
+
|
| 141 |
+
---
|
| 142 |
+
|
| 143 |
+
## The Transform Pipeline
|
| 144 |
+
|
| 145 |
+
Headroom runs four transforms on each request:
|
| 146 |
+
|
| 147 |
+
### 1. CacheAligner
|
| 148 |
+
LLM providers offer cached token pricing (Anthropic: 90% off, OpenAI: 50% off). But caching only works if your prompt prefix is stable.
|
| 149 |
+
|
| 150 |
+
Problem: your system prompt probably has a timestamp. `Current time: 2024-01-15 10:32:45`. That breaks caching.
|
| 151 |
+
|
| 152 |
+
CacheAligner extracts dynamic content and moves it to the end, stabilizing the prefix. Same information, better cache hits.
|
| 153 |
+
|
| 154 |
+
### 2. SmartCrusher
|
| 155 |
+
The statistical compression engine. Analyzes arrays, detects patterns, preserves anomalies, factors constants.
|
| 156 |
+
|
| 157 |
+
### 3. ContentRouter
|
| 158 |
+
Different content needs different compression. Code isn't JSON isn't logs isn't prose.
|
| 159 |
+
|
| 160 |
+
ContentRouter uses ML-based content detection to route data to specialized compressors:
|
| 161 |
+
- **Code** → AST-aware compression (tree-sitter)
|
| 162 |
+
- **JSON** → SmartCrusher
|
| 163 |
+
- **Logs** → LogCompressor (clusters similar messages)
|
| 164 |
+
- **Text** → Optional LLMLingua integration (20x compression, adds latency)
|
| 165 |
+
|
| 166 |
+
### 4. RollingWindow
|
| 167 |
+
When context exceeds the model limit, something has to go. RollingWindow drops oldest tool calls + responses together (never orphans data), preserves system prompt and recent turns.
|
| 168 |
+
|
| 169 |
+
---
|
| 170 |
+
|
| 171 |
+
## Three Ways to Use It
|
| 172 |
+
|
| 173 |
+
### Option 1: Proxy Server (Zero Code Changes)
|
| 174 |
+
|
| 175 |
+
```bash
|
| 176 |
+
pip install headroom-ai
|
| 177 |
+
headroom proxy --port 8787
|
| 178 |
+
```
|
| 179 |
+
|
| 180 |
+
Point your OpenAI client to `http://localhost:8787/v1`. Done.
|
| 181 |
+
|
| 182 |
+
```python
|
| 183 |
+
from openai import OpenAI
|
| 184 |
+
client = OpenAI(base_url="http://localhost:8787/v1")
|
| 185 |
+
# No other changes
|
| 186 |
+
```
|
| 187 |
+
|
| 188 |
+
Works with Claude Code, Cursor, any OpenAI-compatible client.
|
| 189 |
+
|
| 190 |
+
### Option 2: SDK Wrapper
|
| 191 |
+
|
| 192 |
+
```python
|
| 193 |
+
from headroom import HeadroomClient
|
| 194 |
+
from openai import OpenAI
|
| 195 |
+
|
| 196 |
+
client = HeadroomClient(OpenAI())
|
| 197 |
+
|
| 198 |
+
response = client.chat.completions.create(
|
| 199 |
+
model="gpt-4o",
|
| 200 |
+
messages=[...],
|
| 201 |
+
headroom_mode="optimize" # or "audit" or "simulate"
|
| 202 |
+
)
|
| 203 |
+
```
|
| 204 |
+
|
| 205 |
+
Three modes:
|
| 206 |
+
- **audit**: Observe only. Logs what would be optimized, doesn't change anything.
|
| 207 |
+
- **optimize**: Apply compression. This is what saves tokens.
|
| 208 |
+
- **simulate**: Dry run. Returns the optimized messages without calling the API.
|
| 209 |
+
|
| 210 |
+
Start with `audit` to see potential savings, then flip to `optimize` when you're confident.
|
| 211 |
+
|
| 212 |
+
### Option 3: Framework Integrations
|
| 213 |
+
|
| 214 |
+
**LangChain:**
|
| 215 |
+
```python
|
| 216 |
+
from langchain_openai import ChatOpenAI
|
| 217 |
+
from headroom.integrations.langchain import HeadroomChatModel
|
| 218 |
+
|
| 219 |
+
base_model = ChatOpenAI(model="gpt-4o")
|
| 220 |
+
model = HeadroomChatModel(base_model, mode="optimize")
|
| 221 |
+
|
| 222 |
+
# Use in any chain or agent
|
| 223 |
+
chain = prompt | model | parser
|
| 224 |
+
```
|
| 225 |
+
|
| 226 |
+
**Agno:**
|
| 227 |
+
```python
|
| 228 |
+
from agno.agent import Agent
|
| 229 |
+
from headroom.integrations.agno import HeadroomAgnoModel
|
| 230 |
+
|
| 231 |
+
model = HeadroomAgnoModel(original_model, mode="optimize")
|
| 232 |
+
agent = Agent(model=model, tools=[...])
|
| 233 |
+
```
|
| 234 |
+
|
| 235 |
+
**MCP (Model Context Protocol):**
|
| 236 |
+
```python
|
| 237 |
+
from headroom.integrations.mcp import compress_tool_result
|
| 238 |
+
|
| 239 |
+
# Compress any tool result before returning to LLM
|
| 240 |
+
compressed = compress_tool_result(tool_name, result_data)
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
---
|
| 244 |
+
|
| 245 |
+
## Real Numbers
|
| 246 |
+
|
| 247 |
+
I've been running this in production for months. Here's what the token reduction looks like:
|
| 248 |
+
|
| 249 |
+
| Workload | Before | After | Savings |
|
| 250 |
+
|----------|--------|-------|---------|
|
| 251 |
+
| Log Analysis | 22,000 | 3,300 | 85% |
|
| 252 |
+
| Code Search | 45,000 | 4,500 | 90% |
|
| 253 |
+
| Database Queries | 18,000 | 2,700 | 85% |
|
| 254 |
+
| Long Conversations | 80,000 | 32,000 | 60% |
|
| 255 |
+
|
| 256 |
+
Latency overhead: 3-5ms per request. No extra LLM calls.
|
| 257 |
+
|
| 258 |
+
---
|
| 259 |
+
|
| 260 |
+
## What's Coming Next
|
| 261 |
+
|
| 262 |
+
This is actively maintained. On the roadmap:
|
| 263 |
+
|
| 264 |
+
**More Frameworks**
|
| 265 |
+
- CrewAI integration
|
| 266 |
+
- AutoGen integration
|
| 267 |
+
- Semantic Kernel integration
|
| 268 |
+
|
| 269 |
+
**Managed Storage**
|
| 270 |
+
- Cloud-hosted TOIN backend (opt-in)
|
| 271 |
+
- Cross-device memory sync
|
| 272 |
+
- Team-shared compression patterns
|
| 273 |
+
|
| 274 |
+
**Better Compression**
|
| 275 |
+
- Domain-specific profiles (SRE, coding, data analysis)
|
| 276 |
+
- Custom compressor plugins
|
| 277 |
+
- Streaming compression for real-time tools
|
| 278 |
+
|
| 279 |
+
---
|
| 280 |
+
|
| 281 |
+
## Why I Built This
|
| 282 |
+
|
| 283 |
+
I'm a believer that we're in the "optimization phase" of the AI hype cycle. Getting things to work is table stakes; getting them to work cheaply and reliably is the actual engineering work.
|
| 284 |
+
|
| 285 |
+
Headroom is my attempt to fix the "context bloat" problem properly. Not with heuristics or truncation, but with statistical analysis and reversible compression.
|
| 286 |
+
|
| 287 |
+
It runs entirely locally. No data leaves your machine (except to OpenAI/Anthropic as usual). Apache-2.0 licensed.
|
| 288 |
+
|
| 289 |
+
**Repo:** [github.com/chopratejas/headroom](https://github.com/chopratejas/headroom)
|
| 290 |
+
|
| 291 |
+
If you find bugs or have ideas, open an issue. I'm actively maintaining this.
|
| 292 |
+
|
| 293 |
+
---
|
| 294 |
+
|
| 295 |
+
*Tags: #llm #ai #python #openai #anthropic #agents #optimization*
|
headroom/cache/compression_store.py
CHANGED
|
@@ -741,16 +741,13 @@ class CompressionStore:
|
|
| 741 |
# Handle both direct arrays and wrapped arrays
|
| 742 |
if isinstance(parsed, list):
|
| 743 |
# Filter to dicts only (field learning needs dict items)
|
| 744 |
-
retrieved_items = [
|
| 745 |
-
item for item in parsed if isinstance(item, dict)
|
| 746 |
-
]
|
| 747 |
elif isinstance(parsed, dict):
|
| 748 |
# Check for common wrapper patterns: {"items": [...], "results": [...]}
|
| 749 |
for key in ("items", "results", "data", "records"):
|
| 750 |
if key in parsed and isinstance(parsed[key], list):
|
| 751 |
retrieved_items = [
|
| 752 |
-
item for item in parsed[key]
|
| 753 |
-
if isinstance(item, dict)
|
| 754 |
]
|
| 755 |
break
|
| 756 |
except (json.JSONDecodeError, TypeError):
|
|
|
|
| 741 |
# Handle both direct arrays and wrapped arrays
|
| 742 |
if isinstance(parsed, list):
|
| 743 |
# Filter to dicts only (field learning needs dict items)
|
| 744 |
+
retrieved_items = [item for item in parsed if isinstance(item, dict)]
|
|
|
|
|
|
|
| 745 |
elif isinstance(parsed, dict):
|
| 746 |
# Check for common wrapper patterns: {"items": [...], "results": [...]}
|
| 747 |
for key in ("items", "results", "data", "records"):
|
| 748 |
if key in parsed and isinstance(parsed[key], list):
|
| 749 |
retrieved_items = [
|
| 750 |
+
item for item in parsed[key] if isinstance(item, dict)
|
|
|
|
| 751 |
]
|
| 752 |
break
|
| 753 |
except (json.JSONDecodeError, TypeError):
|
headroom/telemetry/models.py
CHANGED
|
@@ -436,7 +436,7 @@ class FieldSemantics:
|
|
| 436 |
"field_hash": self.field_hash,
|
| 437 |
"inferred_type": self.inferred_type,
|
| 438 |
"confidence": self.confidence,
|
| 439 |
-
"important_value_hashes": self.important_value_hashes[:self.MAX_IMPORTANT_VALUES],
|
| 440 |
"default_value_hash": self.default_value_hash,
|
| 441 |
"value_retrieval_frequency": dict(
|
| 442 |
sorted(
|
|
@@ -514,9 +514,7 @@ class FieldSemantics:
|
|
| 514 |
)[: self.MAX_IMPORTANT_VALUES]
|
| 515 |
|
| 516 |
# Track query operators
|
| 517 |
-
self.query_operator_frequency[operator] = (
|
| 518 |
-
self.query_operator_frequency.get(operator, 0) + 1
|
| 519 |
-
)
|
| 520 |
|
| 521 |
def record_compression_stats(
|
| 522 |
self,
|
|
@@ -543,9 +541,7 @@ class FieldSemantics:
|
|
| 543 |
self.total_unique_values_seen = int(
|
| 544 |
(self.total_unique_values_seen * (n - 1) + unique_values) / n
|
| 545 |
)
|
| 546 |
-
self.total_values_seen = int(
|
| 547 |
-
(self.total_values_seen * (n - 1) + total_values) / n
|
| 548 |
-
)
|
| 549 |
self.most_common_value_frequency = (
|
| 550 |
self.most_common_value_frequency * (n - 1) + most_common_frequency
|
| 551 |
) / n
|
|
@@ -569,9 +565,7 @@ class FieldSemantics:
|
|
| 569 |
return
|
| 570 |
|
| 571 |
# Calculate metrics
|
| 572 |
-
uniqueness_ratio = (
|
| 573 |
-
self.total_unique_values_seen / max(1, self.total_values_seen)
|
| 574 |
-
)
|
| 575 |
has_dominant_default = self.most_common_value_frequency > 0.7
|
| 576 |
retrieval_diversity = len(self.value_retrieval_frequency) / max(1, self.retrieval_count)
|
| 577 |
|
|
@@ -598,15 +592,15 @@ class FieldSemantics:
|
|
| 598 |
# ERROR_INDICATOR: Has dominant default + retrievals are for non-default values
|
| 599 |
elif has_dominant_default and self.default_value_hash:
|
| 600 |
# Check if retrieved values are different from default
|
| 601 |
-
default_retrieval_count = self.value_retrieval_frequency.get(
|
| 602 |
-
self.default_value_hash, 0
|
| 603 |
-
)
|
| 604 |
non_default_retrieval_ratio = 1 - (
|
| 605 |
default_retrieval_count / max(1, self.retrieval_count)
|
| 606 |
)
|
| 607 |
if non_default_retrieval_ratio > 0.7:
|
| 608 |
inferred = "error_indicator"
|
| 609 |
-
confidence = min(
|
|
|
|
|
|
|
| 610 |
|
| 611 |
# STATUS: Low uniqueness + specific values retrieved
|
| 612 |
elif uniqueness_ratio < 0.2 and retrieval_diversity < 0.5:
|
|
|
|
| 436 |
"field_hash": self.field_hash,
|
| 437 |
"inferred_type": self.inferred_type,
|
| 438 |
"confidence": self.confidence,
|
| 439 |
+
"important_value_hashes": self.important_value_hashes[: self.MAX_IMPORTANT_VALUES],
|
| 440 |
"default_value_hash": self.default_value_hash,
|
| 441 |
"value_retrieval_frequency": dict(
|
| 442 |
sorted(
|
|
|
|
| 514 |
)[: self.MAX_IMPORTANT_VALUES]
|
| 515 |
|
| 516 |
# Track query operators
|
| 517 |
+
self.query_operator_frequency[operator] = self.query_operator_frequency.get(operator, 0) + 1
|
|
|
|
|
|
|
| 518 |
|
| 519 |
def record_compression_stats(
|
| 520 |
self,
|
|
|
|
| 541 |
self.total_unique_values_seen = int(
|
| 542 |
(self.total_unique_values_seen * (n - 1) + unique_values) / n
|
| 543 |
)
|
| 544 |
+
self.total_values_seen = int((self.total_values_seen * (n - 1) + total_values) / n)
|
|
|
|
|
|
|
| 545 |
self.most_common_value_frequency = (
|
| 546 |
self.most_common_value_frequency * (n - 1) + most_common_frequency
|
| 547 |
) / n
|
|
|
|
| 565 |
return
|
| 566 |
|
| 567 |
# Calculate metrics
|
| 568 |
+
uniqueness_ratio = self.total_unique_values_seen / max(1, self.total_values_seen)
|
|
|
|
|
|
|
| 569 |
has_dominant_default = self.most_common_value_frequency > 0.7
|
| 570 |
retrieval_diversity = len(self.value_retrieval_frequency) / max(1, self.retrieval_count)
|
| 571 |
|
|
|
|
| 592 |
# ERROR_INDICATOR: Has dominant default + retrievals are for non-default values
|
| 593 |
elif has_dominant_default and self.default_value_hash:
|
| 594 |
# Check if retrieved values are different from default
|
| 595 |
+
default_retrieval_count = self.value_retrieval_frequency.get(self.default_value_hash, 0)
|
|
|
|
|
|
|
| 596 |
non_default_retrieval_ratio = 1 - (
|
| 597 |
default_retrieval_count / max(1, self.retrieval_count)
|
| 598 |
)
|
| 599 |
if non_default_retrieval_ratio > 0.7:
|
| 600 |
inferred = "error_indicator"
|
| 601 |
+
confidence = min(
|
| 602 |
+
0.9, non_default_retrieval_ratio * self.most_common_value_frequency
|
| 603 |
+
)
|
| 604 |
|
| 605 |
# STATUS: Low uniqueness + specific values retrieved
|
| 606 |
elif uniqueness_ratio < 0.2 and retrieval_diversity < 0.5:
|
headroom/telemetry/toin.py
CHANGED
|
@@ -169,9 +169,7 @@ class ToolPattern:
|
|
| 169 |
"skip_compression_recommended": self.skip_compression_recommended,
|
| 170 |
"preserve_fields": self.preserve_fields,
|
| 171 |
# Field-level semantics (TOIN Evolution)
|
| 172 |
-
"field_semantics": {
|
| 173 |
-
k: v.to_dict() for k, v in self.field_semantics.items()
|
| 174 |
-
},
|
| 175 |
"sample_size": self.sample_size,
|
| 176 |
"user_count": self.user_count,
|
| 177 |
"confidence": self.confidence,
|
|
|
|
| 169 |
"skip_compression_recommended": self.skip_compression_recommended,
|
| 170 |
"preserve_fields": self.preserve_fields,
|
| 171 |
# Field-level semantics (TOIN Evolution)
|
| 172 |
+
"field_semantics": {k: v.to_dict() for k, v in self.field_semantics.items()},
|
|
|
|
|
|
|
| 173 |
"sample_size": self.sample_size,
|
| 174 |
"user_count": self.user_count,
|
| 175 |
"confidence": self.confidence,
|
headroom/transforms/smart_crusher.py
CHANGED
|
@@ -645,7 +645,8 @@ def _detect_items_by_learned_semantics(
|
|
| 645 |
# Build a quick lookup for field_hash -> FieldSemantics
|
| 646 |
# Pre-filter to fields with sufficient confidence
|
| 647 |
confident_semantics = {
|
| 648 |
-
fh: fs
|
|
|
|
| 649 |
if fs.confidence >= 0.3 and fs.inferred_type != "unknown"
|
| 650 |
}
|
| 651 |
|
|
@@ -1599,7 +1600,9 @@ class SmartCrusher(Transform):
|
|
| 1599 |
return keep_indices
|
| 1600 |
|
| 1601 |
# Use provided field_semantics or fall back to instance variable (set by crush())
|
| 1602 |
-
effective_field_semantics = field_semantics or getattr(
|
|
|
|
|
|
|
| 1603 |
|
| 1604 |
# Identify error items using KEYWORD detection (preservation guarantee)
|
| 1605 |
# This ensures ALL error items are kept, regardless of frequency
|
|
@@ -2024,7 +2027,9 @@ class SmartCrusher(Transform):
|
|
| 2024 |
# === TOIN Evolution: Extract field semantics for signal detection ===
|
| 2025 |
# Store temporarily on instance for use in _prioritize_indices
|
| 2026 |
# This enables learned signal detection without changing all method signatures
|
| 2027 |
-
self._current_field_semantics =
|
|
|
|
|
|
|
| 2028 |
|
| 2029 |
# Local feedback hints (if TOIN didn't apply)
|
| 2030 |
if not toin_hint_applied and self.config.use_feedback_hints and tool_name:
|
|
|
|
| 645 |
# Build a quick lookup for field_hash -> FieldSemantics
|
| 646 |
# Pre-filter to fields with sufficient confidence
|
| 647 |
confident_semantics = {
|
| 648 |
+
fh: fs
|
| 649 |
+
for fh, fs in field_semantics.items()
|
| 650 |
if fs.confidence >= 0.3 and fs.inferred_type != "unknown"
|
| 651 |
}
|
| 652 |
|
|
|
|
| 1600 |
return keep_indices
|
| 1601 |
|
| 1602 |
# Use provided field_semantics or fall back to instance variable (set by crush())
|
| 1603 |
+
effective_field_semantics = field_semantics or getattr(
|
| 1604 |
+
self, "_current_field_semantics", None
|
| 1605 |
+
)
|
| 1606 |
|
| 1607 |
# Identify error items using KEYWORD detection (preservation guarantee)
|
| 1608 |
# This ensures ALL error items are kept, regardless of frequency
|
|
|
|
| 2027 |
# === TOIN Evolution: Extract field semantics for signal detection ===
|
| 2028 |
# Store temporarily on instance for use in _prioritize_indices
|
| 2029 |
# This enables learned signal detection without changing all method signatures
|
| 2030 |
+
self._current_field_semantics = (
|
| 2031 |
+
toin_hint.field_semantics if toin_hint.field_semantics else None
|
| 2032 |
+
)
|
| 2033 |
|
| 2034 |
# Local feedback hints (if TOIN didn't apply)
|
| 2035 |
if not toin_hint_applied and self.config.use_feedback_hints and tool_name:
|
tests/test_cache/test_client_integration.py
CHANGED
|
@@ -388,9 +388,9 @@ class TestCacheOptimizerInvocation:
|
|
| 388 |
headroom_meta = result.headroom
|
| 389 |
|
| 390 |
# Check that cache optimizer was reported
|
| 391 |
-
assert headroom_meta.cache_optimizer_used is not None or
|
| 392 |
-
|
| 393 |
-
|
| 394 |
|
| 395 |
@patch("headroom.storage.sqlite.SQLiteStorage.save")
|
| 396 |
def test_optimizer_not_called_in_audit_mode(self, mock_save, temp_db):
|
|
@@ -430,9 +430,7 @@ class TestCacheOptimizerInvocation:
|
|
| 430 |
)
|
| 431 |
|
| 432 |
# Optimizer should NOT be called in AUDIT mode
|
| 433 |
-
assert not spy_optimize.called,
|
| 434 |
-
"Cache optimizer should NOT be called in AUDIT mode"
|
| 435 |
-
)
|
| 436 |
|
| 437 |
|
| 438 |
class TestSemanticCacheIntegration:
|
|
@@ -444,9 +442,7 @@ class TestSemanticCacheIntegration:
|
|
| 444 |
"""
|
| 445 |
|
| 446 |
@patch("headroom.storage.sqlite.SQLiteStorage.save")
|
| 447 |
-
def test_semantic_cache_hit_returns_cached_response_without_api_call(
|
| 448 |
-
self, mock_save, temp_db
|
| 449 |
-
):
|
| 450 |
"""CRITICAL: Verify semantic cache hit returns cached response without API call.
|
| 451 |
|
| 452 |
This test catches the gap where semantic cache is enabled but cached
|
|
@@ -565,8 +561,7 @@ class TestSessionStatsTracking:
|
|
| 565 |
after_requests = after_stats["session"]["requests_total"]
|
| 566 |
|
| 567 |
assert after_requests == initial_requests + 1, (
|
| 568 |
-
f"requests_total should increment. "
|
| 569 |
-
f"Before: {initial_requests}, After: {after_requests}"
|
| 570 |
)
|
| 571 |
assert after_stats["session"]["requests_audit"] >= 1, (
|
| 572 |
"requests_audit should be at least 1 after AUDIT mode request"
|
|
|
|
| 388 |
headroom_meta = result.headroom
|
| 389 |
|
| 390 |
# Check that cache optimizer was reported
|
| 391 |
+
assert headroom_meta.cache_optimizer_used is not None or any(
|
| 392 |
+
"cache_optimizer" in t for t in (headroom_meta.transforms_applied or [])
|
| 393 |
+
), "Cache optimizer usage should be reported in metadata"
|
| 394 |
|
| 395 |
@patch("headroom.storage.sqlite.SQLiteStorage.save")
|
| 396 |
def test_optimizer_not_called_in_audit_mode(self, mock_save, temp_db):
|
|
|
|
| 430 |
)
|
| 431 |
|
| 432 |
# Optimizer should NOT be called in AUDIT mode
|
| 433 |
+
assert not spy_optimize.called, "Cache optimizer should NOT be called in AUDIT mode"
|
|
|
|
|
|
|
| 434 |
|
| 435 |
|
| 436 |
class TestSemanticCacheIntegration:
|
|
|
|
| 442 |
"""
|
| 443 |
|
| 444 |
@patch("headroom.storage.sqlite.SQLiteStorage.save")
|
| 445 |
+
def test_semantic_cache_hit_returns_cached_response_without_api_call(self, mock_save, temp_db):
|
|
|
|
|
|
|
| 446 |
"""CRITICAL: Verify semantic cache hit returns cached response without API call.
|
| 447 |
|
| 448 |
This test catches the gap where semantic cache is enabled but cached
|
|
|
|
| 561 |
after_requests = after_stats["session"]["requests_total"]
|
| 562 |
|
| 563 |
assert after_requests == initial_requests + 1, (
|
| 564 |
+
f"requests_total should increment. Before: {initial_requests}, After: {after_requests}"
|
|
|
|
| 565 |
)
|
| 566 |
assert after_stats["session"]["requests_audit"] >= 1, (
|
| 567 |
"requests_audit should be at least 1 after AUDIT mode request"
|
tests/test_proxy_ccr.py
CHANGED
|
@@ -386,7 +386,9 @@ class TestEndToEndTOINIntegration:
|
|
| 386 |
yield client
|
| 387 |
reset_compression_store()
|
| 388 |
|
| 389 |
-
def test_pipeline_compresses_tool_output_and_records_toin(
|
|
|
|
|
|
|
| 390 |
"""CRITICAL: Verify SmartCrusher compression records events in TOIN.
|
| 391 |
|
| 392 |
This tests the production code path:
|
|
@@ -467,8 +469,7 @@ class TestEndToEndTOINIntegration:
|
|
| 467 |
|
| 468 |
# Verify SmartCrusher was invoked (transform name starts with smart_crush)
|
| 469 |
smart_crush_applied = any(
|
| 470 |
-
t.startswith("smart_crush") or t.startswith("smart:")
|
| 471 |
-
for t in result.transforms_applied
|
| 472 |
)
|
| 473 |
assert smart_crush_applied, (
|
| 474 |
f"SmartCrusher should be in transforms: {result.transforms_applied}"
|
|
|
|
| 386 |
yield client
|
| 387 |
reset_compression_store()
|
| 388 |
|
| 389 |
+
def test_pipeline_compresses_tool_output_and_records_toin(
|
| 390 |
+
self, fresh_toin, client_with_optimization
|
| 391 |
+
):
|
| 392 |
"""CRITICAL: Verify SmartCrusher compression records events in TOIN.
|
| 393 |
|
| 394 |
This tests the production code path:
|
|
|
|
| 469 |
|
| 470 |
# Verify SmartCrusher was invoked (transform name starts with smart_crush)
|
| 471 |
smart_crush_applied = any(
|
| 472 |
+
t.startswith("smart_crush") or t.startswith("smart:") for t in result.transforms_applied
|
|
|
|
| 473 |
)
|
| 474 |
assert smart_crush_applied, (
|
| 475 |
f"SmartCrusher should be in transforms: {result.transforms_applied}"
|
tests/test_toin_field_learning.py
CHANGED
|
@@ -245,7 +245,9 @@ class TestFieldSemanticsLearning:
|
|
| 245 |
|
| 246 |
# VERIFY
|
| 247 |
assert fs.is_value_important(pending_hash), "Retrieved value should be important"
|
| 248 |
-
assert not fs.is_value_important(unknown_hash),
|
|
|
|
|
|
|
| 249 |
|
| 250 |
|
| 251 |
class TestTOINFieldLearningIntegration:
|
|
@@ -275,8 +277,7 @@ class TestTOINFieldLearningIntegration:
|
|
| 275 |
pattern = toin._patterns.get(sig.structure_hash)
|
| 276 |
assert pattern is not None, "Pattern should exist"
|
| 277 |
assert len(pattern.field_semantics) > 0, (
|
| 278 |
-
f"field_semantics should be populated after retrieval. "
|
| 279 |
-
f"Got: {pattern.field_semantics}"
|
| 280 |
)
|
| 281 |
|
| 282 |
# Check that field hashes match expected fields
|
|
@@ -327,7 +328,9 @@ class TestTOINFieldLearningIntegration:
|
|
| 327 |
|
| 328 |
status_sem = pattern.field_semantics[status_hash]
|
| 329 |
# The type should be inferred (not unknown) after enough data
|
| 330 |
-
assert status_sem.retrieval_count >= 6,
|
|
|
|
|
|
|
| 331 |
|
| 332 |
def test_get_recommendation_includes_field_semantics(self):
|
| 333 |
"""PROVES: get_recommendation returns learned field_semantics."""
|
|
@@ -513,9 +516,10 @@ class TestEndToEndFieldLearning:
|
|
| 513 |
# Error value should be tracked
|
| 514 |
error_hash = _hash_value("error")
|
| 515 |
failed_hash = _hash_value("failed")
|
| 516 |
-
assert
|
| 517 |
-
|
| 518 |
-
|
|
|
|
| 519 |
|
| 520 |
def test_recommendation_hint_includes_learned_semantics(self):
|
| 521 |
"""PROVES: TOIN recommendation includes learned field semantics for SmartCrusher."""
|
|
@@ -549,8 +553,7 @@ class TestEndToEndFieldLearning:
|
|
| 549 |
# VERIFY
|
| 550 |
assert hint is not None
|
| 551 |
assert len(hint.field_semantics) > 0, (
|
| 552 |
-
f"Recommendation should include field_semantics. "
|
| 553 |
-
f"Got: {hint.field_semantics}"
|
| 554 |
)
|
| 555 |
assert status_hash in hint.field_semantics
|
| 556 |
|
|
|
|
| 245 |
|
| 246 |
# VERIFY
|
| 247 |
assert fs.is_value_important(pending_hash), "Retrieved value should be important"
|
| 248 |
+
assert not fs.is_value_important(unknown_hash), (
|
| 249 |
+
"Never-retrieved value should NOT be important"
|
| 250 |
+
)
|
| 251 |
|
| 252 |
|
| 253 |
class TestTOINFieldLearningIntegration:
|
|
|
|
| 277 |
pattern = toin._patterns.get(sig.structure_hash)
|
| 278 |
assert pattern is not None, "Pattern should exist"
|
| 279 |
assert len(pattern.field_semantics) > 0, (
|
| 280 |
+
f"field_semantics should be populated after retrieval. Got: {pattern.field_semantics}"
|
|
|
|
| 281 |
)
|
| 282 |
|
| 283 |
# Check that field hashes match expected fields
|
|
|
|
| 328 |
|
| 329 |
status_sem = pattern.field_semantics[status_hash]
|
| 330 |
# The type should be inferred (not unknown) after enough data
|
| 331 |
+
assert status_sem.retrieval_count >= 6, (
|
| 332 |
+
f"Should have 6+ retrievals, got {status_sem.retrieval_count}"
|
| 333 |
+
)
|
| 334 |
|
| 335 |
def test_get_recommendation_includes_field_semantics(self):
|
| 336 |
"""PROVES: get_recommendation returns learned field_semantics."""
|
|
|
|
| 516 |
# Error value should be tracked
|
| 517 |
error_hash = _hash_value("error")
|
| 518 |
failed_hash = _hash_value("failed")
|
| 519 |
+
assert (
|
| 520 |
+
error_hash in status_sem.important_value_hashes
|
| 521 |
+
or failed_hash in status_sem.important_value_hashes
|
| 522 |
+
), "Error/failed values should be marked as important"
|
| 523 |
|
| 524 |
def test_recommendation_hint_includes_learned_semantics(self):
|
| 525 |
"""PROVES: TOIN recommendation includes learned field semantics for SmartCrusher."""
|
|
|
|
| 553 |
# VERIFY
|
| 554 |
assert hint is not None
|
| 555 |
assert len(hint.field_semantics) > 0, (
|
| 556 |
+
f"Recommendation should include field_semantics. Got: {hint.field_semantics}"
|
|
|
|
| 557 |
)
|
| 558 |
assert status_hash in hint.field_semantics
|
| 559 |
|