惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
Jina AI
Jina AI
S
SegmentFault 最新的问题
博客园 - 【当耐特】
阮一峰的网络日志
阮一峰的网络日志
IT之家
IT之家
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
aimingoo的专栏
aimingoo的专栏
博客园 - 叶小钗
The Cloudflare Blog
MyScale Blog
MyScale Blog
F
Full Disclosure
T
Tailwind CSS Blog
Recent Announcements
Recent Announcements
云风的 BLOG
云风的 BLOG
B
Blog
Apple Machine Learning Research
Apple Machine Learning Research
Engineering at Meta
Engineering at Meta
P
Privacy & Cybersecurity Law Blog
H
Help Net Security
A
Arctic Wolf
T
Tor Project blog
WordPress大学
WordPress大学
Cisco Talos Blog
Cisco Talos Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Project Zero
Project Zero
V2EX - 技术
V2EX - 技术
C
CERT Recently Published Vulnerability Notes
L
Lohrmann on Cybersecurity
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Hacker News: Ask HN
Hacker News: Ask HN
有赞技术团队
有赞技术团队
Y
Y Combinator Blog
S
Securelist
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 三生石上(FineUI控件)
PCI Perspectives
PCI Perspectives
雷峰网
雷峰网
J
Java Code Geeks
G
GRAHAM CLULEY
D
DataBreaches.Net
C
CXSECURITY Database RSS Feed - CXSecurity.com
Attack and Defense Labs
Attack and Defense Labs
美团技术团队
Security Archives - TechRepublic
Security Archives - TechRepublic
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
D
Docker
Cloudbric
Cloudbric
L
LangChain Blog

博客园 - 深蓝

如何用 120 行提示词让 AI 写少一半的代码——ponytail AI Coding Agent Token 成本优化指南(下):工具层、代码图谱与多 Agent 协作 AI Coding Agent Token成本优化指南(上):成本结构、使用习惯与模型路由 gongfeng-cli——专为 AI Agent 打造的腾讯工蜂命令行工具 tapd-ai-cli——专为 AI Agent 打造的 TAPD 命令行工具 【译】Harness——用于长时运行应用的智能体框架设计 规范驱动开发(Spec-Driven Development)深入解析——从方法论演进到 AI 时代的软件工程范式转移 用自然语言写控制台命令——AICLI发布 告别审美黑洞!手把手教你用 NotebookLM 给 PPT “一键美颜” 拥抱AI编程,用中文进行规范驱动开发:OpenSpec汉化版正式发布 Web3 与 dApp 基础设施指南【翻译】 Sonic区块链技术调研 区块链分片技术详解:架构、挑战与实践 以太坊Pectra升级技术详解 比特币与以太坊的区块结构设计差异:从默克尔树到全局状态树的进化 符文Runes协议技术详解 基于Ordinals在比特币L1网络实现EVM图灵完备智能合约支持——BxE协议 基于DID实现第三方应用的分布式身份登录 shardingdb:支持分片和并发读写的 GoLevelDB 以太坊数据存证性能与膨胀率测试 Stable Diffusion XL1.0正式发布了,赶紧来尝鲜吧 直接用中文写提示词的Stable Diffusion扩展:sd-prompt-translator发布 ChatGPT大量封号,推荐几款可平替的AI工具 strchecker——Go源码字符串规范检查lint工具
Headroom——又一款AI 编程助手的 Token 省钱利器
深蓝 · 2026-06-17 · via 博客园 - 深蓝

一、你的 Token 都花到哪去了

1.1 包装纸比糖还多

一次典型的 AI 编程对话中,可能触发十几次工具调用——读取文件、搜索代码、执行命令、检查进程状态。每次工具返回的结果,都会被包裹在一个 JSON 信封里:

// 典型的工具返回结构——注意真正有用的只有 text 字段里的几行
{
  "tool_use_id": "toolu_01ABC123",
  "type": "tool_result",
  "content": [
    {
      "type": "text",
      "text": "src/main.py:42:    def process_data(data):\nsrc/main.py:87:    def validate_input(input):\nsrc/utils.py:15:    def format_output(result):\n... (以下 200 行类似内容)"
    }
  ]
}

你看,真正有价值的信息——那几行匹配结果——只占整个 JSON 的不到 5%。剩下的 tool_use_idtypecontent 字段,每一次调用都长得几乎一模一样。可 LLM 不知道这一点,它老老实实地把每个字符都"读"了一遍,再一遍,又一遍。

1.2 浪费的不只是钱

Token 的浪费带来三重连锁反应:

钱包受伤——按 Token 计费的模式下,每次对话多消耗几千 Token,一个月累积下来,账单数字比你想象的要刺眼得多。

记忆衰退——这是最隐蔽也最致命的问题。主流模型的上下文窗口看似宽裕(128K-200K Token),但在一个复杂的编程会话里,冗余信息会像淤泥一样逐渐填满河道。当窗口被占满,模型就"忘了"你一小时前说过的关键设计约束,开始给出自相矛盾的建议。

体验打折——更多的 Token 意味着更长的处理时间。在交互式编程场景中,每多等一秒钟,流畅感就减一分。

1.3 Headroom 登场

这就是 Headroom 存在的理由。一句话说清楚:

Headroom 是一个 HTTP 代理,拦截 AI 编程助手的 API 请求,智能压缩后转发给上游 LLM。不修改 CodeBuddy 的任何一行代码,不改变你的任何使用习惯。唯一的区别是——你的 Token 账单缩水了 87%。


二、Proxy:Token 压缩的核心引擎

2.1 概览:请求如何穿越 Proxy

Headroom Proxy 是整套体系的神经中枢。每个来自编程助手的 API 请求,都要经过一条精心编排的管线:

flowchart LR CB[CodeBuddy CLI] --> CA[CacheAligner<br/>缓存对齐] CA --> CR[ContentRouter<br/>内容识别与路由] CR --> CCR[CCR<br/>可逆压缩检索] CCR --> LLM[上游 LLM]

这三站是 Proxy 的核心主线——CacheAligner 负责让 KV 缓存命中,ContentRouter 负责精准压缩各类内容,CCR 负责让压缩可逆。除此之外,还有三个关键配套机制:

  • PrefixCacheTracker——保护已被 Provider 缓存的前缀,避免压缩破坏缓存
  • ReadLifecycle——检测过时和重复的 Read 输出,用标记替换
  • IntelligentContext——智能管理对话历史,避免上下文溢出

这不是简单的"一刀切"压缩——每一站解决一类特定的浪费,层层递进,互不重叠。接下来我们逐一拆解。

2.2 CacheAligner:KV 缓存命中率从 0 到大幅提升

问题:Anthropic 等服务商支持 KV 缓存——如果两次请求的前缀相同,模型可以复用之前的计算结果,既省 Token 又省时间。但系统 Prompt 里往往藏着"定时炸弹":当前日期、会话 ID、随机种子这些动态值。它们可能只占几个 Token,却能让整个前缀发生偏移,导致 KV 缓存次次落空。

打个比方:这就像图书馆按书名排序时,每本书的封面上都印着"今天借阅次数"——这个数字天天变,排序结果天天乱。

Headroom 的方案:CacheAligner 自动识别这些动态字段,把它们挪到消息末尾。前缀保持完全静态,缓存命中率从近乎为零飙升至接近上限。系统 Prompt 的 Token 在后续请求中几乎不再计费,也不再霸占上下文窗口。

2.3 Prefix Cache Tracker:别把缓存给压坏了

CacheAligner 解决了"让缓存命中"的问题,但还有一个更隐蔽的陷阱:压缩本身可能破坏缓存。

Claude Code 等客户端已经内置了前缀缓存管理——它们在消息中插入 cache_control 断点,告诉 Provider"前面这段我已经缓存了,下次复用"。如果 Headroom 对这些已缓存的消息做了压缩或修改,缓存就会失效——原本享受 90% 读取折扣的内容,反而要支付 25% 的写入惩罚。

Headroom 的方案:Prefix Cache Tracker 在每个 API 响应后记录 Provider 实际缓存了多少 Token 和多少条消息。下一次请求时,这些消息会被"冻结"——整个压缩流水线跳过它们,只处理新增的、未被缓存的内容。当压缩收益足够大时(超过缓存折扣),它也会主动选择压缩。

用数字说话:Anthropic 缓存读取享受 90% 折扣,缓存写入则加收 25% 惩罚。冻结策略确保"不该动的一个不动,该压的一个不放过"。

2.4 ContentRouter:按内容类型精准分发压缩策略

KV 缓存和前缀冻结只解决了"重复内容"的问题。但真正的浪费大头在于"低价值内容"——一个 200 条记录的 JSON 数组和一段 Git Diff,它们的冗余模式完全不同,怎可能用同一把剪刀来裁?

Headroom 的 ContentRouter 先用 Magika(Google 开源的 ML 文件类型检测器)精准识别内容类型,然后分发给对应的压缩策略:

压缩器 输入类型 策略 典型压缩率
SmartCrusher JSON 数组 统计采样,保留首末项/错误/异常值 80-95%
LogCompressor 日志输出 保留 ERROR/WARN,丢弃 INFO/DEBUG 60-90%
SearchCompressor 搜索结果 按相关性截断,丢弃低分结果 50-80%
DiffCompressor Git Diff 只保留变更块,丢弃上下文行 30-60%
CodeCompressor 源代码 tree-sitter AST 解析,保留签名,压缩函数体 40-70%

纯文本内容通过 Kompress-base(基于 HuggingFace 训练模型)处理,实现通用文本压缩。

下面逐一展开,每个压缩器都配上压缩前后的直观对比。

SmartCrusher:JSON 数组压缩

面对 200 条记录的 JSON 数组,SmartCrusher 不会简单粗暴地截断——它保留第一条、最后一条、所有包含 errorfail 关键词的条目,再附上统计摘要。LLM 既能看到全貌,又不会漏掉异常。

// === 压缩前:200 条 JSON 记录(约 5000 Token)===
[{"id":1,"status":"ok","data":"..."},
 {"id":2,"status":"ok","data":"..."},
 ...(196 条类似记录)...
 {"id":199,"status":"error","data":"connection timeout"},
 {"id":200,"status":"ok","data":"..."}]
// === 压缩后:3 条采样 + 统计摘要(约 250 Token,节省 95%)===
[共 200 条记录,已采样]
第1条: {"id":1,"status":"ok","data":"..."}
第199条: {"id":199,"status":"error","data":"connection timeout"}  ← 异常值
第200条: {"id":200,"status":"ok","data":"..."}
统计: status=ok 199条, status=error 1条

从 200 条压缩到 3 条 + 统计摘要,Token 节省超过 95%,LLM 依然知道"有一条连接超时的错误"。

LogCompressor:日志输出压缩

日志输出通常 95% 是例行 INFO/DEBUG,只有少量 ERROR/WARN 才有关注价值。LogCompressor 根据日志级别过滤,只保留关键行。

// === 压缩前:500 行构建日志(约 3000 Token)===
INFO: Starting build process...
INFO: Checking dependencies...
INFO: Dependency check complete.
INFO: Compiling src/main.rs...
INFO: Compiling src/utils.rs...
...(490 行 INFO 日志)...
ERROR: Compilation failed in src/auth.rs:42 - undefined variable 'token'
WARN: Deprecated function 'old_hash' used in src/crypto.rs:15
INFO: Build finished with 1 error, 1 warning.
// === 压缩后:保留关键行(约 150 Token,节省 95%)===
[日志已压缩:共 500 行,保留 3 行关键信息]
ERROR: Compilation failed in src/auth.rs:42 - undefined variable 'token'
WARN: Deprecated function 'old_hash' used in src/crypto.rs:15
统计: ERROR 1条, WARN 1条, INFO 498条

SearchCompressor:搜索结果压缩

grep 返回 100 条匹配时,前面的高相关性结果最有价值,末尾的低分结果基本无用。SearchCompressor 按相关性截断。

// === 压缩前:grep 返回 100 条匹配(约 4000 Token)===
src/main.py:42:    def process_request(data):
src/handler.py:15:    def handle_request(req):
src/utils.py:88:    def validate_request(input):
...(中间 94 条低相关性匹配)...
tests/old_test.py:200:    # TODO: process_request deprecated
docs/legacy.md:35:    - process_request (removed in v2)
// === 压缩后:保留前 3 条高分结果(约 120 Token,节省 97%)===
[搜索结果已压缩:100 条匹配,保留前 3 条高相关性结果]
src/main.py:42:    def process_request(data):
src/handler.py:15:    def handle_request(req):
src/utils.py:88:    def validate_request(input)
统计: 高相关性 3条, 低相关性 97条(已丢弃)

DiffCompressor:Git Diff 压缩

一段 200 行的 Git Diff 中,大部分是未修改的上下文行(以空格开头),真正有信息量的是 +- 开头的变更行。DiffCompressor 剥离上下文,只保留变更块。

// === 压缩前:200 行 Git Diff(约 2500 Token)===
diff --git a/src/auth.py b/src/auth.py
@@ -35,12 +35,18 @@
 import hashlib
 import time
 from datetime import datetime
-
 def verify_token(token):
     if not token:
         return False
+    # 新增:token 过期检查
+    if token.expired():
+        raise TokenExpiredError("Token has expired")
     try:
-        payload = jwt.decode(token, SECRET)
+        payload = jwt.decode(token, SECRET, algorithms=["HS256"])
     except jwt.InvalidTokenError:
         return False
-    return payload.get("user_id")
+    user_id = payload.get("sub")
+    if not user_id:
+        return False
+    return user_id
 ...(后面还有 150 行未修改的上下文)...
// === 压缩后:仅保留变更行(约 200 Token,节省 92%)===
[Diff 已压缩:200 行 → 8 行变更]
--- a/src/auth.py
+++ b/src/auth.py
+    # 新增:token 过期检查
+    if token.expired():
+        raise TokenExpiredError("Token has expired")
-        payload = jwt.decode(token, SECRET)
+        payload = jwt.decode(token, SECRET, algorithms=["HS256"])
-    return payload.get("user_id")
+    user_id = payload.get("sub")
+    if not user_id:
+        return False
+    return user_id

CodeCompressor:源代码压缩

CodeCompressor 用 tree-sitter 解析代码的 AST(抽象语法树),保留函数签名、类定义、类型注解——这些是 LLM 理解代码结构的关键信息——然后安全地压缩函数体实现。

// === 压缩前:500 行 Python 文件(约 6000 Token)===
class AuthService:
    """Authentication service with JWT support."""

    def __init__(self, secret: str, algorithm: str = "HS256"):
        self.secret = secret
        self.algorithm = algorithm

    def generate_token(self, user_id: str, expires_in: int = 3600) -> str:
        """Generate a JWT token for the given user."""
        now = datetime.utcnow()
        payload = {
            "sub": user_id,
            "iat": now,
            "exp": now + timedelta(seconds=expires_in),
        }
        return jwt.encode(payload, self.secret, algorithm=self.algorithm)

    def verify_token(self, token: str) -> dict | None:
        """Verify and decode a JWT token."""
        try:
            payload = jwt.decode(token, self.secret, algorithms=[self.algorithm])
            return payload
        except jwt.ExpiredSignatureError:
            logger.warning("Token has expired")
            return None
        except jwt.InvalidTokenError as e:
            logger.error(f"Invalid token: {e}")
            return None

    ...(后面还有 400 行实现代码)...
// === 压缩后:保留签名 + 骨架(约 800 Token,节省 87%)===
[代码已压缩:500 行 → 保留结构签名]

class AuthService:
    """Authentication service with JWT support."""

    def __init__(self, secret: str, algorithm: str = "HS256") -> None: ...

    def generate_token(self, user_id: str, expires_in: int = 3600) -> str:
        """Generate a JWT token for the given user."""
        ...(函数体已压缩,可通过 headroom_retrieve 展开)...

    def verify_token(self, token: str) -> dict | None:
        """Verify and decode a JWT token."""
        ...(函数体已压缩,可通过 headroom_retrieve 展开)...

    ...(后续方法同上)...

注意:CodeCompressor 默认关闭,需显式开启 --code-aware。详见 7.3 节。

2.5 Read Lifecycle:75% 的 Read 输出可以安全丢弃

这是我个人认为 Headroom 最容易被忽视但效果最显著的优化。

在一个典型的编程会话中,AI 会反复读取文件。实测数据显示,这些 Read 输出中:

状态 占比 说明
Stale(过时) 67% 文件在读取之后被编辑过,上下文中的内容已经是错的
Superseded(被覆盖) 12% 同一个文件后来又读了一次,旧内容变成冗余
Fresh(新鲜) 20% 仍然有效,需要保留

也就是说,80% 的 Read 输出对 LLM 没有实际价值,但它们每条都占着几千字符的上下文空间。

Headroom 的 Read Lifecycle Manager 在整个会话中追踪每个文件的读取和编辑事件。一旦检测到某个 Read 已经过时或被覆盖,就用一条紧凑标记替换它的全部内容,同时把原始内容存入 CCR 缓存:

// 过时文件内容的标记替换——从几千字符压缩到一行
[Read content stale: src/auth.py was modified after this read. Retrieve original: hash=a1b2c3...]

如果 LLM 确实需要那份原始内容——比如它想对比修改前后的差异——它可以通过 CCR 机制按需检索。但绝大多数情况下,过时的内容对 LLM 毫无价值,只会白白占用上下文。

这项优化单独就能省下 15-25% 的额外 Token,叠加在压缩器之上,效果显著。

2.6 IntelligentContext:聪明地遗忘

对话越来越长,历史消息不断堆积,终将溢出上下文窗口。传统的 Rolling Window 方案简单粗暴——扔掉最早的。但最早的消息往往是最重要的设计约束。

举个例子:你在第一轮说"必须兼容 Python 3.9",然后聊了 50 轮实现细节。到第 51 轮,Rolling Window 把第一轮丢了——模型开始给你 Python 3.12 才有的语法。这种踩坑我亲身经历过,排查起来特别费劲。

Headroom 的 IntelligentContext 用六因素评分模型来决策哪些保留、哪些丢弃:

因素 逻辑 为什么重要
时效性 越近的消息权重越高 最新上下文通常最相关
语义相似度 与当前请求越相关,越可能被保留 避免丢弃正在讨论的话题
学习到的模式 从历史检索数据中学习哪些消息 LLM 频繁回溯 数据驱动,而非拍脑袋
错误标记 包含错误和修复记录的消息,权重加码 错误上下文是调试的关键锚点
前向引用 被后续消息引用的内容,视为锚点 引用链断裂会导致逻辑混乱
Token 密度 信息密度高的优先保留 避免留着大段空话

这个评分模型不是静态的——它会根据 LLM 实际的检索行为持续学习,越用越聪明。

2.7 安全底线:四条铁律

省钱的前提是不出错。Headroom 设定了四条不可逾越的红线:

  • 人类写的内容一字不改——你的代码、你的注释、你的提问,原封不动
  • 工具调用顺序绝不打乱——LLM 靠时序理解因果,顺序一乱,逻辑就断
  • 错误信息 100% 保留——所有含 error/fail/exception 的内容,完整穿越
  • 压缩失败即直通——任何环节出错,直接转发原始内容,不影响功能

【注意】 这四条铁律是硬编码的,不可配置。安全优先于省钱,没有例外。


三、CCR:压缩不是删除,是折叠

3.1 再聪明的压缩器也有猜错的时候

前面这些机制已经能大幅削减 Token。但这里有一个根本问题:压缩的本质是丢弃信息。 压缩器再聪明,也可能判断失误——把某条看似普通、实则关键的记录丢掉。

传统压缩的问题在于不可逆。一旦 SmartCrusher 丢掉了 JSON 数组的第 150 条记录,而 LLM 恰好需要它来回答你的问题——那条数据就永远消失了。LLM 甚至不知道自己错过了什么。

这就像你把一份 100 页的报告交给助理做摘要,他给了你 10 页精华版。大部分时候够用,但某一天你需要查第 73 页的一个数据——发现那份报告已经进了碎纸机。

3.2 压缩-缓存-检索:信息零丢失

Headroom 的 CCR(Compress-Cache-Retrieve)机制优雅地解决了这个困境。

核心思想:压缩只是折叠,不是删除。LLM 随时可以展开。

工作流程分五步:

  1. 压缩前存档:原始内容完整存入本地 SQLite 缓存,生成哈希键作为索引
  2. 注入检索工具:Headroom 悄悄在发给 LLM 的消息中塞入 headroom_retrieve 工具定义
  3. 按需展开:LLM 发现信息不够用时,可以主动调用 headroom_retrieve,Proxy 从缓存中取出原始内容返回
  4. 连锁展开:LLM 检索了某个压缩块后,Headroom 会智能扩展与之相关的其他压缩内容
  5. 持续学习:Headroom 会学习 LLM 的检索模式,让后续的压缩决策更精准

下面用一个完整的例子说明 CCR 的工作过程:

# 步骤1:原始工具输出(5000 Token)
[100 条用户记录的完整 JSON 数组]

# 步骤2:压缩后发送给 LLM(500 Token)
[共 100 条记录,已采样] [hash=abc123]
第1条: {"id":1,"name":"Alice",...}
第100条: {"id":100,"name":"Zara",...}
统计: ...

# 步骤3:LLM 发现需要第 42 条记录,主动调用检索工具
LLM 调用: headroom_retrieve(hash="abc123", query="id=42")

# 步骤4:Proxy 从缓存中取出原始内容返回
Proxy 返回: {"id":42,"name":"Frank","email":"frank@example.com",...}

# 步骤5:LLM 拿到完整数据,继续回答

LLM 永远不会陷入"不知道自己在不知道什么"的窘境——压缩内容不够时,它有主动权去展开。

一个精妙的设计细节是 Sticky-on 机制:一旦会话中出现过 CCR 压缩,headroom_retrieve 工具就会持续存在于后续所有请求中,而不是只在有压缩的请求里闪现。这避免了工具列表的抖动——如果工具时有时无,LLM 的 Prompt Cache 就会失效,反而造成更多浪费。

3.3 一句话总结

压缩只是"折叠",不是"删除"。LLM 随时可以展开。

这种设计让 Headroom 敢于大胆压缩——因为即使压缩过度,LLM 能自行恢复。保守策略每次都在浪费 Token,而 CCR 只在真正需要时才消耗额外的 Token。


四、Memory:让 LLM 拥有跨会话记忆

4.1 问题:每次对话都从零开始

前面的优化都聚焦在单次会话内的 Token 节省。但有一个更根本的浪费发生在会话之间:LLM 每次都像一个失忆的医生,需要重新问诊、重新检查、重新翻病历——哪怕你昨天刚跟它聊过同样的项目。

比如你昨天花了两小时让 AI 理解了项目的认证流程,今天你打开一个新会话问它"给认证加个二因素验证"。它不会记得昨天学到的任何东西——又要重新 grep、重新 Read、重新推理。这些重复的"学习成本"换算成 Token,是一个惊人的数字。

4.2 Memory 系统:从流量中自动积累知识

Headroom 内置了一套 Memory 系统,让 Proxy 从流经它的每一次请求中自动学习,形成跨会话的项目知识库。

它由三个组件协作完成:

组件 职责 工作方式
Memory Store 存储和检索记忆 本地 SQLite + 向量索引,零配置启动;也支持 Qdrant + Neo4j 生产部署
Traffic Learner 从流量中提取模式 规则引擎,无需 LLM 调用,零延迟后台处理
TOIN 优化压缩策略 记录 LLM 检索了哪些字段,反馈给 SmartCrusher 调整采样权重

Memory Store 就像一个不断积累的项目笔记,记录着:

  • 项目架构:核心模块、依赖关系、技术栈
  • 环境信息:哪些命令可用、哪些路径存在、构建方式
  • 错误修复模式:遇到某类错误时,正确的解决方案是什么
  • 用户偏好:代码风格、命名习惯、架构倾向

当 LLM 在新会话中发起请求时,Memory 系统自动检索相关记忆,注入到上下文末尾。LLM 不需要重新 grep 整个项目就能知道"上次我们讨论过的认证模块在 src/auth/ 下,使用 JWT 方案"。

4.3 Traffic Learner:无需 LLM 的规则学习

Traffic Learner 是我个人认为 Memory 系统中最巧妙的部分。它不调用任何 LLM,纯粹从流经 Proxy 的请求/响应中提取模式:

学习模式 例子 效果
错误→恢复 LLM 执行 npm install 失败(缺 package.json),然后 ls 发现自己在错误目录,再 cd 到正确目录执行成功 下次直接提示正确的目录
环境事实 某条命令成功执行后 记录"该命令在此项目中可用"
偏好信号 LLM 反复使用某种模式被用户接受 将其标记为偏好

Learner 在后台异步运行,不增加任何请求延迟。积累到一定证据量后才写入 Memory,避免单次噪声污染知识库。

【注意】 Memory 默认关闭,需显式开启(--memory),避免未经授权的数据积累。


五、外部工具集成

Proxy 解决了 API 传输层面的 Token 优化,Memory 解决了跨会话的知识积累。但 Token 浪费还有一个源头:工具本身的输出就冗余。如果能让工具从源头少产废料,Proxy 的压缩负担就更轻了。

5.1 RTK:Shell 命令的瘦身教练

RTK(Realtime Token Kompress)的思路很直接:不压缩已有输出,而是改写命令本身,让命令在源头就产出更精简的结果。

比如 AI 决定执行 git log,默认吐出来几百行完整日志。RTK 把它重写为 git log --oneline -n 50,从源头限制输出量。说白了,这不是事后减肥,而是让命令自己少吃多运动

核心工作方式:

  • 命令重写:拦截 AI 即将执行的 shell 命令,改写参数以削减冗余输出
  • Hook 集成:通过 CodeBuddy 的 PreToolUse 事件钩子注入,命令执行前自动触发
  • 量化反馈rtk gain 实时汇报节省量,数据汇入 Dashboard
# 查看 Token 节省统计
rtk gain

# 结构化输出,供 Dashboard 采集
rtk gain --format json

# 历史节省记录
rtk gain --history

5.2 lean-ctx:通用上下文过滤器

如果说 RTK 专门优化 shell 命令,lean-ctx 则面向所有工具的输出。它在工具结果进入 LLM 上下文窗口之前,对内容进行路由和过滤,把低价值的冗余信息挡在门外。Headroom 自动从 GitHub Releases 下载安装二进制,支持 macOS、Linux、Windows 全平台。

注意:lean-ctx 默认不启用,需通过 HEADROOM_CONTEXT_TOOL=lean-ctx 环境变量切换。默认上下文工具为 RTK。

5.3 CodeGraph:用图谱代替 grep

当 LLM 试图理解项目结构——"谁调用了 process_request?""这个类的继承链是什么?"——它的武器库里只有 grep 和 search。每次搜索输出冗长的文件列表裹在 JSON 信封里,一趟几千 Token。更糟的是,往往要搜索五六次才能拼出完整图景。

CodeGraph 维护一个实时代码结构索引,让 LLM 直接查图谱。底层是 codebase-memory-mcp,通过 tree-sitter 解析 AST,用 watchdog 监控文件变更实现增量索引。

对比项 grep 搜索 CodeGraph 图谱查询
典型 Token 消耗 ~3,500 ~200
节省率 - 94%
搜索次数(理解调用链) 5-6 次 1 次

启用方式:

# 启用 CodeGraph(显式 opt-in,需先启动 Proxy)
headroom proxy --code-graph --port 8787 &
CODEBUDDY_BASE_URL=http://127.0.0.1:8787/v2 codebuddy

5.4 Serena:语义级代码编辑

CodeGraph 解决了"理解"的开销,但 LLM 还需要编辑、重构、诊断。一个简单的重命名 process_requesthandle_request,传统方式需要 grep 找到所有引用 → 逐个 Read 每个文件 → 逐个 Write 修改后的完整内容,消耗数千 Token。而 Serenarename_symbol 一个调用就完成,几百 Token。

Serena 是一个语义级代码智能 MCP 服务器,底层基于 LSP 或 JetBrains 插件,支持 40+ 种语言,提供符号查询、代码编辑、重构、诊断等 30+ 工具。Headroom 将其作为默认启用的工具,每次 headroom wrap 自动注册(可通过 --no-serena 关闭)。

5.5 CodeGraph 与 Serena:互补,而非重复

一个自然的问题:既然有了 CodeGraph,为什么还需要 Serena?答案是它们攻击的是 Token 消耗的不同环节:

维度 CodeGraph Serena
定位 只读图谱查询 读写 IDE 能力
底层 tree-sitter AST → SQLite LSP / JetBrains 插件
查询 trace_path, search_graph, get_architecture find_symbol, find_referencing_symbols
编辑 replace_symbol_body, rename_symbol
重构 safe_delete_symbol, rename_symbol
诊断 get_diagnostics_for_file
启用 --code-graph(显式 opt-in) 默认启用(--no-serena opt-out)

CodeGraph 省的是"理解"的 Token,Serena 省的是"操作"的 Token。两者结合,覆盖 LLM 与代码交互的完整链路。

5.6 全链路视图

把所有组件放到一起,就是下面这张架构图:

flowchart LR User[用户输入] --> CLI[CodeBuddy CLI] CLI --> T1[RTK<br/>源头优化] CLI --> T2[lean-ctx<br/>上下文过滤] CLI --> T3[CodeGraph<br/>图谱查询] CLI --> T4[Serena<br/>语义编辑] T1 & T2 & T3 & T4 --> Proxy[Headroom Proxy<br/>localhost:8787] Proxy --> P1[CacheAligner<br/>缓存对齐] Proxy --> P2[PrefixCache<br/>前缀冻结] Proxy --> P3[ReadLifecycle<br/>过期检测] Proxy --> P4[ContentRouter<br/>内容压缩] Proxy --> P5[IntelligentContext<br/>上下文管理] Proxy --> P6[CCR<br/>可逆检索] P1 & P2 & P3 & P4 & P5 & P6 --> Upstream[腾讯云 Copilot] Upstream --> GLM[GLM 、DeepSeek 模型] Proxy --> Mem[Memory 系统<br/>跨会话知识积累]

六层 Proxy 内部防线 + 四个外部工具 + 跨会话 Memory 体系,从源头到传输,层层拦截 Token 浪费。


六、CodeBuddy 实战:三步接入

6.1 数据怎么走

理解架构有助于排查问题,但简单来说就是一句话:所有原本直连腾讯云 Copilot 的请求,现在先经过 Headroom Proxy 的智能压缩,再转发到上游。

flowchart LR subgraph CB[CodeBuddy CLI] direction LR RTK[RTK 钩子<br/>命令重写] LCTX[lean-ctx<br/>上下文过滤] CB_Core[核心引擎] end CB -->|API 请求<br/>CODEBUDDY_BASE_URL| Proxy subgraph Proxy["Headroom Proxy(localhost:8787)"] direction TB subgraph Request[请求处理] direction LR CA[CacheAligner<br/>对齐动态字段] --> PCT[PrefixCache<br/>冻结已缓存前缀] end subgraph Response[响应处理] direction LR RL[ReadLifecycle<br/>标记过时读取] --> CR[ContentRouter<br/>按类型分发压缩] CR --> CCR[CCR<br/>存档 + 检索工具注入] CCR --> IC[IntelligentContext<br/>智能裁剪历史] end Request -.-> Response end Proxy -->|压缩后请求| Upstream[腾讯云 Copilot] Upstream -->|API 响应| Model[GLM / DeepSeek 模型] Proxy -.->|跨会话同步| Mem[(Memory 系统<br/>SQLite + 向量索引)] Proxy -.->|工具注册| MCP[MCP 工具<br/>headroom_retrieve 等]

关键在于 CODEBUDDY_BASE_URL 这个环境变量——CodeBuddy 通过它指定 API 端点,Headroom 正是利用这个机制实现零侵入代理。

6.2 试用:从源码安装并体验

前置条件:截至本文发布,Headroom 对 CodeBuddy 的支持尚未合入官方仓库。你需要从 Fork 版源码安装。相关 PR 已提交,等待官方合入后即可直接 pip install

最快的方式,从源码安装并启动:

# 克隆 Fork 版仓库
git clone https://github.com/studyzy/headroom.git
cd headroom
pip install -e ".[all]"

# 启动——wrap 会同时启动 Proxy 和 CodeBuddy,退出时自动清理
headroom wrap codebuddy

也可以自定义端口或手动分步启动:

# 自定义端口
headroom wrap codebuddy --port 8080

# 手动分步启动(适合调试)
headroom proxy --port 8787 &                      # 先启动 Proxy
CODEBUDDY_BASE_URL=http://localhost:8787/v2 codebuddy  # 再启动 CodeBuddy

# 退出清理
headroom unwrap codebuddy

6.3 长期使用:安装为系统服务

长期使用推荐把 Proxy 安装为系统服务,配合 alias 灵活切换:

# 安装为系统服务(开机自启)
headroom install apply --preset persistent-service --target codebuddy
# 这个命令会设置环境变量CODEBUDDY_BASE_URL,如果想保留直连LLM的能力,可以手动删除环境变量然后设置一个 alias 来覆盖环境变量:
alias cb='CODEBUDDY_BASE_URL=http://127.0.0.1:8787/v2 codebuddy'

# 日常使用
codebuddy   # 直连,不走 Proxy
cb          # 走 Proxy,省 Token

这样设计的好处是:调试或排查问题时用 codebuddy 直连快速验证,日常开发用 cb 自动省 Token。不再需要时卸载即可:

# 卸载系统服务
headroom install remove

6.4 背后发生的事

headroom wrap codebuddy 在你看不到的地方还做了这些(默认行为,均可通过 --no-* 标志关闭):

  • MCP 工具注册:通过 CodeBuddy MCP 配置注册 headroom_retrieveheadroom_compressheadroom_stats 等 MCP 工具(优先走 codebuddy mcp add,CLI 不可用时回退写入 ~/.codebuddy/.mcp.json
  • RTK 钩子注入:自动下载 RTK 二进制,向 CodeBuddy settings 注入 PreToolUse 钩子
  • Memory 同步--memory 时):通过本地 .headroom/memory.db 实现跨会话知识积累,LLM 在新会话中自动获取项目上下文

6.5 实时 Dashboard

Token 到底省了多少?不用等到月底看账单。启动 Proxy 后访问 http://localhost:8787/dashboard

指标 说明
Token 节省率 每个请求的压缩前后对比
缓存命中率 KV 缓存复用情况,直接反映 CacheAligner + Prefix Cache Tracker 效果
压缩器分布 各压缩器的调用次数与效果
请求时间线 完整流水线耗时分解
CCR 检索次数 LLM 展开压缩内容的频率——数字低说明压缩质量高
Memory 统计 已学习的模式和积累的项目知识

Proxy 启动即自带 Dashboard,零成本接入可观测性。
Clipboard_Screenshot_1781691815


七、进阶配置

7.1 两种压缩模式

不同场景需要不同的压缩策略:

模式 策略 适用场景
token 激进压缩,最大化节省 个人开发、Token 预算有限
cache 偏重缓存对齐 多轮重构、长时间编程会话

默认模式为 token。切换方式:

# 切换为缓存优先模式——适合长时间编程会话
headroom proxy --mode cache

提示headroom wrap codebuddy 会将未知参数透传给 codebuddy 而非 Headroom Proxy。需要调整 Proxy 模式时,建议先独立启动 Proxy(headroom proxy --mode cache --port 8787 &),再设置 CODEBUDDY_BASE_URL 启动 CodeBuddy。

7.2 性能开销

压缩不是零成本的,但 Headroom 的开销小到几乎感觉不到:

处理阶段 耗时
SmartCrusher ~1-2ms
完整压缩管线中位数 16.9ms
典型 LLM 响应延迟 1-10 秒

Headroom 的压缩开销占整个请求延迟不到 0.2%。省下 87% 的 Token,换来几乎不可察觉的延迟增加——我认为这个交换非常划算。

7.3 适用边界

Headroom 并非万能,了解它的边界有助于你更好地使用:

  • 短消息不压:少于 300 Token 的消息,压缩收益低于开销,Headroom 自动跳过
  • 源代码默认保留:除非显式开启 CodeCompressor,否则你的代码原文穿越
  • 用户输入永远不动:你写的每一个字,Headroom 都不修改——压缩只作用于工具输出和系统消息
  • Memory 默认关闭:Memory 系统需显式开启(--memory),避免未经授权的数据积累

八、总结与实践建议

回顾 Headroom 的核心设计哲学:

零侵入:一个 HTTP 代理,不修改 CodeBuddy 的任何代码,不改变使用习惯。headroom wrap codebuddy 一行命令接入。

六层 Proxy 防线:CacheAligner 提升缓存命中率 → Prefix Cache Tracker 保护已缓存前缀 → Read Lifecycle 清理 75% 的过时读取 → ContentRouter 六种压缩器精准施压 → IntelligentContext 智能管理上下文 → CCR 可逆压缩兜底。

跨会话 Memory:Memory Store + Traffic Learner + TOIN 三者协作,从流量中自动积累项目知识,让 LLM 不再每次都从零开始。

外部工具加持:RTK 重写命令、lean-ctx 过滤输出、CodeGraph 图谱查询、Serena 语义编辑——四件利器,从源头削减工具输出的冗余。

实践建议

根据我的使用经验,给你几条建议:

  1. 先用 token 模式试跑——大多数人日常开发用默认模式就够了。如果发现某些场景下缓存命中率更重要,再切换到 cache 模式
  2. 长期使用装系统服务——比每次手动 wrap 方便得多,配合 alias 随时切换
  3. 关注 Dashboard 的 CCR 检索次数——如果 LLM 频繁检索压缩内容,说明压缩可能过于激进,考虑切换模式
  4. CodeGraph 按需开启——大型项目(>1000 文件)效果最明显,小项目收益有限

Headroom 展示了一种可能性:在 AI 编程助手的 Token 经济中,"省钱"和"好用"不是二选一。 通过全链路智能压缩、可逆检索和跨会话学习,两者完全可以兼得。我认为,随着上下文窗口越来越大、工具调用越来越频繁,Token 优化不再是一个锦上添花的选项,而是 AI 编程助手走向大规模普及的必经之路。

试试看:

pip install "headroom-ai[all]"
headroom wrap codebuddy

然后打开 http://localhost:8787/dashboard,亲眼看看你的 Token 被省下了多少。


参考资源: