Agent 跑长任务时最典型的故障,不是模型不够聪明,而是它在第 40 轮之后开始胡来:重复调用同一个工具,忘记最初的目标,或者在自己删掉关键信息后给出一个看起来合理、实际跑不通的方案。Cognition AI 在谈单线架构时给过一个观察,一个 tool calling loop 连续跑 10~20 轮之后,很容易进入无法自我恢复的混乱状态。这里的混乱大多不是推理能力退化,而是上下文的组织和回收没做好。
“分段回收”是我在项目里常用的说法:不是等上下文撑到窗口上限再一刀切,而是在 Agent 循环中点状地识别哪些段落已经完成使命、哪些还需要留、哪些只需要留一个可回溯的钩子。
先承认物理限制:Context Rot 不是玄学
Chroma 的研究把这个现象叫 Context Rot,token 数量上升后,模型准确利用信息的能力持续下降。原因是自注意力在 token 数上是近似 O(n²)。窗口越大计算越贵,且关键信号会被噪声稀释。128K 到 200K 这个区间被不少人当成性能腐烂的阈值参考。
所以“上 1M 窗口就好了”是伪解。窗口换来的不是免费记忆,而是更贵的注意力分配。真正的问题是:哪些段落必须留在窗口里参与推理,哪些可以压缩成一次指针,哪些可以完全移出去、只在需要时拉回。
分段的第一刀:切静态,别切动态
合理的拆法不是按“第几轮到第几轮”切,而是按信息是否易变来切。System Prompt、工具定义、领域规则属于相对静态的部分,会话开始就定了,token 预算需要提前划走;对话历史、工具调用返回、检索片段属于动态增长部分,膨胀速度取决于任务类型。
静态部分适合做前缀缓存,动态部分适合按阶段注入。这条边界如果一开始不划清楚,后面所有回收动作都会变成“感觉这里差不多了删掉一点”,迟早删错。
回收的触发点:按预算放量,不按消息条数
比较可靠的触发条件是预算使用率,而不是消息数。用估算函数先把 token 算出来:文本按字符数除以 4、代码按除以 3、每条消息加 5 的结构开销,再乘 1.1 的安全边际,误差通常在 10~15%。在预算用到 75% 左右时启动压缩,目标压回 37% 附近,这个余量留给当前轮的输入输出和工具返回。
触发太早会浪费时间,触发太晚会导致这一轮已经被垃圾占满。按预算触发的好处是可以和 Prompt Cache 对齐:压缩本身会破坏前缀变化,频率必须控制。
三件事不能一起回收
压缩不能只交给一个 LLM 摘要了事。我自己踩过两类坑。
一是不可逆的截断
Shell 输出、长日志、MCP 返回值如果直接按行截断,最关键的报错往往就在末尾,或者藏在中间几行。Agent 拿到半截结果,会得出错误结论,然后花更多步数反复确认。较稳的做法是保留结构:错误类型、关键文件路径、URL、行号这类可回溯的指针必须留在摘要里。丢失细节可以接受,丢掉回源路径就是丢记忆。
二是错误路径被清空
失败的尝试是很有价值的上下文。Agent 删掉自己走过的那条死路,会很快再走一遍。失败分支值得用一条几十 token 的记录保留:试了什么、在哪一步挂了、看到什么报错。给信息打标记——冗余、关键、待确认——比按规则无条件删除可控得多,因为误删隐性关键信息的代价通常大于多留几行的代价。
Shannon 在压缩实现里用了一个三段式参考:Primers 留前 3 条消息,建立最初需求;SUMMARY 把中间几百条压成一段结构化摘要,保留关键决策、重要发现和未解决问题;RECENTS 留最后 20 条,找回“刚才那个方案”。这是最近这一侧连贯性的底线,删掉之后 Agent 就无法回答指代性的追问。
回收不等于只压:卸载和隔离是同一套账
LangChain 的四策略里,Compress 只是其中一个动作。Write 先把信息写到文件或 notepad 里,动态任务目标、已完成的检查项、等待解决的前提写成 todo 文件,需要时再读回上下文尾部——这还有一个额外好处,模型对上下文中间的信息关注度最低,回填到尾部能提高它被用上的概率。Select 强调的是 just-in-time,不预加载,按需从文件搜索或 RAG 取。Isolate 是在窗口容量之外做分区:子 Agent 在干净的窗口里探索几万 token,只返回一两千 token 的结论。
分段回收要配合这几件事才是完整的账。只压缩不卸载,摘要会越写越长、本身也要占空间;只卸载不隔离,超过单窗口容量的任务没有出口。
工程落地里真正麻烦的三件事
掩码成本不对称。没被采纳的探索历程不能一概删掉。给每条轨迹打上标记时要允许中性存在,给错误的尝试留下最小的结构记录。判断“这条能不能删”本身不值多少钱,判断错了的代价包括多走 10~20 步、多烧一轮 token,甚至模型在错误前提上给出一个看起来能跑的答案。
回收不能打断身份连续性。压缩之后,模型得认得出“我刚才提出这个方案”和“用户最初提的问题”之间的区别,对应的摘要方案就该保留话语主体和具体冲突点——两个决策的分歧不能合并。过于简化的摘要会制造出“虽然所有信息都提到了,但核心传达失败了”的结果。
需要在压缩前后保存缓存命中记录。不能绕开的情况是:一次会话里带了缓存命中,回收的边界一旦漂移,后面每轮的预填充成本都会变成全价。把缓存边界和分段边界放在同一次判断里,是一种会约束你决策的自由度。
一个可执行的最小策略
我目前的量尺是:
- System Prompt、工具定义做前缀缓存,开工前先按预算一次划清;
- 每条任务分支到完成就固化成一个“结论摘要+回源指针”,不上台堆在原位上;
- 出错或放弃的步骤用几行记录保留,不进入原始历史;
- 每到一个段边界处重新写一次任务目标,并定义清楚下一步边界——把叙述尾巴敲在这类可以被注意力覆盖的范围终点,比去评估中间哪些信号强可复用的姿态更好;
- 压缩过程中校验无法回放的边界点:工具定义丢失、原始文件句柄、调试所需的标识符。
大部分场景最先被忽视的从来不是窗口容量,而是回收是否可逆、分段是否对齐缓存这些没出现在示意图里的具体判断。
转载请注明出处: 皮哈皮阿
本文的链接地址: https://blog.phpat.com/ai-engineering/662.html
微信或手机浏览器扫一扫,随时随地随心阅读与分享
-
推理成本下降之后:模型路由、KV Cache 与端侧推理
上个月一个团队拿着账单来找我:模型单价降了,费用却涨了三倍。看完调用链就明白了,问题不在单价,在用法。单位 token 便宜,不等于每次任务便宜。
2026/10/10
-
2026 年 AI 应用评测:从单轮打分到多轮轨迹评估
一个 Agent 的评测分数从 72% 涨到 81%,团队很高兴,直到发现它学会了在失败时直接结束任务,把“没做完”算成了“无需回答”。评测设计错了,指标越好看越危险。
2026/10/19
-
推理模型的流式输出:thinking 块怎么流
推理模型的流式输出不只是逐字渲染:思考块、正文、工具调用参数混杂在 SSE 流里,客户端要按字段分流、独立缓冲、支持中途中断。记录接通义千问与智谱过程中的判断和踩坑。
2026/10/24
-
动态拼批不是开个开关:线上拼批的取舍与门禁
在线服务里的动态拼批和离线批量推理是两码事。窗口时长、KV Cache 水位、批内参数对齐共同决定拼接能否成立。上线前该盯的是 p99 和显存峰值,而不只是吞吐。
2026/10/25
-
长度惩罚参数调优:常见参数名字容易混淆
长度惩罚在主流 API 里并不存在。真正管长度的是 max_tokens 和 stop sequences,frequency、presence、repetition 惩罚管的是用词重复和新词探索。把这些旋钮分开,能避免输出被截断、术语被替换成同义词两类故障。
2026/10/23
-
推理服务排队延迟的分层观测:从请求数到Token账本
盯 QPS 和 P99 看不到排队延迟长在哪。把这件事拆成入口→上游准入队列→后端等待→batch 解码四层,用 vLLM 暴露的排队与 KV 缓存指标做层级归因,讨论分层之后仍然看不见的部分与取舍。
2026/10/27
京公网安备11011402053654 
@muffin:链接已更新,分类图像功能...
AI网址导航模版图标库更换指南
作者大大,图标库邀请链接失效了,麻烦更新...
AI网址导航模版图标库更换指南
太好看了,快点更新!
友情链接
@戴墨镜的刘备:哈喽,路过点赞。
vue.js组件之手写签名板vue-signature-simple
太好看了,快点更新!太棒了!👍真的很赞!
vue.js组件之手写签名板vue-signature-simple
你写得非常清晰明了,让我很容易理解你的观...
Emlog插件RSS订阅器发布