1. 长程 Agent 的上下文困境:为什么 2026 年顶会都在盯这件事
如果你最近在跑一个需要几十步甚至上百步才能完成任务的 Agent,大概率遇到过这种情况:前 20 步表现堪称完美,工具调用准确、推理链条清晰,但到了第 40 步之后,它开始忘记最初的目标,重复调用同一个工具,或者干脆把之前已经确认过的中间结论推翻重来。这不是模型变笨了,而是上下文管理出了问题。
长程 Agent(Long-Horizon Agent)指的是那些需要跨越大量交互轮次、持续维护状态、在多个子任务之间保持连贯性的智能体。它和那种一问一答的短任务 Agent 有本质区别——短任务里,你把所有信息塞进一次 prompt 就够了;长任务里,上下文会随着每一步的工具返回、观察结果、中间推理不断膨胀,很快就会撞上模型的上下文窗口上限,或者即使没撞上,也会因为信息密度被稀释而导致注意力涣散。
ICLR 和 ICML 这类会议近两年关于 Agent 的投稿量暴涨,其中相当一部分工作都指向同一个核心问题:如何在有限的上下文预算下,让 Agent 在长程任务中保持记忆的完整性、推理的连贯性和决策的一致性。围绕这个问题的解法大致分成了几条技术路线,我把它拆成下面这张表,方便你先建立一个全局认知:
| 技术路线 | 核心思路 | 典型代表方向 | 主要代价 |
|---|---|---|---|
| 上下文压缩 | 把历史信息压缩成更短的表示 | 摘要式记忆、隐状态压缩 | 信息有损,可能丢关键细节 |
| 检索增强记忆 | 把历史存到外部,按需检索 | 向量库、结构化记忆 | 检索质量决定上限,延迟增加 |
| 分层记忆架构 | 短期/长期/永久分层管理 | 多级缓存、记忆固化 | 架构复杂,层间同步难 |
| 状态外置与重放 | 把状态放到环境里,按需重建 | 检查点、轨迹重放 | 重建成本高,依赖环境可复现 |
| 注意力与位置优化 | 不改内容,改信息摆放方式 | 位置编码、注意力掩码 | 受模型底层能力限制 |
这张表不是让你选一个,而是让你意识到:长程上下文管理从来不是单一技术能解决的,它是一套组合拳。下面我会逐条拆开讲,每一条都配上我实际跑 Agent 项目时踩过的坑和验证过的做法。
先说一个反直觉的结论:上下文窗口变大,并不等于长程问题被解决了。现在动辄 128K、200K 甚至更长的窗口,很多人第一反应是"那我全塞进去不就行了"。实测下来,当有效信息被淹没在几万 token 的噪声里时,模型的召回率会明显下降,尤其是那些出现在上下文中段的关键约束,最容易被忽略。这就是所谓的"lost in the middle"现象。所以窗口大只是给了你更多预算,怎么花这个预算才是真本事。
2. 上下文压缩:摘要、隐状态与选择性遗忘的取舍
2.1 摘要式记忆为什么容易"越摘越糊"
最直觉的做法是让模型定期把历史对话总结成一段摘要,然后用摘要替换原始历史。这个思路在早期 Agent 项目里非常流行,实现也简单:每 N 轮触发一次总结,把之前的消息列表压缩成一条 system 或 assistant 消息。
但我实测下来,纯摘要式记忆有个致命问题:摘要会丢失那些当时看起来不重要、后面却关键的细节。比如 Agent 在第 5 步随口确认了一个参数"用户偏好用 JSON 格式输出",总结时这句话很可能被合并成"确认了输出偏好",等到第 60 步真正要生成输出时,具体是 JSON 还是 YAML 已经无从考证。
解决办法是分层摘要:不要把所有历史压成一段,而是保留一个"关键事实清单"(key facts ledger),这个清单只增不减,专门记录那些不可逆的决策和约束。摘要负责压缩叙事性内容,清单负责锁定硬约束。两者分开维护,互不干扰。
# 关键事实清单的维护逻辑(伪代码) class KeyFactsLedger: def __init__(self): self.facts = [] # 不可逆决策、硬约束、用户明确指令 def maybe_add(self, message, llm): # 只提取"约束类"信息,不提取叙事 prompt = f"从以下内容中提取不可逆的决策或硬约束,没有则返回空:\n{message}" extracted = llm(prompt) if extracted and extracted not in self.facts: self.facts.append(extracted) def render(self): return "【必须遵守的事实】\n" + "\n".join(f"- {f}" for f in self.facts)这个清单在每次构造 prompt 时都完整带上,因为它通常很短,几十条也就几百 token,但它是长程任务不跑偏的底线。
2.2 隐状态压缩:把历史编码进向量而不是文字
另一条路是不用文字摘要,而是把历史信息编码成连续的隐状态向量,比如通过一个专门的压缩模块把多轮对话映射成固定长度的表示。这类方法在论文里很漂亮,理论上信息损失更小,因为它不受自然语言的离散瓶颈限制。
但工程上要小心:隐状态压缩对训练和推理的一致性要求极高。你训练时用的压缩器和推理时用的如果不是同一套,或者模型版本有细微差异,压缩出来的向量分布就会漂移,导致 Agent 行为不稳定。我在一个项目里用过类似方案,训练环境跑得好好的,换到线上推理后 Agent 开始出现莫名其妙的工具选择,排查了两天才定位到是压缩器版本不一致。
所以如果你要走这条路,务必把压缩器和主模型绑定版本管理,任何一方升级都要重新验证长程任务的表现,不能只看短任务的指标。
2.3 选择性遗忘:主动丢弃比被动压缩更有效
还有一个容易被忽视的角度:不是所有历史都值得保留。工具返回的大段原始数据(比如一次网页抓取的完整 HTML)、失败的尝试、已经被证伪的中间结论,这些留着只会污染上下文。
我的做法是给每条消息打一个"保留价值"标签,分三档:
- 必须保留:用户指令、关键事实、最终结论
- 条件保留:中间推理(如果后续可能被引用)、工具调用的参数
- 可丢弃:原始工具返回的大段文本、失败尝试的详细过程、重复的确认信息
可丢弃的内容在进入下一轮之前就被替换成一句极简的占位说明,比如"(第 12 步:抓取了页面,提取到 3 个候选链接,详见检查点)"。真正的原始数据存到外部,需要时再取。这样上下文里留下的都是高密度信息,模型的注意力不会被浪费。
提示:选择性遗忘的阈值不要设得太激进。我一开始把"条件保留"也大量丢弃,结果 Agent 在需要回溯推理链时频繁要求重新执行已经做过的步骤,反而更慢。后来把中间推理的保留窗口设为最近 15 步,效果明显好转。
3. 检索增强记忆:向量库不是万能药,结构化才是关键
3.1 纯向量检索在 Agent 场景下的三个硬伤
说到外部记忆,很多人第一反应就是上向量数据库,把每轮对话 embed 一下存进去,需要时做相似度检索。这个方案在知识问答场景很成熟,但直接搬到长程 Agent 上会暴露三个问题。
第一,Agent 的记忆查询往往不是语义相似,而是结构化定位。比如 Agent 想回忆"我第 30 步调用那个 API 时用的超时参数是多少",这是一个精确的字段查询,不是"找语义相近的段落"。向量检索很可能返回一堆语义相关但字段不对的内容。
第二,时间顺序在 Agent 记忆里至关重要。向量检索默认是忽略时序的,但 Agent 经常需要"最近一次""上一次成功的那次"这类带时序的查询,纯相似度排序满足不了。
第三,检索结果的噪声会直接误导决策。知识问答里检索错一条顶多答案不准,Agent 里检索错一条可能导致它基于错误记忆做出不可逆的操作。
3.2 结构化记忆表:把 Agent 的记忆当数据库来设计
我的做法是把 Agent 记忆拆成几张结构化的表,向量检索只作为其中一张表的补充手段:
| 记忆表 | 存储内容 | 查询方式 | 更新时机 |
|---|---|---|---|
| 事实表 | 关键约束、用户偏好 | 精确匹配 + 标签过滤 | 实时写入 |
| 动作表 | 每步工具调用及结果摘要 | 时序查询 + 状态过滤 | 每步写入 |
| 实体表 | 任务中出现的对象及其属性 | 主键查询 | 实体首次出现时 |
| 语义表 | 非结构化的观察、推理 | 向量检索 | 批量写入 |
关键在于动作表。它记录了 Agent 每一步做了什么、结果如何、状态是什么。当 Agent 需要回溯时,它查的是这张表,而不是去翻原始对话。这张表天然带时序,天然结构化,查询又快又准。
# 动作表的典型结构 action_record = { "step": 30, "action": "call_api", "params": {"endpoint": "/v1/search", "timeout": 30, "retry": 2}, "result_summary": "返回 5 条结果,已提取标题", "status": "success", "timestamp": "2026-01-15T10:23:00Z" }有了这张表,Agent 在第 60 步想知道"之前那个 API 超时设的多少",直接按 action 类型查最近一条成功记录就行,根本不需要向量检索。
3.3 检索时机比检索质量更影响整体表现
很多人把精力全花在提升检索准确率上,但我实测发现,什么时候触发检索对长程任务的影响更大。如果 Agent 每一步都去检索一遍记忆,不仅慢,还会引入大量无关信息干扰当前决策。
我的策略是事件驱动检索:只在特定事件发生时触发记忆查询,比如:
- 即将执行一个不可逆操作前(查有没有相关约束)
- 连续两次工具调用失败后(查历史上类似情况怎么处理的)
- 进入一个新的子任务时(查这个子任务相关的实体和事实)
- 模型自己显式请求回忆时
这样检索次数大幅下降,但每次检索都是有针对性的,信噪比高得多。
4. 分层记忆架构:短期、长期、永久三层怎么落地
4.1 三层的职责边界必须清晰
Agent 记忆分层是老生常谈,但很多实现失败在层与层之间的职责没划清。我见过一个项目,短期记忆和长期记忆都在存对话历史,结果两边数据不一致,Agent 时而记得时而忘记,排查起来极其痛苦。
我的划分标准是这样的:
- 短期记忆(工作记忆):当前子任务的上下文,容量小(比如最近 10-20 步),全量保留,任务切换时清空或归档。
- 长期记忆:跨子任务的任务级状态,包括关键事实、已完成步骤、待办事项,容量中等,结构化存储,全程可查。
- 永久记忆:跨任务的知识,比如用户长期偏好、领域常识、历史成功模式,容量大,需要显式写入和检索。
三层的读写规则要明确:短期记忆每步都写,长期记忆在子任务边界写,永久记忆只在明确确认有价值时写。读的时候,短期记忆直接进 prompt,长期记忆按需检索,永久记忆通过检索或预加载。
4.2 层间同步:最容易出 bug 的地方
层间同步是分层架构的命门。举个真实例子:Agent 在短期记忆里确认了"用户要求输出中文",但这条信息没有及时同步到长期记忆,结果子任务切换后短期记忆被清空,Agent 又开始输出英文。
我的解法是关键事实双写:任何被标记为"关键事实"的信息,在写入短期记忆的同时,立即同步写入长期记忆的事实表。这样即使短期记忆被清空,长期记忆里还有备份。同步是同步操作,不搞异步,避免时序问题。
def write_memory(content, level, is_key_fact=False): if level == "short": short_term.append(content) if is_key_fact: long_term.facts.append(content) # 双写 elif level == "long": long_term.append(content) elif level == "permanent": permanent.append(content)4.3 记忆固化:什么时候把短期升级为长期
不是所有短期记忆都值得升级为长期。升级太频繁,长期记忆会被噪声淹没;升级太少,关键信息会随短期记忆一起丢失。
我用的判断标准是**"跨子任务相关性"**:如果一条信息在当前子任务结束后,下一个子任务还可能用到,就升级。具体来说,满足以下任一条件就升级:
- 是用户明确指令或约束
- 是某个实体的关键属性(比如"这个 API 的 rate limit 是 100/min")
- 是已经确认的中间结论(后续推理会依赖)
- 是失败教训(避免重复踩坑)
这套标准跑下来,长期记忆的增长是可控的,不会爆炸。
5. 状态外置与轨迹重放:把记忆交给环境而不是模型
5.1 为什么"让模型记住一切"是错误的方向
前面讲的都是怎么帮模型更好地记住东西,但还有一条完全相反的思路:干脆不让模型记,把状态放到环境里,需要时重建。
这个思路的洞察是:模型记忆本质上是不可靠的,与其花大力气优化记忆,不如把状态外置到确定性的存储里,模型只负责决策,不负责记忆。每一步的状态都持久化,模型需要什么就现查现用。
这在工程上有个巨大好处:可复现、可调试。当 Agent 出错时,你可以精确地回到某一步,查看当时的状态,重放决策过程。而如果状态全在模型的上下文里,出错后你只能看到一团模糊的历史,很难定位。
5.2 检查点设计:存什么、多久存一次
状态外置的核心是检查点(checkpoint)。检查点要存的东西包括:
- 当前任务目标(原始指令)
- 已完成步骤的摘要列表
- 当前环境状态(文件、数据库、外部系统的快照或引用)
- 待办事项
- 关键事实清单
存频率上,我的经验是每个不可逆操作后必存,其他步骤按固定间隔存。不可逆操作包括写文件、发请求、修改数据库等,这些一旦执行就无法回退,必须在执行前存一个检查点,执行后再存一个,这样出问题能精确回滚。
def execute_with_checkpoint(action): save_checkpoint(f"before_{action.id}") result = action.run() save_checkpoint(f"after_{action.id}", result=result) return result5.3 轨迹重放:调试长程 Agent 的杀手锏
轨迹重放是状态外置带来的最大红利。当 Agent 在长程任务中出错,你可以:
- 找到出错的那一步
- 加载它之前的检查点
- 用相同的输入重放这一步
- 对比实际输出和期望输出
- 定位是记忆问题、推理问题还是工具问题
我靠这套方法定位过好几个隐蔽 bug,比如某个工具在特定参数下返回格式不一致,导致 Agent 后续解析失败。如果没有重放能力,这种问题在长程任务里几乎不可能复现和定位。
注意:轨迹重放要求环境是可复现的。如果 Agent 依赖的外部系统状态会变(比如实时数据),重放时要么用快照,要么接受一定的不确定性。设计时要把"可复现"作为环境的一个硬指标。
6. 注意力与位置优化:不改内容,改摆放方式
6.1 信息摆放位置对长程表现的影响
前面讲的都是怎么改内容,但还有一类方法不改内容,只改信息在上下文里的摆放方式。这背后的原理是:模型对上下文不同位置的注意力分布是不均匀的,开头和结尾的召回率通常高于中间。
所以一个简单但有效的技巧是:把最关键的约束放在上下文的开头和结尾各一份。开头放一份让模型一开始就建立约束意识,结尾放一份让模型在生成前最后确认。中间放详细的历史和推理。这个"首尾呼应"的做法,我在多个长程任务上验证过,能明显降低 Agent 违反约束的概率。
6.2 注意力掩码与位置编码的工程应用
更进阶的做法是用注意力掩码或位置编码来引导模型关注特定区域。比如给关键事实打上特殊的位置标记,让模型的位置编码能区分"这是约束"和"这是普通历史"。
但这类方法对模型底层有要求,不是所有模型都支持自定义注意力模式。如果你用的是闭源 API,基本没法改;如果是开源模型自己部署,可以尝试。我的建议是先用简单的位置摆放技巧,收益不够再考虑改底层,因为改底层的维护成本很高,模型一升级可能就失效。
6.3 上下文预算分配:给每类信息定配额
最后一个实操性很强的技巧是给上下文预算定配额。不要让它自然增长到满,而是提前分配:
- 系统指令和约束:10%
- 关键事实清单:10%
- 最近 N 步详细历史:40%
- 检索到的相关记忆:20%
- 当前任务描述和待办:20%
每类信息超出配额就触发压缩或丢弃。这样能保证上下文里各类信息都有位置,不会出现某一类(比如工具返回)把其他类挤没的情况。配额比例可以根据任务特点调整,但一定要有配额这个概念,否则上下文管理就是失控的。
7. 我在长程 Agent 项目里踩过的几个真实坑
7.1 摘要触发时机不对导致关键信息丢失
早期我用固定轮次触发摘要,每 20 轮总结一次。结果有一次 Agent 在第 18 轮确认了一个关键参数,第 20 轮总结时这个参数被合并进了一句模糊的描述,第 40 轮需要用时已经找不回来了。
后来改成事件触发 + 轮次兜底:关键事实随时写入清单(不依赖摘要),摘要只在上下文接近预算上限时触发,且摘要时明确要求保留所有数字、标识符和约束。这个改动之后,关键信息丢失的问题基本消失。
7.2 检索增强引入的错误记忆
有一次 Agent 在检索历史时,检索到了一条语义相似但实际属于另一个任务的记忆,导致它把两个任务的约束混在一起,行为变得莫名其妙。排查后发现是向量库没有做任务隔离,所有任务的记忆混在一个索引里。
修复方法是给记忆加任务命名空间,检索时强制过滤当前任务。同时给每条记忆加上时间戳和任务 ID,检索结果按相关性和时效性综合排序,而不是只看相似度。
7.3 分层记忆的同步延迟
前面提过层间同步的问题,我实际遇到的是异步同步导致的延迟。当时为了性能,长期记忆的写入用了异步队列,结果 Agent 在写入后立即读取,读到的还是旧数据。这种 bug 特别隐蔽,因为大部分时候队列处理很快,偶尔慢一次就出问题。
改成同步写入后性能确实下降了一点,但稳定性大幅提升。在记忆这种基础能力上,稳定性优先于性能,这是我用血泪换来的教训。
7.4 上下文配额被工具返回撑爆
有一次接了一个返回数据量很大的工具,每次调用返回几千 token,几步下来上下文就被撑满了,导致关键事实被挤出去。后来给工具返回单独设了配额,超出部分自动截断并存入外部存储,上下文里只留摘要和引用。这个改动让 Agent 在长任务里的稳定性上了一个台阶。
8. 面向 2026 的技术趋势与选型建议
从 ICLR、ICML 近期的投稿方向看,长程 Agent 上下文管理有几个明显的趋势。一是记忆的结构化程度越来越高,纯向量的方案在退潮,混合结构化存储成为主流。二是状态外置和可复现性被提到前所未有的高度,因为长程任务的调试需求太强烈了。三是上下文管理开始和推理过程耦合,不再是独立的预处理步骤,而是嵌入到 Agent 的每一步决策里。
选型上,我的建议是按任务长度分档:
- 20 步以内:选择性遗忘 + 关键事实清单就够了,不用上复杂架构。
- 20 到 100 步:分层记忆 + 结构化动作表 + 事件驱动检索,这套组合能覆盖大部分场景。
- 100 步以上:必须上状态外置和检查点,否则调试和维护成本会失控。
最后分享一个我一直在用的自检清单,每次 Agent 长程任务出问题,我就按这个顺序排查:先看关键事实清单是否完整,再看动作表是否有断档,然后看检索是否引入了错误记忆,最后看上下文配额是否被某一类信息撑爆。按这个顺序走,八成问题能在十分钟内定位。
这套方法不是从论文里抄的,是实打实跑项目跑出来的。顶会论文给的是方向,真正落地还得靠这些脏活累活的细节。