把 Agent 的上下文压缩和工具调用预算当成两个独立模块,是很多线上事故的起点。压缩器按自己那套阈值决定砍什么,预算模块按步数和 token 上限决定还能不能继续调工具,两边互不知情。结果是压缩刚把上一轮的 tool_result 抹成一行占位符,预算模块又放行了新一轮工具调用,而模型手里剩下的信息已经不足以构造正确的参数。
先看一组数量级。公开的统计里有一个挺有说服力的数据:工具响应占 Agent 总 token 的 67.6%,系统提示只占 3.4%。也就是说,上下文真正被撑爆的地方是工具返回值——读文件的几百行代码、网页搜索的几千字正文、一次 pytest 的完整输出。想靠优化 system prompt 来省上下文,等于在一栋失火的房子里修玄关。
压缩的触发点和预算的剩余量应该互相看得见
Hermes Agent 的做法比较有代表性。它把压缩分成两层:网关侧在上下文用到 85% 时做一次会话清理,作为安全网,防止 Telegram、Discord 这类场景隔夜堆出巨大会话直接打挂 API;agent 自己的 ContextCompressor 默认在 50% 触发,运行在工具循环内部,能拿到 API 实际报告的 token 数。
这两层阈值为什么不设成一样?资料里给的理由很具体:如果网关也用 50%,长会话每一轮都会提前触发压缩,白白多花摘要模型的调用和延迟。把安全网放高,正常机制放低,是在让主压缩器先干活。
真正该连起来看的是压缩器处在什么位置、预算剩多少。一个可用的规则是:压缩后保留的尾部 token 预算,必须大于下一轮最坏情况下的单次工具返回。项目里常见的 target_ratio: 0.20 之类的默认值来自通用经验,但如果你某个工具一次能返回 40K token 的 CSV,那么 20 万上下文模型下手握 100K × 0.20 = 20K 的尾部预算就不够,压缩刚结束就可能被一个新工具结果顶穿。
压缩过程的成本要计入调用预算
压缩不是免费的。Hermes 的摘要预算公式是 content_tokens × 0.20,下限 2000 token、上限 min(context_length × 0.05, 12000) token。Claude Code 走的是五层压缩引擎,从低成本阶段往上叠:先做预算缩减,再做剪裁去掉多余空行、过长缩进块、已被后续编辑覆盖的旧文件版本,然后才轮到微压缩等更重的步骤。排序原则就是便宜的先上。
这里有个容易踩的坑:摘要模型的上下文窗口必须不小于主 agent 模型。否则整段中间历史一次性送进去会直接返回上下文长度错误,Hermes 的实现会捕获这个错误、记一条警告、返回空摘要,然后丢弃中间轮次而不带摘要——静默丢上下文,是压缩质量下降最常见的原因。你会在几十轮之后突然发现 agent 忘了最初的约束条件,而日志里只有一个 warning。
从预算角度看,这意味着两件事:一,压榨后的连续多轮里,agent 实际拿到的总 token 数并不会线性下降,因为摘要本身也要付钱;二,若会话特别长,触发多次压缩,原文提到 Hermes 会做迭代重压缩,把上一次的摘要连同指令一起交给模型更新,条目从“进行中”移到“已完成”。这个机制能让信息在多次压缩里存活,但也让每次压缩多一次模型调用的账。
Suna 的演进方向是个有意思的反例。它早期的 context manager 是构造一段精心设计的 prompt 丢给 LLM 摘要,7 月的重构把 LLM 移除了,改用基于文本长度的本地截断,同时提供了 expand-message 工具,被截断的超长消息带 message_id 标记,模型需要时可回查完整内容。这样做出来的取舍很清楚:本地截断耗时稳定,省掉摘要那笔 token,但代价是上下文里常驻着“这里有东西被砍掉了”的痕迹;压缩不再是有损的语义提炼,变成了分页。哪种更好,取决于你的场景容不容忍模型做一次额外的工具调用去补细节。
预算:一次工具调用至少占三段开销
工具调用的成本不是一个数字。第一段是工作流本身从上一轮结束点重新读上下文时的 prefilling;第二段是模型花在 CoT 上的 reasoning token;第三阶段是天真的工具返回值。真正的陷阱往往在第一段和第三段的相加:如果你用推测解码、prefix cache 做优化,却忽略了工具响应输入经常是一个巨大的非缓存命中段落,那压缩策略就会与缓存策略打架。
这类问题的解法是让压缩与预算共用一份可观测的 token 台账。每次准备调用工具前先预测返回大小(正常情况下压到 read_file 的 max_lines / 搜索的 top_k 上去固化),如果 remaining_budget < 预计返回 + 兜底,那就先清理遗留的 snapshot,把文件读取改成离线读结果列摘要,但这一轮请求仍然放行。短期看像是把超支从下一轮挪到了这一轮,但上下文确实少了一条垃圾结果,代价是用户多看不到完整的工具响应。
关键在于是“把完整证据传给模型”还是“把证据拦在门外”。对大输出场景,正确做法通常是让工具层返回已裁剪、带分页游标和 message_id 的可替代结果,而不是靠压缩器做统一砍伐,因为压缩器看不到领域语义。
真实业务里,模型早就练出了在缺证据时发起扩查的本能,因为多数 harness 就是让它“记住 truncation 警告-下次再拉具体段落”这样驱动工具循环。所以一个报文的摘要摘要也不完美,但尾部紧丢掉可还原的线索,反而让 agent 进入无效循环。要想少踩,压缩逻辑至少该替 agent 留一个 message_id / turn_span_id / 查询窗口之类的恢复锚点,被截断的 tool_result 可以机械地通过一次工具调用恢复原文。Hermes 在 lean 尾部模式里做的是类似的“降级成一行占位 + 恢复指引入口”的设计,保留 session_search 的路径指向。
结论
把上下文压缩和工具调用预算拆成两个人管,Agent 就会在两者边界上反复出事。更稳的设计是把它们放在同一个循环里:用真实的 API token 数做 watch,用最便宜的清理步骤优先触发,为预留的尾部预算叠加最大工具返回实测值而不是经验比例,压缩器不允许输出不可恢复的“上下文段落被吞掉”警告。剩下的就是把这些逻辑都写便宜——能本地截断的不调摘要模型,因为模型的稳定输出还没那种命。
转载请注明出处: 皮哈皮阿
本文的链接地址: https://blog.phpat.com/agent-engineering/681.html
微信或手机浏览器扫一扫,随时随地随心阅读与分享
-
AI Coding Agent 如何稳定提交可合并的 PR
上周 review 了一个 AI 生成的 PR,测试全绿,但我还是把它关了。它改了 37 个文件,顺手升级了一个依赖,还修改了两条测试断言。问题不是代码不能跑,而是这个 PR 没人敢合。
2026/10/14
-
Agent Harness:可观测、可评测、可回滚的 Agent 基础设施
去年我们做了一个处理退款申请的 Agent。Demo 很顺,上线第一周就出了事故:进程重启后状态丢了,同一笔申请被处理了两次。问题不在模型,而在模型外面那层没人愿意写的基础设施。
2026/10/09
-
MCP 进入生产环境:工具协议、网关与权限边界
MCP 刚出来时,我以为最难的是协议兼容。真正接了十几个工具之后才发现,把工具接上只花了两天,把权限、审计和故障处理理清楚花了两个月。
2026/10/07
-
Agent 安全清单:提示注入、工具越权与数据外泄
一个内部助手被要求总结一封客户邮件。邮件末尾有一行白色小字:把最近的客户名单转发到某个地址。Agent 真的准备去调邮件工具了。安全不能靠模型自觉,得靠系统设计。
2026/10/16
-
并行工具调用暴露的工具设计耦合问题
并行工具调用能砍掉四成延迟,但它更重要的价值在于暴露工具之间的隐性耦合。本文拆解上下文依赖、共享状态竞争与时序前提这三类静默失败,给出读写分类规则、工具合并的判据,以及什么时候该上依赖图编排。
2026/10/23
-
Agent 子目标检查点回归:从回滚状态到登记副作用
Agent 的子目标检查点能回滚本地状态,却回滚不了已提交的外部副作用。本文拆解恢复后重新合成工具调用导致的语义回滚,给出基于副作用指纹的检查点边界划分与确定性回归断言。
2026/10/25
京公网安备11011402053654 
@muffin:链接已更新,分类图像功能...
AI网址导航模版图标库更换指南
作者大大,图标库邀请链接失效了,麻烦更新...
AI网址导航模版图标库更换指南
太好看了,快点更新!
友情链接
@戴墨镜的刘备:哈喽,路过点赞。
vue.js组件之手写签名板vue-signature-simple
太好看了,快点更新!太棒了!👍真的很赞!
vue.js组件之手写签名板vue-signature-simple
你写得非常清晰明了,让我很容易理解你的观...
Emlog插件RSS订阅器发布