使用rtk、headroom和caveman削减LLM Token成本——基于实际工作负载测量的节省

Reddit r/LocalLLaMA 新闻

摘要

对三个旨在降低编码代理LLM Token成本的开源工具(rtk、headroom和caveman)的详细分析,发现实际节省远低于声称值。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/06/18 17:33

# 用 rtk、headroom 和 caveman 降低 LLM 令牌成本 来源:https://codepointer.substack.com/p/cutting-llm-token-costs-with-rtk 三个开源项目承诺减少你的编程智能体使用的令牌数量。 rtk (https://github.com/rtk-ai/rtk)、headroom (https://github.com/chopratejas/headroom) 和 caveman (https://github.com/JuliusBrussee/caveman) 均获得了数万 GitHub 星标,并宣称节省 60% 到 90%。本文深入剖析每个项目的实际作用,并在真实工作负载上进行了测试。在我自己过去的 Claude Code 会话的 6.14 亿令牌语料库上重放,三者合计节省了 3.7% 的开支,远低于他们声称的数据。 一个智能体回合会将令牌流经三个位置。模型读取**输入**(系统提示、你的消息、之前的工具结果)并生成**输出**(包含其散文和工具调用)。这些工具调用作为命令执行,其**工具输出**流回下一个输入。这三个项目各自作用于这些流中的一个。 [图片链接](https://substackcdn.com/image/fetch/$s_!3hpU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e1568cb-3c33-40e2-bc4e-12787bd262e4_1140x664.png) `headroom` 是一个 API 代理。它位于智能体和提供商之间,当你将 `ANTHROPIC_BASE_URL` 指向它或运行 `headroom wrap claude` 时,它会看到每一个字节。其设计核心是一个**活动区**。 [图片链接](https://substackcdn.com/image/fetch/$s_!cYiX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56e9b5d1-d011-443a-9647-3c9b9b33ef3e_1086x638.png) 每个请求都被分成两部分。直到最后一个缓存标记之前的所有内容都已存储在提供商的 KV 缓存中,而最新的用户消息及其工具结果尚未缓存。这个未缓存的尾部就是活动区,也是 `headroom` 可以自由重写的唯一部分。它压缩这些块,同时保持旧消息不变,这样缓存的先前部分仍然命中,而不会破坏缓存导致成本更高。 在活动区内,一个路由器根据内容类型将每个内容块(一个工具结果或消息部分)发送给专门的压缩器。路由器识别七种类型,并将每种映射到一个策略。 content_router.py:1153-1161 (https://github.com/chopratejas/headroom/blob/v0.26.0/headroom/transforms/content_router.py#L1153-L1161) 源代码进入一个 AST 感知的 CodeAware 压缩器,JSON 进入 SmartCrusher,纯文本进入 Kompress,其他格式如构建日志、git diff 和 HTML 各有一个自己的压缩器。`headroom` 还附带一个更快的 Rust 代理,它原生压缩四种格式(JSON、构建输出、搜索结果和 git diff),其余保持未压缩。 让我们看看搜索结果的压缩。grep 或 ripgrep 的输出以数百行 `file:line:content` 的形式出现,许多行在略有不同的位置重复相同的匹配。这里的压缩意味着删除行,因此压缩器只需决定保留哪些行。压缩器解析每一行,对其评分,然后只保留得分最高的子集。当一行与活动查询或调用者配置的关键字匹配时,会获得更多分数;当它看起来像错误时,也会获得更多分数,因此智能体最可能需要的行得分最高并被保留。 search_compressor.py:234-248 (https://github.com/chopratejas/headroom/blob/v0.26.0/headroom/transforms/search_compressor.py#L234-L248) 错误提升会按顺序检查三种模式类型:首先是错误,然后是警告,最后是一般重要性,并在第一个匹配处停止,因此一行只被提升一次。这就是错误行如何能超过普通查询匹配行的分数。 评分对行进行排名,但保留多少行是一个单独的决定。压缩器不会保留固定的 top-N。它会统计输出中真正不同的匹配数量,并保留那么多。 adaptive_sizer.py:52-64 (https://github.com/chopratejas/headroom/blob/v0.26.0/headroom/transforms/adaptive_sizer.py#L52-L64) 首先,它使用 SimHash 对近乎重复的行进行分组,因此一千个几乎相同的行只算作几个不同的行。如果这几个行已经捕获了所有不同的匹配,它就停止。否则,它一次添加一个匹配,直到新匹配不再带来新词,并保留那么多。 压缩器始终保留每个文件中的第一个和最后一个匹配,这样智能体就能看到搜索覆盖的完整范围,而不仅仅是中间的一个高分组。在压缩过程中被删除的匹配会被替换为一个 `[…… 文件中还有 N 个匹配]` 的标记,告诉智能体有多少被删除了。 `headroom` 还可以恢复它删除的内容。它的 CCR(压缩-缓存-检索)功能将原始的未压缩内容缓存在内容哈希下,并注入一个 `headroom_retrieve` 工具,因此当压缩器删除了模型所需的内容时,模型可以按需拉取完整数据。 ccr/tool_injection.py:22 (https://github.com/chopratejas/headroom/blob/v0.26.0/headroom/ccr/tool_injection.py#L22) 存储在缓存后会保留原始内容五分钟。无论哪种情况,压缩后的视图及其标记都保持在对话中,因此如果模型在该窗口之后请求一个 blob,检索调用返回空,它就会回退到压缩视图或重新运行搜索。 `headroom` 也可以为你管理 `rtk`。`headroom wrap claude` 会下载 `rtk` 二进制文件并将其注册为 Claude Code 钩子,这样 `rtk` 在 CLI 侧缩小 shell 输出,而代理在网络上压缩请求。两者作用于不同的令牌流,因此一个命令可在两方面都节省成本。 `rtk` 在 shell 命令执行前拦截它们。一个 `PreToolUse` 钩子将 `git status` 重写为 `rtk git status`。模型相信自己运行的是原始命令。在重写路径上,`rtk` 通过 `std::process::Command` 使用独立参数运行真正的二进制文件(而不是通过 shell),然后过滤输出。过滤只保留相关字段,对相关行进行分组,删除重复行,并截断长输出,同时提供如何恢复其余部分的提示。 `ls` 过滤是一个清晰的例子。它将长的目录列表压缩成一个紧凑的树状结构并带有计数,并在解析失败时返回原始输出。 这是它在 `rtk` 仓库本身上的样子。原始的 `ls -la` 为每个条目提供一行,包含模式、所有者、组、字节大小和日期。 `rtk ls` 删除了智能体很少需要的列,先列出带有尾部斜杠的目录,以人类可读的单位显示文件大小,并以一行摘要结尾。它还过滤了噪音目录,如 `.git` 和 `target`,除非传入了 `-a` 参数。 ls.rs:90-99 (https://github.com/rtk-ai/rtk/blob/v0.42.4/src/cmds/system/ls.rs#L90-L99) 那个回退块是 `rtk` 的整个安全设计。当过滤器无法解析输出时,它让原始命令原样通过。截断的 diff 包含一个 `[full diff: rtk git diff --no-compact]` 提示,以便智能体可以恢复被删除的行。`rtk` 只对它知道如何过滤的命令删除数据。其他所有内容都原始运行。它有一个限制。Claude Code 内置的 Read、Grep 和 Glob 调用永远不会经过 Bash 钩子,因此 `rtk` 只节省智能体在 shell 中运行的内容。 `caveman` 不会在运行时转换任何负载。它通过两个钩子使用提示注入重写智能体的行为。一个 `SessionStart` 钩子读取其 `SKILL.md` 规则集,将其过滤到当前强度级别,并在第一个回合之前将其作为隐藏的系统上下文粘贴进去。 caveman-activate.js:50-58 (https://github.com/JuliusBrussee/caveman/blob/v1.9.0/src/hooks/caveman-activate.js#L50-L58) 第二个钩子在 `UserPromptSubmit` 事件上运行(每次你发送消息时触发),并重新注入相同的规则。这是必需的,因为第一次注入并不总是持久。随着对话增长,Claude Code 会压缩旧上下文以节省空间,其他插件也会添加自己的文本。任何一个都可能将原始规则推出窗口,一旦它们消失,模型就会回到其冗长的默认行为。 caveman-mode-tracker.js:124-131 (https://github.com/JuliusBrussee/caveman/blob/v1.9.0/src/hooks/caveman-mode-tracker.js#L124-L131) 这个提醒很小,是一行注释而不是完整的规则集(完整的规则集仍然来自会话开始的注入)。 规则集删除冠词、填充词、客套话和委婉语,允许碎片化表达,偏好短同义词(用"big"而非"extensive")。它保持技术术语、代码块、文件路径、API 名称和错误字符串原样。 SKILL.md:27-30 (https://github.com/JuliusBrussee/caveman/blob/v1.9.0/skills/caveman/SKILL.md#L27-L30) 一个强度级别控制着这个效果的强弱。`full` 是默认值。`lite` 保持完整句子,只删除填充词。`ultra` 缩写散文词(如 config 和 req),并使用箭头表示因果关系,但从不触及真正的代码符号。还有 `wenyan` 级别,可以压缩成文言文以节省更多。 SKILL.md:43-46 (https://github.com/JuliusBrussee/caveman/blob/v1.9.0/skills/caveman/SKILL.md#L43-L46) `caveman` 在安全警告、不可逆操作确认和有序多步骤序列中会关闭简洁风格,使用完整句子。这条规则优先于其上所有压缩规则,因此当省略一个词可能造成实际损害时,简洁性永远不会胜出。 `caveman` 只减少输出令牌,绝不减少思考令牌。该项目将这一点总结为"大脑仍大,嘴巴小"。其宣称的平均 65% 节省量是针对一个冗长的"有帮助的助手"默认值测量的,因此它包括任何简洁指令都会带来的节省。以简单的"简洁回答"为基线测量时,真实的增量较低。我们下面的微基准测试得出的中位数是 50%。 我们直接测量每个转换,而不是通过一个实时的智能体。在实时运行中,配置之间的令牌差异主要来自回合数,而回合数因运行而异,隐藏了压缩器的作用。在固定的负载上,结果是确定性的。 我们从四个冻结的开源检出中捕获了负载:VS Code 1.99、llama.cpp b9692、uv 0.11.21 和 ollama 0.30.10。在每个仓库中,我们 grep 了一个高频符号,以便搜索返回数千个几乎相同的匹配行:VS Code 中的 `registerSingleton`,llama.cpp 中的 `ggml_tensor`,uv 中的每个 `pub fn`,以及 ollama 中的每个 `func`。对于 git diff,我们选取了一个真实的版本到版本范围,覆盖一个代表性的子目录:VS Code 从 1.98 到 1.99 的聊天浏览器代码,`ggml/src` 从 b9500 到 b9692,`uv-resolver` crate 从 0.10 到 0.11.21,以及 `server/` 从 v0.30.0 到 v0.30.10。然后每个工具在其拥有的流上运行:`headroom` 在工具结果输入上,`rtk` 在 shell 输出上,`caveman` 在模型散文上。 `headroom` 在这些 diff 和 grep 输出上运行。它的 grep 结果波动最大,从重复匹配的 99% 到匹配已经不同时的 10%。 [图片链接](https://substackcdn.com/image/fetch/$s_!MHjJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a109b28-7a39-474a-b610-33a3057a7161_434x338.png) `rtk` 在每个仓库中运行了四个 shell 命令:相同的 grep 和 git diff,加上在一个大目录(`src/vs/workbench/contrib/`、`ggml/src/`、`crates/`、`server/`)上的 `ls -la`,以及 `git log --stat -50`。grep 削减最多,因为 `rtk` 返回的是分组计数而不是每个匹配行。在 VS Code 的 `src/` 中,`rg registerSingleton` 从 694k 令牌减少到 2.4k。 [图片链接](https://substackcdn.com/image/fetch/$s_!0uik!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6b655b8-e2cd-4f31-b491-07076c1d4db4_686x342.png) grep 在每个仓库中都保持接近 99%,因为近乎相同的匹配行可以很好地压缩。ollama 在 git diff 上是例外。它在 `server/` 上从 v0.30.0 到 v0.30.10 的变化很小且代码密集,因此 `rtk` 只削减了 33%。 `caveman` 在其十个散文评估提示上运行。每个提示在带有技能的情况下的回答,与同一提示在简单的"简洁回答"系统提示下(没有技能)的回答进行比较。`caveman` 自己的评估使用这个简洁控制而非冗长默认值,因此节省量反映了技能在仅仅要求简短回答之上额外增加的效果。 [图片链接](https://substackcdn.com/image/fetch/$s_!mlGj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fff222d6a-7f4c-49f3-854f-b6ee51acd0ae_1076x736.png) 削减最少的是内存泄漏回答,仅为 0.4%,因为它本来就很简洁。十个提示的中位数是 50%。 每个工具在其针对性的内容上都达到了其宣称的节省量。`headroom` 在 grep 和 diff 上的中位数是 54%,`rtk` 削减识别的 shell 输出 33% 到 99%,`caveman` 将散文减少一半。这些数字是真实且可重复的,但它们是在理想输入上单独测量一个流的结果。 最佳情况下的数字不能代表真实账单,因此第二个实验在每个工具上的真实流量中重放。流量是我自己的 Claude Code 历史记录,位于 `~/.claude/projects` 下,共 2182 个会话,分布在 13 个项目目录中。我们从中抽样了 500 个会话,按目录分层抽样,以确保没有单个项目占主导地位。空会话和一个异常离群值被删除。这 500 个会话共同构成了一个 6.14 亿令牌的语料库,对应 926.31 美元的基线支出,我们逐回合重新计算了每个工具的反事实情况。 `headroom` 是直接测量的。其压缩器是负载的纯函数,因此我们在每个记录的工具结果上运行了真正的压缩器。`rtk` 和 `caveman` 无法在历史日志上忠实地重放,因此两者都是估计值。`rtk` 应用其自己发布的每个命令节省率(what-rtk-covers.md:12-20 (https://github.com/rtk-ai/rtk/blob/v0.42.4/docs/guide/resources/what-rtk-covers.md#L12-L20),根据命令不同为 60% 到 99%)到它本应匹配的每个记录的 shell 输出的真实令牌大小上。`caveman` 根本无法重放,因为它是一个提示,在生成时缩短模型的写作,而不是可以在完成的文本上运行的东西。因此,对于每个记录的回答,我们计算其散文令牌(像技能那样排除代码块),然后乘以 `caveman` 的中位数散文减少量(约 50%)。这假设 `caveman` 以基准测试的速率缩短真实回答,这使得它成为三者中最软的估计。 [图片链接](https://substackcdn.com/image/fetch/$s_!tyzh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59bea4b6-767f-4d83-8158-7e09e66d0b3c_1382x362.png) 在阅读两个令牌列时,有一件事需要记住。每个模型回合都会以更便宜的 `cache_read` 费率重新发送整个上下文,因此一个工具结果在后面的每个回合中都会被再次支付,直到会话结束。第一回合列计算压缩器在负载第一次出现时删除的内容,完整会话列计算同一负载在之后每次重新发送时的相同内容。因此,压缩一次就能多次节省令牌。`headroom` 的 150 万第一回合令牌在完整会话中累积到 4130 万,约为 27 倍。美元列对完整会话的节省进行定价。 有两个发现是微基准测试无法显示的。第一个是上面提到的生命周期增长,三种工具平均约为 26 倍,这来自于一次又一次地重新发送相同的压缩负载。第二个是 `rtk` 只接触到工具输出令牌的 22%,因为 78% 流经了绕过其 shell 钩子的原生工具,其中几乎全部都是 Read 工具。关于生命周期数字有一个注意事项。当上下文填满时,Claude Code 会压缩一个会话并删除旧的工具结果,但在所有 500 个会话中只出现了两个压缩事件。这个乘数假设负载的存活时间比实际更长,因此它是一个上限。真实的比较说明:实际节省量可能会低于此数字。

相似文章

令牌压缩幻象:为什么我对RTK持怀疑态度

Hacker News Top

本文批评了RTK,一种用于LLM代理的令牌压缩工具,认为其声称的60-90%成本节省具有误导性,引入了静默失败风险,缺乏严格的准确性基准,并且作为独立产品在结构上脆弱。

rtk-ai/rtk

GitHub Trending (daily)

RTK 是一个高性能的 CLI 代理,可在命令输出到达 LLM 上下文之前对其进行过滤和压缩,从而将 token 消耗减少 60-90%,且开销极低。