Agent上下文管理:Context Editing、Compaction与Memory Tool实战

发布时间:2026/10/6 11:06:37
Agent上下文管理:Context Editing、Compaction与Memory Tool实战
1. 从第20轮失忆说起Agent上下文管理的真实痛点如果你正在做 Agent 开发大概率遇到过这个场景前几轮对话里 Agent 表现得像个专家逻辑清晰、工具调用准确但聊到十几二十轮之后它开始答非所问忘了最初的目标甚至把之前已经确认过的参数又搞错了。你翻日志一看上下文窗口塞满了历史消息模型在信息过载下开始抓不住重点。这不是模型变笨了而是上下文管理策略出了问题。很多人第一反应是换个上下文更长的模型从 32K 换到 128K再换到 1M结果发现——问题依旧只是推迟到了第 50 轮。因为上下文长度是资源不是解决方案。真正的高手管的不是历史记录而是当前这一步模型需要看到什么。这篇文章我想系统聊聊 Agent 上下文管理的几个核心机制Context Editing上下文编辑、Compaction压缩、Memory Tool记忆工具以及它们在实际项目里怎么配合使用。适合正在做 Agent 开发、被上下文超长折磨过、或者正在设计多轮对话架构的同学。我会尽量把原理讲透把踩过的坑摊开让你少走弯路。先说一个反直觉的结论上下文管理的目标不是记住更多而是在该忘的时候果断忘在该记的时候精准记。这个思路转变是从管理历史到管理上下文的分水岭。2. 为什么堆历史是最容易踩的坑2.1 上下文窗口用完时模型到底发生了什么很多人把上下文窗口想象成一个容器觉得只要没装满就能一直用。但实际机制更微妙。Transformer 的注意力机制在计算时每个 token 都要和上下文里所有 token 做注意力计算。当上下文从 4K 涨到 100K注意力矩阵的规模是平方级增长的这带来两个后果第一注意力稀释。模型对关键信息的注意力权重会被大量无关历史摊薄。你前面第 3 轮说的用户 ID 是 12345到第 20 轮时这个信息在注意力分布里可能已经被淹没在几十条闲聊里了。第二位置偏差。大量实验表明模型对上下文开头和结尾的信息记得最牢中间部分容易丢失。这就是所谓的lost in the middle现象。你把关键约束放在第 8 轮很可能正好落在被忽略的区间。所以上下文窗口用完了怎么办这个问题真正的答案不是扩容而是在窗口用完之前主动做信息取舍。2.2 一个真实的翻车案例我之前做过一个客服 Agent需求是帮用户处理订单问题。初期设计很简单把完整对话历史每轮都塞进去。测试阶段聊 5 轮没问题上线后遇到一个用户连续咨询了 18 个订单到第 15 轮左右Agent 开始把订单 A 的收货地址套用到订单 B 上。排查后发现上下文里堆了 15 轮对话包含十几个订单号、地址、金额模型在生成时无法稳定区分哪个订单对应哪个信息。信息都在但关联关系丢了。这就是典型的历史堆砌陷阱——你以为模型能看到所有信息实际上它看到了一堆互相干扰的噪声。后来我们改成每轮只保留当前订单的完整信息 最近 3 轮的对话摘要 全局的用户偏好问题立刻消失。上下文长度从平均 8K 降到 2K响应速度还快了。2.3 管理历史和管理上下文的本质区别这两个词听起来像同义词但思路完全不同维度管理历史管理上下文核心动作记录、追加、保存筛选、压缩、重组关注点完整性相关性增长模式线性增长有界波动失败表现窗口溢出注意力稀释典型手段全量存储Context Editing / Compaction管理历史的人思考的是怎么把东西存下来管理上下文的人思考的是这一步模型需要什么。前者是被动的后者是主动的。这个转变是 Agent 从能跑到跑得稳的关键。3. Context Editing在塞进模型前做减法3.1 Context Editing 到底编辑什么Context Editing 的核心思想是在把上下文送给模型之前先做一轮筛选和重组。它不是简单地截断而是有策略地决定哪些留、哪些删、哪些改写。常见的编辑动作有这么几类去重多轮对话里重复出现的系统提示、工具定义、固定约束只保留一份。裁剪工具调用的原始返回比如一大段 JSON只保留模型真正需要的字段。替换把冗长的历史对话替换成一句摘要。重排把关键信息挪到上下文开头或结尾避开中间丢失区间。隔离不同任务的信息分区存放避免互相污染。这五类动作里裁剪和替换的收益最大因为它们直接减少 token 数量重排和隔离的收益最隐蔽但往往能解决信息都在却用不对的诡异问题。3.2 工具返回值的裁剪一个被低估的优化点Agent 调用工具后返回值经常是一大坨结构化数据。比如查订单接口返回 30 个字段但模型真正需要的可能只有订单号、状态、金额三个。如果你把 30 个字段全塞进上下文每轮浪费几百 token20 轮下来就是上万 token 的浪费。我的做法是在工具层做一次字段白名单过滤。具体来说给每个工具定义一个context_fields配置只把白名单里的字段注入上下文其余字段存在外部存储里需要时再按 ID 取回。# 工具返回值裁剪示例 TOOL_CONTEXT_FIELDS { query_order: [order_id, status, amount, created_at], query_user: [user_id, level, preferred_language], } def trim_tool_result(tool_name, raw_result): fields TOOL_CONTEXT_FIELDS.get(tool_name, []) if not fields: return raw_result return {k: v for k, v in raw_result.items() if k in fields}这个改动看起来简单但在实际项目里它能把单轮上下文的工具部分从 800 token 压到 150 token 左右。20 轮下来省下的空间足够多塞好几轮对话摘要。注意裁剪字段时要留一个逃生通道。如果模型明确要求某个被裁掉的字段要能从外部存储里补回来否则会出现模型问了一个它本该知道的信息的尴尬。3.3 系统提示的锚点策略系统提示system prompt是每轮都要带的但很多项目的系统提示写得又长又全几百上千 token 每轮重复。这里有个技巧把系统提示拆成不变锚点和可变指令两部分。不变锚点包括角色定义、核心约束、输出格式要求这些每轮都一样可以放在上下文最开头利用模型对开头信息记得牢的特性。可变指令包括当前任务目标、本轮特殊要求放在上下文结尾利用结尾的高注意力。中间部分才是对话历史和工具结果。这样整个上下文形成一个强-弱-强的注意力结构关键信息落在两端噪声在中间模型抓重点的能力会明显提升。3.4 什么时候该编辑什么时候不该动Context Editing 不是越激进越好。我踩过的坑是早期为了省 token把历史对话压得太狠结果模型丢失了必要的上下文连贯性回答变得跳跃。判断标准可以这样定如果删掉某段信息后模型在后续 3 轮内没有因此产生错误那这段信息就是可以删的。反过来如果删了之后模型反复追问同一个信息说明这段是承重墙不能动。实操上我会给不同类型的上下文打标签critical关键约束永不删、reference参考信息可压缩、transient临时状态可删。编辑策略按标签走而不是一刀切。4. Compaction把长历史压成可用的记忆4.1 Compaction 和 Context Editing 的分工如果说 Context Editing 是每轮做小减法那 Compaction 就是阶段性做大压缩。它通常在上下文接近阈值时触发把一大段历史对话压缩成一段摘要用摘要替换原始对话。两者的分工可以这样理解Context Editing高频、轻量、局部每轮都做。Compaction低频、重量、全局接近阈值时做。一个健康的 Agent 上下文管理应该是 Editing 每轮跑Compaction 按需跑。只做 Editing 不做 Compaction上下文还是会缓慢增长只做 Compaction 不做 Editing压缩频率会过高每次压缩都损失信息。4.2 压缩的粒度摘要到什么程度Compaction 最难的不是压而是压到什么程度。压得太粗信息丢失压得太细等于没压。我的经验是分三层压缩第一层对话摘要。把 5-10 轮对话压成 2-3 句话保留用户说了什么、Agent 做了什么、结论是什么。第二层状态快照。把当前任务的关键状态比如已确认的参数、待办事项单独抽出来结构化存储。第三层决策日志。记录 Agent 做过的关键决策和理由用于后续一致性检查。这三层里状态快照最重要。因为对话摘要会丢细节但状态快照能保证关键信息不丢。很多 Agent 失忆的本质是状态没被单独抽出来全埋在对话历史里一压缩就没了。4.3 压缩触发时机的选择什么时候触发 Compaction常见的有三种策略阈值触发上下文 token 数超过窗口的 70% 时触发。轮次触发每 N 轮固定触发一次。事件触发任务阶段切换时触发比如从信息收集进入执行阶段。我推荐阈值 事件混合。纯阈值触发的问题是可能在任务中途压缩打断连贯性纯事件触发的问题是遇到长任务可能还没到事件点就溢出了。混合策略能兼顾两者。具体参数上我一般设两个水位线软阈值 60%时开始准备压缩生成摘要但不替换硬阈值 75%时执行替换。这样压缩动作有缓冲不会太突兀。4.4 压缩后的一致性校验压缩最大的风险是压出矛盾。比如压缩前 Agent 已经确认用户要退款压缩后摘要写成用户咨询退款语义就变了。我的做法是压缩后跑一次一致性校验把压缩前的关键状态和压缩后的摘要做对比检查有没有语义漂移。校验可以用规则关键词匹配也可以用模型让另一个模型判断摘要是否忠实。这一步多花一点成本但能避免很多Agent 突然改口的诡异 bug。5. Memory Tool让 Agent 拥有外部大脑5.1 为什么光靠上下文管理还不够Context Editing 和 Compaction 解决的是当前会话内的上下文问题。但有些信息是跨会话的用户的长期偏好、历史交互记录、领域知识。这些信息不该塞进每轮的上下文而应该存在外部按需检索。这就是 Memory Tool 的定位给 Agent 一个可读写的外部记忆库让它像人一样记住重要的事需要时想起来。Memory Tool 通常包含三个动作写入writeAgent 判断某条信息值得长期记住主动存进去。检索retrieveAgent 需要某类信息时按关键词或语义检索。更新update信息过时了Agent 主动修正。5.2 什么信息值得进 Memory不是所有信息都值得存。存太多检索时噪声大存太少Agent 又记不住。我的筛选标准是三条跨会话有用只在当前会话有用的不进 Memory。稳定不变或变化慢用户偏好、账号信息这类适合进实时价格、库存这类不适合。检索时能明确匹配信息要有清晰的键否则检索时找不到。举个例子用户说我一般用英文回复这是跨会话偏好进 Memory。用户说帮我查一下今天的订单这是当前任务不进 Memory。5.3 Memory 的检索策略语义还是关键词Memory 检索有两种主流方式关键词匹配和语义检索向量相似度。两者各有适用场景关键词匹配精确、快、可解释适合结构化信息用户 ID、订单号。语义检索模糊、慢、能处理同义表达适合非结构化信息偏好描述、历史摘要。实际项目里我一般两者结合先用关键词做粗筛再用语义做精排。这样既保证召回率又控制延迟。提示Memory 检索的结果不要全塞进上下文只取 top-3 到 top-5 条并且要带上这条记忆是什么时候写的、可信度多少的元信息让模型自己判断要不要采信。5.4 Memory 的写入时机主动还是被动Memory 写入有两种模式被动写入每轮对话结束后用一个独立的判断逻辑决定要不要写。主动写入Agent 在对话中显式调用memory_write工具。被动写入的好处是不依赖模型判断稳定坏处是可能写入噪声。主动写入的好处是精准坏处是模型可能忘记调用。我的实践是被动为主、主动为辅默认走被动写入用规则 小模型判断同时给 Agent 暴露memory_write工具遇到明确该记的信息时主动写。两者结合覆盖率和精准度都能兼顾。6. 三套机制怎么配合一个可落地的架构6.1 分层架构设计把 Context Editing、Compaction、Memory Tool 组合起来我一般用这样的分层L1 实时层当前轮的用户输入 最近 3 轮对话 当前任务状态。每轮都重新组装。L2 会话层本会话的历史摘要 关键决策日志。由 Compaction 维护。L3 长期层跨会话的用户偏好、领域知识。由 Memory Tool 维护。每轮请求时从 L1 取实时信息从 L2 取会话摘要从 L3 检索相关长期记忆三者拼装成最终上下文。这样上下文长度是有界的不会随对话轮次无限增长。6.2 各层的 token 预算分配预算分配很关键。我的经验值以 32K 窗口为例层级内容建议预算L1 实时层当前输入 近 3 轮 任务状态8KL2 会话层历史摘要 决策日志6KL3 长期层检索到的记忆4K系统提示角色 约束 工具定义4K输出预留模型生成空间10K这个分配不是死的要根据任务类型调。工具调用密集的任务系统提示工具定义占比要高纯对话任务L1 可以多给。6.3 一个完整的请求组装流程把流程串起来大概是这样的用户输入到达先做意图识别和实体抽取。从 L1 取最近 3 轮对话和当前任务状态。判断是否需要触发 Compaction检查 token 水位。从 L3 检索相关长期记忆按意图和实体。组装上下文系统提示 L2 摘要 L3 记忆 L1 实时。调用模型拿到输出。输出后处理更新任务状态、判断是否写 Memory、更新 L2 摘要。这个流程里第 3 步和第 7 步是维护性的不直接产生用户可见输出但决定了 Agent 能不能长期稳定运行。很多项目只做第 5 步和第 6 步忽略了维护步骤结果就是聊到 20 轮就失忆。6.4 并发场景下的上下文隔离AI Agent 怎么扛并发是个高频问题。多用户并发时上下文必须严格隔离每个会话有独立的 L1 和 L2L3 可以共享但要按用户 ID 分区。隔离的实现方式有两种物理隔离每个会话独立进程/容器和逻辑隔离共享进程用 session_id 区分。物理隔离简单但资源消耗大逻辑隔离省资源但要注意状态污染。我的建议是会话状态用逻辑隔离 不可变数据结构。每次组装上下文时从不可变的状态快照里读而不是直接改共享对象。这样即使并发也不会出现A 用户的上下文混进 B 用户的问题。7. 踩坑实录那些文档不会告诉你的细节7.1 压缩摘要的语义漂移前面提过一致性校验这里展开说一个具体案例。我们有个 Agent 处理退款流程压缩前状态是用户已确认退款金额 299 元等待执行。压缩后摘要写成用户咨询退款金额待确认。结果下一轮 Agent 又问了一遍金额用户直接炸了。问题出在压缩模型把已确认理解成了咨询。修复方法是压缩时把结构化状态单独抽出来不参与自然语言摘要。状态用 JSON 存摘要只描述对话过程。这样状态永远精确摘要只负责连贯性。7.2 Memory 检索的过度召回Memory 检索如果召回太多会污染上下文。我遇到过检索出 10 条记忆其中 7 条和当前任务无关结果模型被带偏回答里混进了不相关的信息。解决办法是加相关性阈值。语义检索的相似度分数低于某个值比如 0.75的直接丢弃宁可少召回也不要噪声。另外检索结果要按时间倒序新的记忆优先因为旧记忆可能已过时。7.3 工具定义膨胀Agent 工具多了之后工具定义本身就很占上下文。20 个工具每个定义 200 token就是 4000 token每轮都带。优化方法是工具分组 按需加载。把工具按场景分组当前场景只加载相关组的工具定义。比如客服场景只加载订单、退款、查询类工具不加载后台管理类工具。这样工具定义能压到 1000 token 以内。7.4 上下文重排的副作用重排把关键信息挪到两端虽然能提升注意力但会破坏对话的自然顺序。模型可能因为顺序错乱而产生逻辑错误。我的经验是只重排信息块不重排对话流。对话历史保持原序但可以在历史前后插入关键信息提醒块。这样既利用了位置优势又不破坏连贯性。8. 从能跑到跑得稳几个判断标准8.1 怎么判断上下文管理是否健康几个可观测的指标上下文长度曲线健康的曲线应该是锯齿状——缓慢上升压缩后回落整体有界。如果是一直上升的斜线说明 Compaction 没生效。信息召回准确率随机抽查若干轮看模型是否正确使用了历史信息。低于 90% 就要排查。压缩后错误率对比压缩前后几轮的错误率如果压缩后明显升高说明压缩策略有问题。8.2 不同任务类型的策略差异不是所有 Agent 都用同一套策略短任务 Agent单次问答、简单工具调用Context Editing 为主Compaction 和 Memory 可以简化。长对话 Agent客服、助手三套机制都要Compaction 频率要高。任务型 Agent多步骤执行状态快照是核心Memory 用于跨任务知识。先明确你的 Agent 属于哪类再决定投入多少精力在哪套机制上。8.3 一个容易忽略的点上下文也是安全边界上下文里可能包含敏感信息用户隐私、内部数据。做 Context Editing 和 Memory 时要同步做敏感信息过滤。比如工具返回里的身份证号、手机号进上下文前要脱敏Memory 里存的用户信息要加密存储。这一点在 Agent 安全里经常被忽略但一旦出事就是大事。我的做法是在上下文组装层加一个统一的脱敏过滤器所有进上下文的内容都过一遍规则 模型双重检查。聊到这里关于 Agent 上下文管理的核心机制基本讲完了。回到开头那个问题——聊到第 20 轮就失忆本质不是模型能力问题而是你有没有把管理历史的思维换成管理上下文。Context Editing 做每轮的减法Compaction 做阶段性的压缩Memory Tool 做跨会话的延伸三者配合才能让 Agent 在长对话里保持稳定。我自己在实际项目里最大的体会是上下文管理的投入产出比远高于换更大的模型。把这三套机制做扎实32K 窗口的 Agent 能跑得比 128K 窗口但不管上下文的 Agent 更稳。这个结论可能反直觉但你试过就知道了。