数据截至 (上游 commit b77d61291399)
CCR — 把有损压缩做成可逆:原文不删,模型可以要回来
30 秒导读: 上一章的压缩器会真的扔东西(丢行、丢字段、丢整段 base64)。CCR 是给这些"扔"配的后悔药:扔之前把原文存进本地仓库,在提示词里留一张写着 hash 的取货单,再给模型注册一个
headroom_retrieve工具。模型发现信息不够,就凭取货单换原文;取回动作由代理自己执行,agent 端一行代码都不用改。
本章是 Headroom 系列的第三章。压缩主干怎么把内容路由到压缩器,见 01-pipeline-and-router;每种压缩器各自"不能破坏什么",见 02-compressors;为什么不能直接删历史消息,见 04-cache-safety;代理层怎么零改代码接进 agent,见 05-proxy-and-wrap。
1. 这章讲什么(零基础也能懂)
有损压缩的死穴
压缩器把一个 500 行的搜索结果砍成 20 行,省了 95% 的 token。
问题出在第 7 轮:模型突然要看第 213 行。那行已经不在上下文里了,模型只能重跑一遍工具——省下的 token 连本带利吐回去,还多花一次工具调用。
Headroom 的判断写在源码注释第一句里:可逆压缩胜过不可逆压缩(headroom/cache/compression_store.py:6-7)。
CCR 是哪三步
CCR = Compress-Cache-Retrieve,一句话三步:
| 步 | 谁干的 | 干什么 |
|---|---|---|
| Compress | 压缩器 | 砍内容,同时在砍掉的位置留一个带 hash 的 marker |
| Cache | CompressionStore | 把原文按同一个 hash 存进本地仓库(默认 SQLite 文件) |
| Retrieve | 代理 + 模型 | 模型调 headroom_retrieve(hash=...),代理查仓库、把原文塞回去、续跑对话 |
用起来什么样
模型在上下文里看到的,是这么一行东西:
[{"path":"src/a.py"}, {"path":"src/b.py"}, {"_ccr_dropped":"<<ccr:a1b2c3d4e5f6 480_rows_offloaded>>"}]
它想要那 480 行,就发一次工具调用:
{"name": "headroom_retrieve", "input": {"hash": "a1b2c3d4e5f6"}}
这次调用不会到达模型厂商之外的任何地方——代理在响应路径上把它拦下来,自己查库、自己造 tool_result、自己再请求一次模型,最后只把最终答案交给客户端。Agent(Claude Code / Cursor / 你自己的脚本)全程不知道发生过这一轮。
一句话直觉
把上下文窗口当手上拿的几页纸,把 CompressionStore 当桌上的文件柜。压缩不是把纸撕了,是把大部分放回柜子、手上留一张取件条;模型念出取件条上的编号,就有人去柜子里取来递给它。
2. 顶层全景(它大概怎么转)
CCR 横跨请求和响应两条路,先看整体。怎么读这张图: 上半是请求路(压缩+注册工具),下半是响应路(拦截+取回+续跑),中间那个方框是唯一的共享状态。
┌──────────────── 请求路 ────────────────┐
client │ │
请求 ──┼─▶ ① 压缩器砍内容 │
│ └─ 留 marker <<ccr:HASH ...>> │
│ └─ 原文写入 ──────────┐ │
│ │ │
│ ② 扫描 marker → 验货 │ │
│ └─ 注册 headroom_retrieve 工具 │
└────────────────────────────┼───────────┘
▼
╔═════════════════════════╗
║ CompressionStore ║
║ hash → 原文 + 元数据 ║
║ TTL 30 分钟 / 堆驱逐 ║
╚═════════════════════════╝
▲
┌────────────── 响应路 ───────┼───────────┐
│ ③ 模型回了 headroom_retrieve │
│ └─ 代理拦下 ──────────┘ │
│ ④ 造 tool_result,再请求一次模型 │
最终 ◀─┼─── ⑤ 只把最后那次响应交给 client │
响应 └────────────────────────────────────────┘
部件与落点:
| 部件 | 干什么 | 文件 |
|---|---|---|
| marker 文本 | 模型看得见的"取货单",三种形态 | headroom/parser.py:30、crates/headroom-core/src/transforms/smart_crusher/crusher.rs:940 |
CompressionStore | 原文仓库:存 / 取 / TTL / 驱逐 / 反馈 | headroom/cache/compression_store.py:210 |
| 存储后端 | 内存 / SQLite / 第三方,协议解耦 | headroom/cache/backends/ |
CCRToolInjector | 扫 marker、验货、把工具塞进 tools | headroom/ccr/tool_injection.py:160 |
CCRResponseHandler | 拦工具调用、执行取回、续跑对话 | headroom/ccr/response_handler.py:81 |
StreamingCCRHandler | 流式响应下的同一件事 | headroom/ccr/response_handler.py:619 |
ContextTracker | 跨轮追踪 + 主动展开 | headroom/ccr/context_tracker.py:125 |
HeadroomMCPServer | 把三个工具以 MCP 暴露(与注入二选一) | headroom/ccr/mcp_server.py:354 |
| 批处理补偿 | 批 API 结果里的取回请求异步补跑 | headroom/ccr/batch_processor.py:75 |