LLM Token优化:上下文压缩降低Agent成本关键
LLM Token 优化实战:从 Headroom 看透上下文压缩如何成为 Agent 成本的核心瓶颈
许多团队对 LLM 成本的认知仍停留在:“模型越来越便宜,token 消耗不值一提。” 这其实只对了一半。虽然单个 token 的单价确实在走低,Agent 的使用方式却让 token 消耗量级级放大。普通聊天场景下,单次请求可能只需几千 tokens;但在 AI Coding、Deep Research、RAG、日志分析、多工具 Agent 这类复杂任务中,单次任务就可能触发几十次工具调用。
每一次工具调用都伴随着文件内容、搜索结果、终端输出、JSON 响应、报错堆栈以及历史对话被重新塞入上下文。真正的成本天花板,并非来自“用户问了一句话”,而是来自“系统为了回答这一句话,把多少机器生成的冗余上下文反复喂给了模型”。
Headroom 官方将其定位为 LLM 应用的 context optimization layer,核心处理目标是 tool outputs、DB results、file reads、RAG results 等进入模型之前的原始内容。这个定位极其精准:它不是模型压缩、量化或蒸馏,而是解决“发给模型之前的上下文过于臃肿”这一工程问题。
核心公式可概括为:
LLM Token 优化 = 让模型只阅读高密度上下文,而非全量原始材料
为什么 token 压缩再次成为焦点?
一个 AI Coding Agent 为了修复一个 Bug,通常需要执行读取目录结构、搜索文件、打开源码、运行测试、查看日志、分析报错、追溯调用链、修改代码并最终将新日志提交给模型判断。每一步都会产生上下文,且这些上下文并非一次性消费,而是在多轮任务中持续累积。第 1 轮可能仅消耗 5K tokens,第 3 轮就可能膨胀到 30K,第 8 轮则直逼 100K。轮次越往后,每次请求携带的历史轨迹越长,成本滚雪球效应越明显。
RAG 场景同样如此。许多系统为了追求“召回更全”,将 top-k 设得极高,并将 chunk 原样塞入 prompt。看似上下文完整,实则经常将低相关片段、重复内容、HTML 残留、JSON 元数据等垃圾信息一并送入模型。
日志分析更是典型。一个 SRE Agent 读取 5 万行日志,其中绝大部分是正常请求、重复 warning、时间戳、线程名、traceId 和固定字段。真正有价值的信息往往只有几个 error、exception、timeout 或状态突变。
因此,第一条铁律是:
不要将 LLM 当作压缩软件、grep、数据库、搜索引擎和日志聚合器来使用。
模型应当消费被整理过的高密度信息,而非所有原始材料。
token 成本公式:别只盯着 input tokens
LLM API 的成本通常可拆解为四类:
总成本 = 输入 token 成本 + 输出 token 成本 + 缓存写入成本 + 缓存读取成本
展开来看:
Cost = input_tokens * input_price + output_tokens * output_price + cache_write_tokens * cache_write_price + cache_read_tokens * cache_read_price
这里有四个关键的工程要点。
第一,输出 tokens 的单价通常高于输入 tokens,因此不能只盯着输入,还要严格限制输出长度,避免模型反复生成冗长的解释性段落。
第二,在 Agent 场景下,输入 tokens 量级远超输出 tokens。模型可能只输出几百到几千 tokens,但输入可能高达几十万 tokens。
第三,prompt cache 虽能降低重复前缀的成本,但其效果高度依赖上下文结构的稳定性。如果每次请求都将当前时间、随机 sessionId、临时路径和工具输出塞入前缀,缓存命中率将大幅下降。
第四,压缩与缓存并非替代关系。压缩解决的是“少发无用内容”,缓存解决的是“重复内容避免重复计算”。
Headroom 的核心定位:解决上下文入口问题
从架构层面看,Headroom 部署在应用或 Agent 与 LLM Provider 之间,可通过 SDK、Proxy、MCP、Wrapper 等多种方式接入。它本质上是 LLM 应用中的一层 context middleware。
过去,Web 系统需要 API Gateway、鉴权中间件、缓存中间件、日志中间件。如今,LLM 应用同样需要上下文中间件,负责压缩工具输出、稳定缓存前缀、识别内容类型、控制上下文预算、保留原始内容以便回溯,并持续跟踪 token、成本和回答质量指标。
Headroom 的价值不在于某个宣传数字能适配所有场景,而在于它指明了一个方向:在请求到达 Provider 之前,优先做好 context optimization。
Token 优化不是单一技术,而是一整套系统工程
仅仅把 prompt 写短只是最浅层的优化。真正的 token 优化至少涵盖七个层面。
第一,Prompt 精简。果断删除无意义的礼貌用语、重复性约束、历史遗留说明和过度角色扮演,只保留任务目标、输出格式、边界条件、工具调用规则及失败处理规则。
第二,上下文裁剪。在多轮任务中,仅保留最近任务、关键决策、用户偏好和当前状态,丢弃已经完成的中间探索步骤。不要简单粗暴地使用 rolling window,否则可能误删“不要修改数据库结构”、“必须兼容 Java 8”这类早期约束。
第三,检索前过滤。RAG 不应是“召回大量内容,再让 LLM 自行判断”。更合理的链路应为:query rewrite、多路召回、去重、rerank、chunk 合并、query-aware compression,最终仅发送高相关性的片段。
第四,工具输出压缩。shell 输出、JSON 响应、数据库查询结果、搜索结果、测试日志、文件列表和 API 返回值通常具有强结构,适合保留 schema、异常项、统计分布、首尾样本、文件路径、行号和命中片段,而非将原始内容原样塞给模型。
第五,Prompt caching。system prompt、工具定义、输出格式、长期项目背景、API schema 和用户长期偏好应置于稳定前缀;当前时间、随机 ID、临时路径、当前请求参数和每轮变化的工具输出则应置于后缀。
第六,输出长度控制。例如:“只输出 JSON”、“只输出 diff”、“只输出 5 条以内结论”、“错误时只返回 error_code 和 reason”。输出控制的目的不是为了缩短,而是为了契合消费端的需求。
第七,模型路由。并非所有上下文都需要昂贵模型处理。可先用规则或廉价模型进行过滤、分类、去重、摘要、字段提取,再将高价值信息交由强模型处理。
原始输入 -> 规则过滤 -> 小模型压缩 / rerank -> 强模型推理 -> 结构化输出
这相当于将强模型从“清洁工”变为“决策者”。
Headroom 的几个关键设计思路
CacheAligner:确保 provider cache 真正命中
许多系统虽然存在大量重复 prompt,却始终无法命中缓存,根本原因在于前缀中混入了动态内容。例如,将当前日期、sessionId 置于工具定义之前,会导致其后稳定的内容也无法被复用。
更合理的结构是:将系统规则、工具定义、输出格式放在稳定前缀,将当前时间、临时状态、工具输出放在后缀。这个优化看似简单,但在 Agent 场景中价值极高,因为工具定义和系统规则通常长度可观,且每一轮都会重复发送。
ContentRouter:不同内容类型,采用不同压缩策略
压缩不能一刀切。JSON、日志、代码、HTML、普通文本、搜索结果的压缩方式差异巨大。
JSON arrays -> 结构化摘要 + 异常项 + 样本
Build logs -> 错误堆栈 + 前后窗口 + exit code
Search results -> 文件路径 + 行号 + 命中片段
Source code -> 保守保护,保留当前代码上下文
Plain text -> 摘要,但必须保护否定词、数值和条件
错误的压缩比不压缩更危险。将代码当作普通文本压缩,可能误删函数体;将日志当作文章摘要,可能丢失唯一的异常信息;将 JSON 随机截断,可能破坏其结构。
可逆压缩:压缩但不丢失证据
普通压缩的最大问题在于,被删除的内容就彻底消失了。压缩器无法预判哪些信息在后续环节会变得重要,一旦误删,模型也可能不知道自己缺少了什么信息。
更可靠的思路是可逆压缩:prompt 中只放置摘要、异常和代表性样本,原始内容则存储在本地 cache 中,并提供 retrieval handle。当模型需要时,可以调用工具取回原文,而不是在压缩后的摘要上硬猜。
原始 JSON 1000 条 -> prompt 中放摘要 + 异常 + 代表样本 -> 原始 1000 条存本地 cache -> 模型需要时调用 retrieve(id)
长上下文不等于不需要压缩
如今许多模型支持 200K、1M 上下文,但长上下文解决的是“放得下”,而非“用得好”,更不是“用得便宜”。
长上下文依然存在成本高、延迟高、注意力不稳定三个核心问题。更工程化的做法是将上下文划分为三层:
第一层:必须原样进入 prompt 的核心信息
第二层:压缩后进入 prompt 的辅助信息
第三层:不进入 prompt,但可通过工具按需检索的原始信息
用长上下文兜底,用压缩提高信息密度,用检索确保召回率,用缓存降低重复成本。
最适合压缩的场景
AI Coding Agent 最为典型。目录结构、搜索命中、大文件、构建日志、测试日志、package lock、依赖树、Git diff 和历史对话都极易产生大量冗余。优化策略为:目录仅保留相关路径,搜索结果保留文件名、行号和命中片段,构建日志保留失败命令、错误堆栈和最后统计,当前正在分析的代码默认不压缩。
RAG 问答系统的重点在于减少低相关 chunk 和重复内容。切勿将压缩视为召回质量差的补救措施,而应先解决召回和排序问题,再进行 query-aware compression。
日志分析 / SRE Agent 天然适合压缩,因为日志重复度高、结构固定、异常稀疏。应保留 error、exception、failed、异常前后窗口、状态变化、慢请求、超时、重试、traceId、requestId、host、pod,并对重复日志进行聚合。
数据库查询结果 / API 响应同样适合压缩。如果返回 1000 行数据,模型未必需要逐行阅读,可以压缩为字段说明、总数、分组统计、异常值、top/bottom、首尾样本以及与问题匹配的记录。
哪些场景不能盲目压缩?
法律、医疗、金融等强合规场景不能随意摘要后让模型判断,必须保留证据链、原文片段和来源位置。
数学推理、算法证明、形式化验证不能压缩题干、公式、约束和已知条件。
当前正在修改的代码不应进行压缩。代码修复需要精确的上下文,尤其是函数体、条件判断、注解、泛型、事务边界和异常处理。
用户的原始意图最好也不要压缩。用户表达中的限制、偏好、否定、语气和优先级都可能直接影响最终结果。
短请求也无需压缩。300 tokens 以内的普通对话,引入复杂的压缩层可能反而增加延迟。
一个合格的 token optimizer 不是“尽可能压缩一切”,而是清楚知道何时压缩、如何压缩、压缩后如何回退,以及何时坚决不压缩。
工程落地路线:从观测开始
如果打算为自己的 LLM 应用实施 token 优化,不建议一上来就接入复杂的压缩器。更稳妥的路线是:
- 先进行 token 观测,记录 input_tokens、output_tokens、cached_input_tokens、tool_output_tokens、rag_context_tokens、history_tokens、system_prompt_tokens、latency、TTFT、cost_estimate、compression_ratio 以及 answer_quality_score。
- 拆分上下文来源,不要只看总 input tokens,要细拆为 system prompt、tool definitions、conversation history、RAG chunks、tool outputs、user input。
- 为不同类型的内容设定 token budget。超出预算时,先去重,再裁剪低相关内容,然后进行结构化压缩,最后才考虑摘要。
- 工具输出先结构化,再压缩。不要将原始 stdout 直接塞给模型,可以返回 summary、important_lines 和 full_log_ref。
- 缓存稳定前缀。将系统提示词、工具定义、项目规范和输出格式放在前面;将当前用户问题、当前工具输出、临时信息放在后面。
- 建立质量评估体系。不要只看 compression ratio,还要关注准确率、工具调用成功率、RAG 忠实度、引用正确性、JSON 有效性、延迟、成本和回退检索率。
- 灰度上线。压缩层应支持 off、audit、optimize 三种模式。先 audit,只统计不改变请求;确认低风险后再 optimize。压缩失败必须 fail open,返回原文。
结语:token 压缩不是抠成本
LLM Token 优化将成为 Agent 工程中的基础设施。
早期 LLM 应用只要会写 prompt 就能跑起来;下一阶段,真正的差距将体现在上下文管理能力上。谁能将有限的 tokens 转化为高密度信息,谁就能获得更低的成本、更低的延迟和更稳定的答案。
Headroom 受到关注并非偶然。它击中的不是小技巧,而是 Agent 时代的结构性问题:工具越来越多,上下文越来越长,模型越来越强,但发送给模型的信息也越来越杂。
不要让模型阅读垃圾。不要为重复的上下文付费。不要将长上下文当作无限的垃圾桶。
真正应该进入模型的,是经过筛选、压缩、排序、缓存并可回溯管理的高价值上下文。
错误速查卡
| 症状 | 根因 | 定位 | 修复 |
|---|---|---|---|
| prompt cache 命中率长期为 0,成本居高不下 | 稳定前缀中混入了 sessionId / 当前时间 / 临时路径 / 每轮工具输出 | 抓取连续 N 次请求,比对 prefix 前 1024 tokens 是否一致 | 引入 CacheAligner: system prompt + 工具定义 + 输出格式放前缀,动态内容放后缀;用 Cache-Control: ephemeral 显式控制 |
| 单次输入 tokens 暴涨但答案质量未提升 | 工具原始输出(shell / JSON / 日志)直接塞进 prompt | 统计 tool_output_tokens 与 answer_quality_score 相关性 |
工具输出结构化:返回 summary + important_lines + full_log_ref;引入 ContentRouter 分类型压缩 |
| AI Coding 任务第 8 轮后单次成本翻倍 | 多轮上下文不断累积,未做上下文裁剪 | 看 history_tokens 趋势 + 任务完成度 | 引入任务级 context budget,裁剪已完成步骤;保留关键决策、用户偏好、当前状态 |
| 压缩后模型开始"答非所问"或编造 | 一刀切摘要丢失关键异常 / 数值 / 否定词 | 比对压缩前后 answer_quality_score + 引用正确性 | 改用可逆压缩(摘要 + 本地 cache + retrieve handle);压缩失败必须 fail open 返回原文 |
| 压缩 JSON 后模型报"语法错误 / 字段缺失" | 随机截断破坏 JSON 结构 | 看压缩输出是否能 JSON.parse | ContentRouter 对 JSON 走"结构化摘要 + 异常项 + 样本",不做截断式压缩 |
| 日志压缩后丢失唯一 FATAL / exception | 把日志当纯文本摘要 | grep 关键错误关键字是否在压缩结果中 | 日志走专用压缩:保留 error/exception/failed + 前后窗口 + exit code + 聚合重复行 |
| 长上下文模型依然贵、慢、注意力飘移 | 误以为"上下文长就不用压缩" | 测 TTFT、cost_per_task、长上下文 needle-in-haystack 准确率 | 引入三层上下文:核心原样 / 辅助压缩 / 检索按需 |
| 当前正在修改的代码被压缩后修复失败 | 代码压缩丢失函数体 / 条件 / 注解 / 事务边界 | 看 diff 是否引用了被压缩片段 | 把"当前编辑的代码文件"加入禁压列表;只压周边文件 |
| 合规/医疗/金融场景压缩掉证据链 | 一律摘要导致原文丢失 | 审计 prompt 是否含原文引用与来源位置 | 强合规场景走白名单:只压缩可公开字段,原文必须可检索 |
| RAG 召回质量差,寄希望于压缩补救 | 召回和排序未做就压缩 | 看 recall@K、context relevance | 先 query rewrite + 去重 + rerank + chunk 合并,再做 query-aware compression |
| 引入压缩层后延迟反而上升 | 300 tokens 以内的短请求也走压缩管线 | 看短请求 P50/P99 latency | 短请求(< 1K tokens)直接跳过压缩;长请求再启用 |
| prompt 中加入压缩层后输出变得更啰嗦 | 未对输出设长度控制 | 看 output_tokens 分布 | 加输出约束:"只输出 JSON"、"最多 5 条结论"、"错误时只返回 error_code 和 reason" |
| 模型路由后小模型浪费 token、大模型响应慢 | 所有请求都走强模型 / 所有请求都走弱模型 | 看 task_type → model 分布 + cost_per_task | 按任务分级:过滤/分类/去重走小模型或规则,决策/推理走强模型 |
headroom wrap 包装后工具调用报错 |
包装器与原 agent CLI 参数不兼容 | 看 wrap 启动日志 + 工具调用轨迹 | 升级到最新 wrap 模式;或在 SDK 模式做最小集成 |
| 压缩层故障导致整个 Agent 宕机 | 压缩层 fail closed,异常直接抛出 | 看异常日志 + 链路追踪 | 压缩层必须 fail open:异常时直接返回原文,不阻断业务 |
| audit 模式看到大量 compress_ratio 接近 1 | 压缩器对高密度内容强行压,浪费 CPU 且无收益 | 看 content_type × compression_ratio 分布 | 对压缩收益 < 5% 的内容直接跳过;记录白名单 |
| 第三方压缩器偷偷把数据外传 | 压缩器无本地化保证 | 看压缩器是否调用外网 / 是否经过审计 | 优先 local-first 方案(如 Headroom 的 local-first · reversible);外网压缩走严格白名单 |
headroom learn 写入的 CLAUDE.md 覆盖了项目原有规则 |
learn 未做变更范围控制 | diff CLAUDE.md 改动点 | 给 learn 设置写入白名单与 PR review;敏感目录加入 exclude |
参考来源
- Headroom GitHub 仓库:github.com/chopratejas…
- Headroom 官方文档:headroom-docs.vercel.app/docs
- 镜像仓库:github.com/gglucass/he…
- Headroom 实战解读:blog.csdn.net/forcedRegCs…
- 今日开源第 5 期 Headroom:www.cnblogs.com/zhang-yd/p/…