
1. 从“第20轮失忆”说起Agent 上下文管理的真实痛点如果你正在做 Agent 开发大概率遇到过这个场景前几轮对话还挺聪明到第 15 轮、第 20 轮之后它开始忘记之前说过的约束条件重复问已经回答过的问题甚至把用户最早设定的角色定位都搞混了。很多人第一反应是“模型不行”或者“上下文窗口太小”但实际排查下来问题往往出在上下文管理策略上而不是模型本身。我最近在做一个多轮任务型 Agent 的项目涉及工具调用、文件读写和长流程编排。刚开始用的是最朴素的做法把每一轮对话原封不动地追加到消息列表里直到接近模型上下文窗口上限才做截断。结果就是标题里说的那个现象——聊到第 20 轮左右Agent 的“智商”断崖式下跌。后来我把这套逻辑推倒重来引入了Context Editing、Compaction和Memory Tool三层机制才真正把这个问题按住。这篇文章适合正在做 Agent 开发、遇到过上下文超长导致行为退化、或者正在选型 Agent 框架的工程师。我会把整个思路拆开讲清楚为什么“管理历史”和“管理上下文”是两回事Compaction 到底该怎么压Memory Tool 什么时候该写、什么时候该读以及在实际项目里踩过的坑。全文基于我自己的实操经验涉及参数和策略的地方会给出具体的计算过程和取舍理由。2. 核心认知纠偏管理历史不等于管理上下文2.1 历史是流水账上下文是工作台很多人把“对话历史”和“模型上下文”当成同一个东西这是最根深蒂固的误解。历史是发生过什么的完整记录它应该是持久化的、可追溯的、尽量不丢信息的。而上下文是模型当前这一步推理所需要的信息集合它是一个工作台不是仓库。打个比方历史就像你公司的全部会议纪要存档上下文就像你此刻摆在办公桌上的那几页纸。你不会把三年的会议纪要全铺在桌上干活那样你什么都找不到。Agent 也一样每一轮推理时塞进上下文窗口的内容应该是经过筛选和加工的“当前工作集”而不是历史的简单堆叠。这个认知转变带来的直接后果是上下文是需要主动构造的而不是被动累积的。一旦你接受这个前提后面所有的策略——Compaction、Context Editing、Memory Tool——才有了落脚点。2.2 上下文窗口用完时模型到底发生了什么要理解为什么第 20 轮会“失忆”得先知道上下文窗口被填满后模型的行为变化。假设你用的是 32K 上下文窗口的模型系统提示词占了 2K工具定义占了 3K剩下 27K 给对话历史。如果每轮对话平均消耗 1.5K token那么大约 18 轮之后历史就会把剩余空间吃满。这时候常见的处理方式是截断——从最老的消息开始删。问题在于最早的消息里往往包含系统级约束用户设定的角色、任务目标、关键偏好、已经确认过的决策。这些信息一旦被截掉模型就失去了行为锚点开始自由发挥。更隐蔽的问题是中间轮次里可能藏着工具调用的结果、文件路径、变量值截断后模型会“假装记得”编造出错误的内容。我实测过一个案例一个代码助手 Agent 在第 22 轮时被要求“把刚才那个函数改成异步的”它确实改了但改的是它自己编造的一个函数名因为真正的函数定义在第 6 轮的工具返回里已经被截掉了。这种错误非常难排查因为模型输出看起来很合理。2.3 三种主流策略的定位差异目前业界处理长上下文的主流思路可以归为三类它们不是互斥的而是分层的策略作用对象核心动作适用时机Context Editing当前上下文删除、替换、重排消息每轮推理前Compaction历史消息摘要、合并、压缩上下文接近阈值时Memory Tool跨会话信息写入、检索、更新需要长期记忆时Context Editing 是“整理桌面”Compaction 是“把旧文件归档成摘要”Memory Tool 是“建一个可检索的档案库”。三者配合才能让 Agent 在长流程里保持稳定。下面我逐个拆解。3. Context Editing每轮推理前的上下文整理术3.1 什么该留、什么该删、什么该换Context Editing 的核心是在构造请求前对消息列表做一次“编辑”。我常用的规则有这么几条保留系统提示词和工具定义这两块是行为基线永远不动。保留最近 N 轮完整对话N 一般取 3 到 5保证短期连贯性。对更早的消息做选择性保留只保留包含关键决策、工具调用结果、用户明确约束的消息其余删除或替换为占位符。工具调用结果做摘要替换原始返回可能很长替换成一句话摘要加关键字段。这里有个细节删除消息时不能简单地从列表里 pop因为很多模型对消息顺序有要求尤其是 tool 角色消息必须紧跟对应的 assistant 消息。我的做法是标记删除然后在构造请求时过滤保持结构完整。3.2 消息优先级打分的一个实用方案为了自动化决定保留哪些旧消息我给每条消息打了一个优先级分数。评分维度包括是否包含用户明确指令权重高是否包含工具调用及其结果权重中高是否是 assistant 的最终结论权重中是否是中间过程性对话权重低消息年龄越老权重越低具体实现时我用了一个简单的加权公式score w1 * is_user_instruction w2 * has_tool_result w3 * is_final_answer - w4 * age_in_turns权重我一般设 w15, w23, w32, w40.1。然后按分数排序保留 top K 条旧消息K 根据剩余 token 预算动态调整。这套方案不复杂但实测比单纯按时间截断稳定得多。注意优先级打分不要做得太复杂规则超过 10 条之后维护成本会急剧上升而且容易出现互相冲突的规则。够用就好。3.3 实操中的三个坑第一个坑是过度删除导致上下文断裂。有一次我为了省 token把工具调用的中间结果全删了只留最终结论。结果模型在后续推理时无法解释“为什么得出这个结论”用户追问细节时它就开始编。后来我改成保留工具调用的关键参数和结果摘要问题才解决。第二个坑是占位符本身消耗 token。我一开始用很长的占位符文本比如“[此处省略了第 5 轮到第 8 轮的对话主要内容是……]”结果占位符加起来也占了不少空间。后来改成极简标记比如[omitted: 4 turns]需要时再通过 Memory Tool 检索。第三个坑是编辑后消息角色顺序错乱。有些框架对消息序列有严格校验删除后可能出现连续两条 user 消息或者 assistant 消息后面直接跟 system 消息。我的处理方式是在编辑后做一次规范化确保角色交替合理必要时插入空的 assistant 消息做占位。4. Compaction把长历史压成高质量摘要4.1 Compaction 不是简单总结而是信息重构很多人把 Compaction 理解成“让模型总结一下前面的对话”然后就把摘要塞回去。这样做的问题在于通用总结会丢失任务相关的关键细节。比如前面讨论了五个方案总结成“讨论了几个方案”那后续要选型时模型就抓瞎了。我的做法是带 schema 的压缩。在触发 Compaction 时给模型一个明确的结构要求让它按固定字段输出{ task_goal: 当前任务的最终目标, confirmed_decisions: [已确认的决策列表], key_constraints: [用户明确提出的约束], tool_results_summary: [工具调用结果的关键信息], open_questions: [尚未解决的问题], context_for_next: 下一步推理需要知道的最小信息集 }这样压出来的摘要信息密度远高于自由文本总结而且后续可以直接按字段检索和更新。4.2 触发时机与压缩比的计算Compaction 什么时候触发我的经验是不要等到窗口满了才压。因为压缩本身也需要调用模型会消耗 token 和时间。如果等到只剩 2K 空间才压可能压缩请求本身就放不下。我一般设两个阈值软阈值上下文使用率达到 60% 时开始准备压缩。硬阈值使用率达到 80% 时强制执行压缩。压缩比方面我实测下来把 10 轮对话压成 1 份结构化摘要token 消耗大约降到原来的 15% 到 25%。具体数字取决于对话的信息密度。如果对话里工具调用多、结果长压缩收益更大如果本来就是短对话压缩收益有限这时候不如直接用 Context Editing 删掉低优先级消息。计算过程举个例子假设当前上下文 20K token软阈值 60% 对应 12K。如果历史部分占了 10K压缩后变成 2K那么总上下文降到 12K又回到了安全区间。这个循环可以持续很多轮。4.3 压缩后的摘要怎么放回去压缩完成后摘要不能随便塞。我通常把它作为一条特殊的 system 消息或者 assistant 消息插入位置在系统提示词之后、最近几轮对话之前。这样模型在推理时先看到行为基线再看到历史摘要最后看到近期对话层次清晰。提示摘要消息要明确标注它的性质比如加上[compacted history]前缀避免模型把它当成当前轮的用户输入。还有一个细节压缩后的摘要不是一成不变的。随着新对话产生旧摘要可能需要和新信息合并。我的做法是每次压缩时把上一次的摘要也作为输入让模型做增量更新而不是重新总结全部历史。这样既省 token又保证摘要的连续性。5. Memory Tool让 Agent 拥有跨会话的长期记忆5.1 Memory Tool 和 Compaction 的本质区别Compaction 解决的是单次会话内的上下文膨胀问题Memory Tool 解决的是跨会话的信息持久化问题。两者经常被混为一谈但设计目标完全不同。Compaction 的产物是给当前会话用的摘要会话结束就没了。Memory Tool 的产物是写入外部存储的结构化记忆下次会话开始时可以按需检索。举个例子用户第一次会话告诉 Agent“我的项目用 Python 3.11依赖管理用 uv”这个信息应该写入 Memory Tool而不是只压在 Compaction 摘要里。否则下次新会话Agent 又得重新问一遍。5.2 写入策略什么时候该记什么时候不该记Memory Tool 最大的风险是记太多。如果每轮对话都往里写很快记忆库就变成垃圾场检索出来的全是噪音。我的写入策略是用户明确表达的偏好和约束必写。已经确认的决策和结论必写。工具调用产生的可复用结果选择性写比如文件路径、配置值。中间过程性对话不写。模型的推测和未确认内容不写。写入时我会带元数据时间戳、来源会话 ID、置信度、过期时间如果有。这样检索时可以按新鲜度和可靠性排序。5.3 检索策略怎么让记忆真正被用上写入只是第一步关键是检索。我见过很多项目Memory Tool 写了一大堆但 Agent 从来不去读等于白做。我的做法是在每轮推理前用当前用户输入去检索相关记忆把 top 3 到 5 条注入上下文。检索方式我用的是混合检索关键词匹配加向量相似度。纯向量检索在专有名词和精确匹配上表现不稳定加上关键词兜底会好很多。检索结果注入时我会加上[memory]前缀并限制总长度避免记忆挤占正常对话空间。注意记忆检索不要每轮都做全量检索那样延迟很高。我的做法是先用轻量规则判断“这轮是否需要记忆”比如用户提到“之前”“上次”“我的偏好”等词时才触发检索。5.4 记忆的更新与遗忘机制记忆不是只增不减的。我设计了一个简单的衰减机制每条记忆有一个last_accessed时间戳和access_count。如果一条记忆超过 30 天没被访问且访问次数低于 2 次就标记为冷记忆检索时降权。如果超过 90 天没访问直接归档删除。另外当用户明确说“我改主意了”或者“之前那个不对”时要能定位到相关记忆并更新或删除。这个能力很关键否则 Agent 会拿着过时信息一直用。6. 三层机制怎么配合一个完整的上下文管理流水线6.1 每轮推理前的标准流程把三层机制串起来我实际项目里的流程是这样的接收用户输入判断是否需要检索 Memory Tool。如果需要检索并注入相关记忆。检查上下文使用率。如果超过软阈值触发 Compaction生成或更新结构化摘要。执行 Context Editing。按优先级打分删除或替换低价值旧消息保留系统提示词、摘要、最近 N 轮和关键工具结果。构造最终请求发送给模型。模型返回后判断是否有新信息需要写入 Memory Tool。如果有异步写入不阻塞主流程。这个流程每轮跑一次开销主要在 Compaction 和 Memory 检索上。实测下来平均每轮额外增加 200 到 500 毫秒延迟换来的是长流程稳定性的显著提升。6.2 不同场景下的参数调优建议参数没有万能值得根据场景调。我整理了一个参考表场景类型最近轮数 N软阈值硬阈值记忆检索条数短任务问答570%90%2多轮工具调用360%80%3长流程编排350%75%5跨会话助手465%85%5长流程编排之所以阈值设得低是因为工具调用结果占空间大而且一旦上下文被工具结果挤满模型对任务目标的注意力会急剧下降。宁可多压几次也不要让上下文处于紧绷状态。6.3 一个容易忽略的点工具定义也占上下文很多人只关注对话历史忘了工具定义本身也占不少 token。如果你有 20 个工具每个工具的描述加参数 schema 平均 150 token那就是 3K token。如果这些工具在当前任务里根本用不到它们就是纯浪费。我的做法是动态工具加载根据当前任务阶段只注入相关的工具子集。比如任务刚开始时只需要“读取文件”和“搜索”工具到写文件阶段再加载“写入”和“编辑”工具。这样能把工具定义从 3K 压到 1K 以内。7. 常见问题与排查技巧实录7.1 Agent 突然开始重复之前的问题这是最典型的上下文管理失效症状。排查顺序先看上下文使用率是不是已经超过硬阈值但 Compaction 没触发。再看最近几轮消息是不是关键约束被 Context Editing 误删了。最后看 Memory Tool是不是该检索的记忆没检索到。我遇到过一次原因是 Compaction 的触发条件写错了用的是 token 绝对值而不是百分比结果换了模型之后窗口变了阈值没跟着变导致一直不触发。7.2 Compaction 后 Agent 行为风格突变压缩摘要如果写得太“概括”模型会丢失原来的行为风格。比如原来用户要求“回答要简洁不要用列表”摘要里没体现这条压缩后模型就开始长篇大论。解决办法是在 Compaction 的 schema 里强制包含style_constraints字段把用户的风格要求单独拎出来确保不丢。7.3 Memory Tool 检索出无关记忆这通常是写入时没有做去重和分类。我的处理方式是给记忆打标签检索时先按标签过滤再做相似度排序。另外检索结果注入前做一次相关性判断低于阈值的直接丢弃宁可少注入也不要注入噪音。7.4 上下文编辑后模型报消息格式错误前面提过删除消息容易导致角色顺序错乱。我的排查清单是检查是否有连续两条同角色消息。检查 tool 消息是否都有对应的 assistant 消息。检查 system 消息是否只在开头。检查是否有空内容的消息。写一个规范化函数在每次编辑后跑一遍能省掉大量调试时间。7.5 长流程中 Agent 忘记任务目标这是最严重的情况。我的兜底方案是在每轮请求的最后追加一条简短的“任务提醒”内容从 Compaction 摘要的task_goal字段取。这条提醒只有一句话token 开销极小但能显著降低目标漂移的概率。提示任务提醒不要写得太长超过 50 token 就开始挤占正常上下文反而得不偿失。8. 我在实际项目中的几点体会这套三层机制跑了大半年最大的感受是上下文管理不是一次性工程而是持续调优的过程。不同任务类型、不同模型、不同用户习惯都会影响最优参数。我现在的做法是给每个 Agent 配一套可调的配置上线后根据日志持续微调。另外不要迷信“大窗口就能解决问题”。我试过把窗口从 32K 换到 128K短期内确实缓解了截断问题但长流程下 Agent 的注意力分散问题反而更严重了——上下文里塞了太多无关信息模型抓不住重点。窗口大是好事但前提是你得会用它。最后分享一个小技巧在开发阶段把每轮请求的上下文构造结果打日志包括保留了哪些消息、压缩摘要内容、检索到的记忆。出问题时翻日志比盲目调参快得多。这个习惯帮我省了至少一半的排查时间。