先给结论:把 AI Agent 的上下文窗口从 8k 扩到 128k,大概率不是让 Agent 变聪明,而是让它在更长的对话里忘得更体面。真正决定一个 Agent 能不能稳定处理长期任务、跨会话记住用户偏好、在信息冲突时自我修正的,不是窗口有多宽,而是记忆系统架构设计。这些年我帮团队做过几版 Agent,从直接怼长文本,到接向量库,再到完整的记忆分层和遗忘机制,最深的感受是:上下文窗口只是一张工作台,不是仓库。工作台再大,摆不下一个季度的高价值信息。这篇文章就围绕“AI Agent 记忆系统架构设计全景”展开,讲清楚为什么更大的上下文窗口救不了你的 Agent,以及一套可以落地的分层记忆方案应该怎么搭、怎么避坑。
1. 为什么“更大的上下文窗口”是治标不治本
1.1 先搞清楚上下文窗口在 Agent 里的真实位置
很多人会把“上下文窗口很大”等同于“模型记住了很多”,这是行业里最常见的误解之一。上下文窗口的本质是模型每次推理时能看到的输入 token 总量,它相当于一个短期缓冲区,或者叫工作记忆。窗口里的内容只是被“临时塞进”了模型的注意力范围内,模型处理完这一次生成之后,这些 token 不会自动沉淀成任何长期状态。
我见过不少团队的做法是:把所有历史会话记录、用户资料、业务知识一股脑拼进 prompt,然后寄希望于大窗口能覆盖一切。这在 demo 阶段确实行得通,但你一旦让 Agent 跑在真实业务里,连续服务一个用户超过几十轮,或者隔几天再来一次,就会发现窗口机制有三个绕不开的短板:成本爆炸、注意力稀释、信息无法持久化。这也是为什么我说它治标不治本,因为窗口增加只是扩容了临时工作台,并没有引入“长期存储—索引—检索—更新”这条记忆通路。
1.2 注意力稀疏和“丢失在中间”现象
长上下文模型有一个被论文反复验证的问题:当输入序列拉长之后,模型对中间位置信息的注意力会明显衰减,业界叫“lost in the middle”。也就是说,即便你把 128k 历史全文都塞进去,模型真正稳定关注的可能还是开头和最近几段,中间那些关键约束、用户偏好、历史结论很容易被稀释。
我在实际项目里遇到过很典型的一次:客服 Agent 要记住用户之前说过“不要电话联系,只发邮件”,同时又记着另一个较早期的诉求“要开发票”。把 60 轮对话原文塞进一个 128k 窗口后,它在第 70 轮时莫名其妙打电话提醒用户,而不是发邮件。查下去才发现,最早的这条偏好被淹没在长文本中间位置,模型不是不知道,而是没有分配到足够注意力权重。这类问题不是把窗口再放大就能解决的,因为你放大窗口的同时,需要被注意的信息相对比例反而更低了。
1.3 成本与算力不会陪你“线性增长”
上下文窗口的扩容代价是高成本和高延迟,这一点做过线上服务的团队应该都有体感。按主流模型输入价格粗略算,一个 4k 窗口的输入如果花 0.015 美元,到了 128k 窗口,单次请求成本会增长三四十倍。而 Agent 任务往往不是单次调用,是循环决策:Agent 每执行一步工具调用、每观察一次结果,都要重新发送完整上下文。
假设一个 Agent 完成一次真实任务需要 10 次模型调用,每次调用都带 80k 历史 token,那一次任务光输入就要消耗 800k token。如果这个 Agent 每天服务 1000 个用户,成本很容易变成无法接受的数字。更关键的是,其中绝大部分历史 token 对当前决策根本没用。所以从工程角度看,长期记忆必须做成“按需加载”,而不是“全量常驻”。
1.4 长上下文评测好,不代表 Agent 任务好
市面上有很多长文本评测集,比如 LongBench 这类基准,动辄几十万字的小说、论文摘要、位置问答。但这类任务和 AI Agent 的真实场景差异非常大:长文本评测主要测“从长材料里找答案”,而 Agent 的难点是“持续利用分散在历史中的信息做多步决策”。前者是检索题,后者是状态管理题。
我见过一个团队,模型升级到 200k 窗口后,长文本阅读理解分数涨了不少,但一上真实的订票 Agent,还是会在第三天忘记用户“只选靠窗座位”的偏好,因为这条偏好藏在第 2 轮,而最近 50 轮都在讨论航班时间。长上下文能力评测和 Agent 稳定性的相关性,远比想象中低,拿评测分数来论证“不用做记忆系统”是站不住脚的。
2. Agent 记忆到底在记什么:一套分层记忆模型
2.1 借用认知科学:四种记忆类型
做 AI Agent 记忆系统架构设计时,我建议先忘掉“把所有东西存到一个仓库”的想法,转而把记忆按生命周期和信息类型分成几类。认知科学里有个很朴素的分类法,放到 Agent 上正好对应四种需求:
- 工作记忆(Working Memory):对应上下文窗口,保存当前任务正在进行的事务、临时变量、正在讨论的问题。
- 情景记忆(Episodic Memory):记录发生过的事件,比如“上周二用户投诉过物流慢”“上次这个订单最后退款处理了”。
- 语义记忆(Semantic Memory):保存从经历中提炼出来的知识、偏好和事实,比如“用户不喜欢电话联系”“公司退款政策是 7 天内免运费”。
- 程序记忆(Procedural Memory):保存技能和流程,比如“遇到退货请求先核查订单状态,再判断是否超过 7 天”。
这种分层不是为了学术美观,而是为了匹配现实中的工程问题:数据量不同、更新频率不同、检索方式也不同。如果把用户的所有对话原话都堆在一起,既不能高效检索,也没法在用户纠正之后定点更新某一条事实。分层记忆的价值是让每一类信息的存取逻辑都能独立优化。
2.2 为什么不能只建一张大表
“做大表”是很多初版 Agent 记忆系统的通病。把每轮对话、每个实体、每条偏好都写入一张记忆表,然后用关键词去搜,短期跑一跑没问题,等数据量上来就会出现几个现象:检索召回大量无关内容、关键字命中但语义不相关、用户纠正之后旧数据和新数据同时存在,模型不知道该信哪个。
更实际的问题是不同的记忆生命周期不同。情景记忆格式统一,适合存时间戳和事件摘要;语义记忆需要去重和冲突处理;程序记忆则需要抽成可复用的动作序列。如果把这些东西混在一张表里,时间衰减、事实更新、摘要压缩这些策略就都很难实现。理论上你能靠一个极宽的表 + 复杂过滤实现同样效果,但工程复杂度会严重失控,而且排查问题极难。
2.3 一个可直接落地的记忆 Schema 示例
我在项目中常用的一种最小记忆结构是四张表,关系型数据库 + 向量库混合。这里给你一个可以直接参考的简化设计:
| 表 | 保存内容 | 关键字段 | 存储方式 |
|---|---|---|---|
| episodes | 每一轮/每一段会话的摘要事件 | id, user_id, ts, summary, embedding | 摘要进向量库,元数据进 SQLite |
| memories | 从事件中提炼的用户偏好、事实、约束 | id, user_id, content, memory_type, importance, created_at, last_access_at | 向量库 + 关系表双写 |
| entities | 业务实体和关系 | id, entity_type, name, properties, relations | 关系表(复杂关系可用图数据库) |
| skills | 可复用的操作流程 | id, trigger, steps, version | 普通表 / JSON 字段 |
这套结构的核心思想是:情景记忆负责“记流水账”,语义记忆负责“提炼规则”,实体表负责“人和物的关联信息”,技能表负责“怎么做”。每一次新对话进来,先写 episode 摘要,再做记忆提取,再更新或新增实体/事实。检索时再根据当前会话的 query 去不同表里拉取相关片段,拼成一个“记忆上下文块”,而不是把所有历史全部倒进 prompt。这个设计无论是接 LangChain 还是自己写调度,都比较容易实现。
3. 外部记忆系统的核心部件:写入、索引、检索、遗忘
3.1 写入:不是存原文,而是抽取结构化记忆
很多第一次做记忆系统的团队,会把记忆写入做成了“聊天记录备份”。比如把每轮对话原样存进向量库,检索时直接搜原文。这个做法短时间有效,但后面会出现两个大问题:第一,原文里大量客套话、噪声会被检索出来;第二,用户随口说一句“我一般晚上有空”和正式申明“请不要在工作时间联系我”,在原始文本层面是完全不同粒度的信息,向量检索很难把它们都准确归类到“用户时间偏好”这个记忆点。
所以我强烈建议在写入环节加一个“记忆抽取”步骤。说白了就是让 Agent 在每次对话结束后,把对话内容提炼成几条候选记忆,并判断该新增、更新还是删除。形式上可以让模型输出一个 JSON,包含操作类型、记忆内容、重要度、过期时间等字段。这一步会多花一些 token 和延迟,但换来的是后续检索效率和事实更新能力。做抽取时有两个小技巧:一是给模型指定抽取目标,比如“只抽取用户明确表达的偏好和承诺,不要抽取日常寒暄”;二是要做一致性去重,防止同一条事实被反复写进去。
3.2 索引与元数据:向量检索不是银弹
真正做过基于向量库的记忆系统的人,都知道“embedding + 余弦相似度”只是检索的一个底座,它扛不住精确过滤和历史筛选。举个例子,一个用户有 3000 条记忆,其中有 200 条是关于“退款”的语义描述,单纯向量搜索会把这 200 条里相似度最高的 5 条全捞出来,但它们可能分属完全不同的订单和时间段。
所以我的做法是:向量检索负责“语义召回”,关系型数据库负责“过滤和聚合”。每条记忆写入时都带上 user_id、session_id、created_at、memory_type、importance 这些元数据,检索时先按业务条件过滤,比如只取 user_id=xx、memory_type=preference、近 30 天创建,再做向量相似度排序。这一套混合检索比纯向量库稳定很多。另外一个容易被忽略的点是:同一个记忆内容可能需要支持多种查询方式。用户说“我讨厌接电话”是一条偏好,用户投诉“上周客服给他打了三次电话”是一个事件,两者的查询方式完全不同,用同一套 embedding 去检索经常会互相干扰。
3.3 检索:时间衰减、重要度和上下文组装
检索阶段的核心不是“查到最相似的一条”,而是“组装一段让模型可以做出正确决策的记忆上下文”。我用三个维度给记忆打分:语义相似性、时间新鲜度、重要度。语义相似性靠向量相似度,时间新鲜度用一个简单的衰减函数,比如 score = base_score * exp(-days / half_life),重要度可以就按 1-5 分级,由抽取模型判断。
把三个分数加权求和之后,再决定最终选哪些记忆进入上下文。关于权重,我的经验是语义相似度占大头,时间衰减次之,重要度作为修正。还有一个实操细节:不要只取 top_k,而是分成两组,一组是“当前任务高度相关”的记忆,另一组是“最近频繁使用但可能不直接相关”的辅助记忆,这样模型既能抓住直接线索,又能保有必要的连续性。组装时把记忆按“事实—约束—历史事件—工具技能”四个区块排列,而不是单纯把一堆向量结果拼接起来,实测推理稳定性会好很多。
3.4 遗忘与整合:记忆系统不膨胀的关键
“遗忘”这个词在记忆系统里听着像功能缺陷,其实它是防止 Agent 记忆库被垃圾信息淹没的核心机制。人类的记忆也不是无限存储所有琐事,而是定期整合、丢弃和抽象。AI Agent 一样需要这个步骤。
我在自己的系统里做了两层处理:一层是低价值记忆的软过期。每条记忆写入时如果重要度为 1-2,且超过 30 天没有访问,就会先被标记为“待归档”,不再参与常规检索。另一层是摘要整合:对同一个主题下超过一定数量的记忆,比如 30 条关于订单处理的旧记录,用一次 LLM 调用生成一篇结构化摘要,替换掉这些原始记录。这个操作有点像把零碎的日记写成年报,既压缩了存储,也保住了高层的规律性信息。
你可能会担心摘要整合会丢失细节。这个担心是对的,所以摘要文档里我会留一个“原始事件时间范围”字段,当检索发现摘要不足以回答问题时,再把对应时间范围内的原始 episodes 捞出来做二次阅读。这种两级档案设计,能平衡压缩率和信息完整度。遗忘机制不是一次做完的,需要定一个后台任务定期执行,我一般设置每天凌晨跑一次,避免影响线上响应。
4. 三条主流实现路线与踩坑实录
4.1 路线 A:MemGPT/Letta 式上下文分页管理
MemGPT 的思路是把 LLM 的上下文窗口当成主存,把外部持久化存储当成磁盘,让模型自己决定什么时候把某些历史“换出”到磁盘,什么时候从磁盘“换入”到窗口。这套设计在概念上很优雅,尤其适合单个 Agent 需要连续运行很久、但注意力有限制的场景,相当于给模型配了一个自动内存管理器。
但实际踩下来有几个坑。第一个是模型自己管理页面置换,稳定性不稳定。模型偶尔会出现“把重要约束换出去了,却把无关历史保留”的乌龙决策,因为它们对注意力的自我感知并不精确。第二个是调试困难,当 Agent 行为异常时,很难判断是“该换入的记忆没换入”还是“换入后模型没读懂”。这套方案适合喜欢折腾、愿意接受不确定性、有足够精力做行为分析和 prompt 调优的团队,不适合指望开箱即用的业务方。
4.2 路线 B:Mem0 / LangMem 式记忆提取管理
Mem0 和 LangMem 这类框架走的是另一条路线:不依赖模型自己管内存,而是在每次对话之后用额外的抽取步骤,将对话中的高价值信息提炼成记忆,存到 SQLite、向量库或图数据库,下一次对话时按用户上下文检索并注入。这套方案最大的优势是可控性强,记忆写入、更新、检索都是显式的,你可以单独观测每一环的质量。
我实际用它落地过类似系统的简化版,踩过最深的坑是“记忆污染”。假如用户最开始说“我喜欢在京东买东西”,过一个星期又改成“我最近主要在拼多多买”,如果记忆系统只做追加不做更新,模型检索时就会同时拿到两条冲突记忆,然后随机选择一个执行。解决办法是在抽取阶段就要识别“用户纠正了先前的偏好”这一类操作,主动把旧记忆置为失效或删除,只保留最新版本。这一点最好不要依赖模型读上下文时自动发现,而是在检索结果注入时做一次冲突检测,一旦发现同类型同属性但值不同的记忆,立刻把旧的那条降权。
4.3 路线 C:知识图谱式记忆与关系推理
知识图谱式记忆适合信息之间强关联、需要多跳推理的场景,比如企业知识库、法律咨询 Agent、客户关系管理。你要记录“用户 A 是公司 B 的采购负责人,公司 B 曾经因为发货延迟投诉过产品 C”,这种复杂关系靠向量库很难表达,Graphiti 和 Zep 这类框架会直接把记忆建模成节点和边。
但这条路的成本最高。建图需要做实体抽取、关系抽取、去重、消歧,每一步都可能出错,而且图查询本身对开发者的技术栈要求更高。我见过不少团队辛辛苦苦建了图谱,最后发现 Agent 回答问题时根本用不上,因为检索入口太复杂。我的建议是:如果当前场景用“向量库 + 关系表”已经能解决,就暂时不要上图谱。知识图谱是记忆架构后期的进阶选择,不该是第一版的技术栈。
4.4 选型实操对照
下面这张表是我在项目里用来做技术选型的一个简版判断表,直接照着看就够:
| 判断维度 | 上下文分页(Letta) | 记忆提取(Mem0 式) | 知识图谱(Graphiti 式) |
|---|---|---|---|
| 适合场景 | 单 Agent 超长会话 | 多数业务 Agent | 强关系推理场景 |
| 实现复杂度 | 中 | 低-中 | 高 |
| 可控性 | 中 | 高 | 中 |
| 记忆更新与纠错 | 较弱 | 强 | 中 |
| 调试难度 | 高 | 低 | 高 |
| 我推荐的优先级 | 有精力再玩 | 第一版优先 | 后期按需引入 |
5. 最小改造方案:给已有 Agent 接上外部记忆
5.1 第一步:先用 Prompt 模拟记忆写出
如果不想一上来就接一堆框架,可以先做最小闭环:在 Agent 的系统提示词里增加一段“记忆管理指令”,要求模型在每次生成完正式回复后,额外输出一个 JSON 块,里面包含需要新增、更新、删除的记忆。比如这样:
请在你每次回复之后,输出一个 memory_commands 字段: { "memory_commands": [ {"op": "create", "type": "preference", "content": "用户更喜欢邮件联系", "importance": 4, "metadata": {}}, {"op": "update", "type": "preference", "target": "old_content_hash", "content": "用户更改为优先微信联系", "importance": 4} ] }这一步的关键作用是让你先验证“记忆提取”这个环节是否靠谱。把模型输出的 JSON 解析后先不做任何存储,只打印到日志,连续跑一两天,观察它抽取的内容是否正确、有没有幻觉、有没有漏掉关键约束。很多人会跳过这一步直接上数据库,结果后面所有问题都分不清是提取的错还是检索的错。先有了可靠的提取,再谈存储和检索。
5.2 第二步:把记忆落库并按需检索
等到你觉得提取质量可以接受,再把它落到本地存储。最简单的组合是 SQLite 存结构化字段,再加一个轻量向量库存 embedding,或者干脆本地跑一个嵌入模型 + FAISS。写入时注意每条记忆都带上 user_id 和 session_id,这是多用户 Agent 绝对不能省的字段。
检索时构造一个“记忆上下文块”注入到系统提示词里,不要直接拼原文。我建议在注入前加一条指令:告诉模型“以下内容是从记忆库中检索到的历史信息,可能与当前对话不完全一致,请优先相信用户在当前对话中的最新表达”。这句话能显著降低错误记忆和用户最新意图之间的冲突率。检索注入模板不需要花哨,但顺序要固定:先全局约束,再用户偏好,再相关历史事件,最后是业务知识。
5.3 第三步:加一个定时遗忘与整合任务
就算记忆库只有几百条,没过多久也会出现重复、过期和低价值内容。如果你前面已经跑通了“抽取—存储—检索”,那么接下来唯一能让系统长期维持稳定的就是遗忘与整合任务。可以用一个每天凌晨两点运行的 cron,对所有超过 30 天且重要度低于 3 的记忆做归档,同时对同一个“主题标签”下大于 20 条的记忆触发一次摘要整合。
这个过程里最容易犯的错是拿线上业务库直接跑批量删除。我吃过亏,因为数据量上去了,一次 delete 操作会把还在被引用的记忆也清掉,导致第二天 Agent 行为突然失忆。正确做法是加一个 archived 标记字段,先把过期记忆置为非活跃状态,观察几天确认没有副作用后再物理清理。这个“软删除”习惯在记忆系统里特别重要。
6. 我最终会怎么选:一份实战决策清单
6.1 什么时候坚决不升上下文,先做外部记忆
以下情况我基本不会靠扩窗口去硬扛:Agent 需要长期服务同一批用户,用户的历史偏好和约束会影响每一次决策;Agent 会跨会话恢复状态,今天中断的流程明天还要继续;业务对成本敏感,而请求量很大;用户的反馈需要被记忆实时更新,比如“我改主意了,以后不要再发短信”。只要命中两条以上,就说明你需要的是一个记忆系统,而不是更大的窗口。
6.2 什么时候确实可以依赖更大窗口
外部记忆也不是银弹。如果 Agent 本身是单次任务型,比如“读一份合同提取重点”“分析一篇长文”,上下文窗口大一点确实方便,因为这时候信息都在当前输入里,不需要跨会话追踪。还有代码补全、长篇翻译这类场景,窗口带来的是直接收益。另外,如果团队完全没有时间维护记忆链路,只有一个低频内部工具,那直接用大窗口临时撑住,也比上一套没人维护的记忆系统更稳妥。
6.3 用成本账单说服团队:一个简单算式
我在给团队做方案评审时,最常用的说服方式不是讲架构理论,而是算钱。假设模型输入价格是 0.15 美元每百万 token,一个 Agent 每次调用塞入 60k 历史 token,执行一次完整任务平均要调用 8 次模型。那么一次任务的输入成本是 60000 × 8 / 1000000 × 0.15 = 0.072 美元。如果每天服务 5000 个任务,一个月就是 0.072 × 5000 × 30 ≈ 10800 美元。而如果走外部记忆,每次检索只注入 2k token 的记忆上下文,同样 8 次调用,输入成本降为 0.0024 美元每任务,一个月大约 360 美元。差距接近三十倍,足够说服绝大多数预算决策人。
这套记忆系统架构设计的核心思路其实很简单:别把模型当成一个无限记忆体,承认它的上下文窗口只是工作台,然后把记忆拆成写入、索引、检索、遗忘四个环节,用工程手段把“该记的记住、该忘的忘掉、该更新的更新”。我自己做第一版记忆系统时也走过弯路,最早以为向量库存了就万事大吉,后来发现没有抽取、没有元数据过滤、没有冲突更新,向量库存得越多反而越扰乱 Agent 的判断。如果你正在被“上下文窗口再大一点”的幻觉困扰,我给的最实在的建议是反着来:先把你的历史对话交给一个抽取模型,试着提炼 100 条结构化记忆,看看这些记忆能不能支撑 Agent 做出比“全文塞入”更稳定的决策。能支撑住,说明你已经踏上了一条正确路线;不能支撑,问题大概率出在抽取质量,而不是记忆容量。