AI Agent记忆机制详解:从短期记忆到长期记忆的工程实践
2026/9/11 4:18:07 网站建设 项目流程

做 AI Agent 的都知道,模型本身没有记忆,这是个绕不开的坑。我见过太多 Demo 站起来能聊,坐下来就失忆:你前一轮告诉它"我是一个做咖啡烘焙的,帮我设计一下新品豆单",下一轮它就开始跟你聊旅游路线;你让它修改一段代码,它改完你换了个会话问"刚才改到第几行了",它两眼一抹黑。于是圈子里聊得最多的就是同一件事——AI Agent 记忆。

作为"走进 AI Agent"系列的第三篇,这篇我想把"让 Agent 记住你"这件事讲透。我会从短期记忆和长期记忆的分层开始聊,接着展开双网络记忆模型、hindsight 记忆库、workbuddy 这类本地记忆迁移方案如何落到工程里,最后给出一套可以照着抄的记忆模块实现思路,顺便把代码开发场景里"让 Agent 记住上次改到哪"的玩法也讲清楚。适合正在搭建自己的 Agent、想给产品加个性化能力、或者准备 AI Agent 相关面试的同学参考。

1. 先把记忆这件事拆清楚:Agent 为什么总"失忆"

1.1 你遇到的失忆,到底属于哪一种

先说结论:Agent 的"失忆"不是一种病,是至少三种病混在一起。

第一种是会话内的失忆。哪怕你在同一个对话窗口里,聊长了之后,早期的信息也会被挤出上下文窗口。这不一定是模型"笨",而是 LLM 推理时注意力范围有限,输入 token 越多,成本和延迟越高,很多实现里会对旧的上下文做截断或压缩,早期细节就这样丢了。

第二种是跨会话的失忆。用户昨天和 Agent 说过"我家有两只猫,一只叫小满,一只叫小乐",今天新开一个会话,Agent 完全不认识这俩名字。这是最影响体验的一种失忆,也是我们通常说的"长期记忆"要解决的核心问题。

第三种是环境状态的失忆。Agent 在跑多步骤任务时,中间结果没有持久化,进程一重启,所有工作上下文就没了。这种问题往往被归到"有状态服务"或"工作流编排"里,但本质上也是记忆管理的一部分。

排查一个"记不住"的问题,先别急着写向量库,先定位它是哪一种。我见过不少团队把会话内截断问题当成长期记忆问题去解决,最后搭了一个很大的向量检索服务,结果发现根本用不上,还白白增加了几百毫秒延迟。分清失忆类型,是解决记忆问题的第一步。

1.2 短期记忆与长期记忆:不是同一个存储层

在 AI Agent 设计里,短期记忆和长期记忆不应该简单当成"时间长短不同"的两个概念。

短期记忆,准确说是工作记忆,指的是 Agent 在完成当前任务时临时持有的信息,典型载体就是对话窗口中的上下文。它对应人类"手里正在做的事"的状态,特点是高频读写、快速失效、只对当前任务有意义。它不需要永久保存,但要保证当前任务的连续性。

长期记忆则对应跨会话、跨任务的持久化知识,比如用户偏好、历史事件、项目背景。它的读取不应该依赖上下文窗口,而应该从外部存储里按需检索,类似人类调用长期经验。

设计上有个简单原则:短期记忆负责"当前对话的连贯性",长期记忆负责"跨对话的一致性"。两者需要联动,但不能互相替代。你去看现在的 Agent 框架,基本都是这个套路:短期记忆做成上下文窗口的动态裁剪,长期记忆做成向量库加实体抽取、偏好存储。

我听过一个很好的类比:短期记忆是草稿纸,长期记忆是笔记本。草稿纸上的东西用完就扔,笔记本上的东西需要整理、编号、定期复习。两者如果混在一起,要么草稿纸太厚(上下文爆掉),要么笔记本太乱(召回一堆垃圾)。

1.3 双网络记忆模型:为什么大家都开始分层

近期讨论度很高的"双网络记忆模型",简单说就是把短期和长期当成两套独立但协作的"网络"来设计。短期记忆网络负责实时状态捕捉,具备快速写入和快速失效的能力;长期记忆网络负责稳定知识的沉淀,具备检索和更新的能力。

这个模型最吸引我的地方在于它强调"写入路径的分离"。短期记忆沿着对话的主流程自然流转,不做过多的格式化;长期记忆则需要经历"抽取、加工、存储"三个步骤,不是什么东西都往长期记忆里塞。

具体到实现,短期记忆可以是队列或滑动窗口,每次只保留最近 N 轮对话,再加上一轮摘要压缩;长期记忆则需要一个持久化存储,常见有 SQLite、PostgreSQL、向量数据库,再加一个语义检索接口。短期记忆的写入成本极低,长期记忆的写入成本高一些,因此需要设置触发条件和重要性判断。

用这个模型去设计 Agent,系统会变得清晰很多:先判断这条信息是"当前任务要用"还是"未来会反复用到",后者才进入长期记忆链路。这也是 hindsight 记忆库、workbuddy 本地记忆迁移这类方案背后共同的影子,它们虽然落地细节不同,但骨架都是短期、长期分层的思路。

2. 记忆模块的三种落地方式:从裸上下文到本地记忆库

2.1 上下文拼接:最直接但最容易爆的方案

最朴素的记忆实现,是把用户的历史对话或用户画像直接拼进 prompt。比如每次请求都带上"用户偏好:咖啡烘焙师、喜欢浅烘、预算 200 元以内",或者把最近 20 轮对话拼接成 system prompt。

优点是实现成本极低,几分钟就能跑通,也不需要额外组件。对于短会话和轻量场景,这确实够用。很多人在原型验证阶段也都是这么做的,先把功能跑起来再说。

但它的瓶颈非常明显。一个是长度问题,上下文窗口终归有限,随着历史变长,拼接的内容会越来越多,最终把预算和延迟都推高。另一个是信号衰减问题,当 prompt 里塞了大量不相关信息时,模型容易抓错重点,该用的偏好反而被忽略。

我自己的做法是,上下文拼接只用来承载"刚发生的事"和"必须始终在场的约束",比如系统设定、用户 ID、近期指令。至于历史事实和偏好,有条件就移出 prompt,交给检索层。这也是很多 Agent 框架默认的做法:messages 只保留当前会话最近几轮,其余存入长期记忆库。

2.2 外部记忆库与向量检索:把记忆变成可查询的数据

当记忆数据积累到一定程度,外部化存储是必然选择。基本思路是:每轮对话结束后,从对话文本里抽取有意义的信息,形成一条条"记忆条目",写入数据库;当用户发起新请求时,根据当前 query 去数据库里检索最相关的记忆,注入到 prompt 里。

记忆条目可以有不同的类型:用户偏好(喜欢简洁回答)、事实(家里有两只猫)、事件(上周三部署了新版)、技能(回复时需要附代码示例)等。每条记忆还可以附上重要度、创建时间、最后访问时间等元数据,这些后面都会用到。

检索通常用向量相似度,先把记忆内容和当前 query 都 embed 成向量,然后取 top-k。如果想要更准,可以在向量检索后再加一个"相关性重排",或者结合关键词过滤;也可以给记忆条目打标签,实现按类型过滤的检索。

这套方案对应了很多工具里的 hindsight 记忆库做法:不是原样存储整段聊天记录,而是把聊天记录"压缩"成结构化记忆,提高召回的精确度,同时减少 token 占用。说白了,存一堆原文进去,不如存几条"提炼过的经验"进去更划算。

2.3 本地记忆迁移:workbuddy 带给我们的提示

"workbuddy 历史对话记录、本地记忆迁移"这个方向,我理解指的是另一类需求:把 Agent 从一个环境迁移到另一个环境时,历史对话和记忆数据不丢失。比如你在 A 机器上训练了一个顺手的工作助手,换到 B 机器,想让它接着用,就需要本地记忆迁移。

workbuddy 这类产品/方案的思路是:把记忆数据独立于模型和 Agent 配置,持久化在本地存储(SQLite 或本地目录),并提供导出、导入机制。迁移时只需要复制一份记忆文件,在新环境重新加载,Agent 就能恢复"记忆"。

这里有几个工程细节值得注意。第一,记忆数据最好用版本化的格式,升级时能迁移;第二,如果记忆里包含向量,迁移后最好重新生成 embedding,因为不同模型的向量维度不同;第三,本地化存储天然符合隐私要求,敏感信息不会上传到云端业务服务,这也是很多用户选择本地记忆方案的原因。

2.4 选型对比:什么场景该用哪套方案

这三种方式其实不是互斥的,一个成熟的 Agent 往往会组合使用。我直接做个对比:

方案优点缺点适合场景
上下文拼接实现快、无需额外组件长度受限、成本高、易稀释注意力单会话对话、原型验证
外部记忆库 + 向量检索可扩展、召回精准、跨会话持久需要 embedding、存储、维护多会话长期记忆、知识型助手
本地记忆迁移隐私好、状态可移植需要设计导出格式、更新链路桌面工具、个人工作流助手

组合使用的典型形态是:上下文窗口存当前工作集,向量库存长期偏好和事实,本地文件或数据库负责持久化和迁移。这套形态也是我后面实操部分要重点展开的。

3. 手把手搭一套可用的 Agent 记忆模块:把理论落到代码

3.1 先定数据结构:记忆和状态的模型

动手前先把数据模型定好,后面能少踩很多坑。我一般会给 Agent 设计三层状态:

第一层是消息状态,对应当前会话的消息列表,这是短期记忆的载体。第二层是用户画像,保存用户的持久偏好和属性,属于长期记忆的一部分。第三层是记忆条目列表,由历史对话抽取而来,带有类型、重要度、时间戳等元数据。

如果用 LangGraph 这类编排框架,状态可以设计成类似下面这样(Python 伪代码,实际以框架 API 为准):

class AgentState(TypedDict): messages: Annotated[list, add_messages] user_id: str session_id: str user_profile: dict memory_ids: list[str]

几个字段的考虑:messages 走框架自带的 reducer,每次节点输出都会追加;user_id 用于区分不同用户的记忆空间,防止多人串扰;user_profile 会在每次会话开始前从记忆库加载;memory_ids 是当前请求召回的记忆条目 ID 列表,方便调试时追溯"这次到底用了哪些记忆"。

3.2 记忆写入:什么时候记住、记住什么

记忆写入是被人低估最多的环节。很多人直接把整段历史塞进向量库,结果是噪音太大,召回质量很差。我在实际项目里验证下来,比较靠谱的写入策略是"触发式抽取 + 事件式总结"。

触发式抽取的意思是,每轮对话结束后,让模型判断"当前对话里有没有值得长期记住的信息"。相当于一个二分类加信息抽取的任务:如果值得,就抽取成结构化记忆;如果不值得,就不写。判断条件可以包括:出现了新的用户偏好、发生了关键事件、用户明确提出了长期要求等。

事件式总结则针对长对话,当对话轮数超过阈值(比如 10 轮),就对之前的对话做一个摘要,把摘要存为一条记忆条目,同时将旧消息从短期上下文里归档。这样既保留了历史梗概,又控制了上下文长度。

写入记忆时建议加一个重要度评分(0~1 或 1~5)。之后召回和清理都依赖这个评分。实现上可以简单写一个 Node,用 LLM 输出结构化 JSON 格式:

{ "extracted": [ { "type": "preference", "content": "用户偏好简洁回答", "importance": 0.8 }, { "type": "event", "content": "2025-01-12 完成支付模块重构", "importance": 0.6 } ] }

这部分我踩过最大的坑是:不加判断地抽取。有段时间我的记忆库写满了类似"用户问了一个问题""用户说了谢谢"这种完全没有长期价值的条目,检索时的噪音高到几乎不可用。后来加了触发式判断,记忆条目的质量明显提升,召回结果也干净多了。

3.3 记忆召回:防呆但必要的 top-k 检索

到了召回环节,核心目标不是"召回更多",而是"召回更准"。每个新请求进来,我会先用规则过滤一轮,比如只看当前用户、只看有效记忆条目,再做 embedding 相似度检索,取 top 3~5 条。

召回之后还要做一次排序,把重要度因子、时间衰减因子考虑进去。一个简单可用的公式是:score = 相似度 * 0.6 + 重要度 * 0.3 + 时间衰减 * 0.1。时间衰减可以按最后访问时间和当前时间的差来指数缩小,保证很久没用的记忆慢慢沉底,但也保留"被重要标记"的记忆不被埋没。

召回的时机也要控制。不是每一轮都要查长期记忆,否则每次请求都多一次向量检索,延迟不可接受。我的策略是:当前对话里已经有足够上下文时跳过召回;只有在会话重建、用户切换话题、或主动提到与过去相关的内容时,才触发召回。日常对话就只依赖短期记忆。

在 LangGraph 里,这可以抽象成一个 memory_retrieve 节点,节点内先从向量库查询,再把召回的记忆内容追加进 prompt 上下文。

3.4 记忆更新与遗忘:不清理,记忆库迟早变成垃圾场

长期记忆如果不做更新和清理,数据积累到一定程度就会变得又乱又杂。这和人类记忆一样,只记不复习、只存不整理,最后什么都想不起来。

更新策略方面,我常用的做法是"版本叠加而非覆盖"。当同一用户出现新的偏好信息时,不是删掉旧条目,而是新增一条带时间戳的新条目,系统在召回时优先返回更新后的内容。如果确定旧条目已经失效(比如用户明确说"我现在做后端了,不做前端了"),就把旧条目标记为过期,而不是物理删除,方便留痕和回滚。

遗忘策略可以基于两个维度:重要度 + 最近访问时间。定期(比如每周)跑一次清理任务,把重要度低、长时间未被访问的条目归档或清除。这里的关键是"归档"而不是直接删除——万一用户某一天又用到了,你还有恢复空间。

当时我接手一个项目时,数据库里有几万条记忆条目,可实际每次能召回到的有效信息不到一成。排查后发现,大量条目是同一事件的重复变体,比如"用户喜欢深色主题"被写了几十条。后来我加入了"去重合并"和"冲突消解"机制,新增前先做一次相似记忆检索,如果相似度高于阈值,就合并或只更新元数据。

3.5 编程场景实操:让 Agent 记住"代码改到哪了"

编程辅助场景对记忆的需求很典型。像 opencode、Cursor 这类的工具里,用户经常要 Agent 连续修改多个文件,如果 Agent 不记得上一次改动的内容,下一次就会重复劳动,甚至破坏已有实现。

opencode 的思路值得借鉴:每次执行代码修改时,把"改了什么、为什么改、涉及哪些文件、改动前后的关键片段、有没有遗留问题"记录成结构化记忆。下次 Agent 工作前,先召回"这个项目最近的修改记录",就能知道上次改到哪一步,从而接着做。

实现上可以做一个简单的"修改日志记忆池",每条记录包含:任务 ID、文件路径、变更摘要、时间戳、关联分支名。在启动新任务时,先按项目路径检索最近记录,再把结果注入工作引导里。实测下来,这种"状态回放"对长任务特别有用,Agent 很少再问"你刚才是不是改过这个文件"。

我建议所有做编程类 Agent 的朋友都至少把这个"最近操作日志"做起来,它比泛化的用户画像更能提升开发体验。毕竟对程序员来说,"记住代码改到哪了"往往比"记住我喜欢什么主题"更重要。

4. 常见问题与排查技巧实录:把踩过的坑一次说清

4.1 上下文一长就爆,怎么办

上下文爆掉的本质是短期记忆管理不当。三个手段组合使用:滑动窗口、摘要归档、动态裁剪。滑动窗口就是只保留最近的 N 轮,N 根据模型上下文长度和成本来定;摘要归档是把超出窗口的旧消息合并成一个 summary 存回上下文;动态裁剪则是去掉无用的中间工具调用结果,只保留关键输出。

实际操作时,我会给上下文设置两个阈值:软阈值和硬阈值。超过软阈值时,触发一次摘要归档;超过硬阈值时,强制裁剪。这样既能保持响应质量,又不会让上下文无限膨胀。

4.2 记忆串了,怎么排查

记忆串扰最常见的原因是隔离没做好。长期记忆表里没有 user_id 字段,或者召回时忘了过滤,导致用户 A 的记忆出现在用户 B 的会话里。

排查思路比较直接:第一步看日志中注入 prompt 的记忆条目 ID 列表;第二步检查这些条目是否属于当前用户;第三步检查召回条件是否有全局默认值。隔离规则没有捷径,就是每次读写都带上命名空间,从查询的第一步就限制死"只能看当前用户的数据"。

4.3 向量库迁移后记忆全部失效,怎么办

这个问题我在 2.3 节提过,这里再展开。如果你之前用 OpenAI embedding 模型生成向量,后来换成开源模型或换了向量维度,旧的向量就无法和新查询做相似度计算了。

解决方案是记录每个记忆条目使用的 embed 模型和维度;迁移时检查版本,不一致就重新 embedding。最好的做法是记忆库里始终存储文本原文和向量两个字段,文本用于兜底和重建。以后不管换什么模型,只要有原文,向量都能重新生成。

4.4 记忆写得太乱、太碎,怎么办

这是很多人会忽视的一个问题:不是检索不够好,而是写入时就不该存。写得太碎会让召回结果里全是噪音。

我的经验是:先定义一个"值得记住"的标准,比如"用户主动表达的偏好""跨会话要复用的任务上下文""有关键时间点的事件";再定义"不该记住"的标准,比如"随口一说的一次性指令""临时性排错信息""情绪化的瞬时评价"。过滤比后来清理更省力,也更能保证记忆库的质量。

4.5 面试题速查:面试官问 Agent 记忆,到底想听什么

最近 AI Agent 面试题里,记忆是高频考点。我整理几个核心回答思路:

  • 短期和长期记忆的区别:可以从载体(上下文窗口 vs 外部存储)、生命周期、读写路径来讲。
  • 怎么实现记忆召回:说清楚 embedding、top-k 检索、重排、prompt 注入这几个环节。
  • 记忆污染怎么避免:写入时做抽取与重要度评估,召回时做过滤与去重,使用时做版本管理。
  • 多用户隔离怎么做:user_id/session_id 命名空间隔离,数据权限控制。
  • 记忆迁移怎么做:导出结构化数据、重新 embedding、版本兼容。

回答这类问题时,最好配上自己做过的项目案例。面试官更看重你有没有动手踩过坑,能说出"我当时遇到记忆串扰,后来加了命名空间隔离才解决"这种细节,比背理论强得多。

5. 进阶方向与落地建议:从"能记住"到"用得聪明"

5.1 多 Agent 记忆共享:不能各记各的

当系统从单 Agent 走向多 Agent(比如 spring ai multi agent 的场景),记忆就不能再各自为政。我建议把记忆模块抽成独立的记忆服务,以 MCP 工具的方式暴露读写接口。各 Agent 通过接口读写共享记忆,行为才能协调。

实践中需要注意记忆的一致性:两个 Agent 同时写同一用户记忆时,要以时间戳和来源区分优先级,否则会出现互相覆盖。比如一个 Agent 更新了用户的偏好,另一个 Agent 同时写了旧偏好,那后来的数据到底以谁为准?我的做法是给每条记忆加"来源标记"和"版本号",写的时候做冲突检测,而不是无脑覆盖。

5.2 从"记住"到"个性化":Agent 越用越懂你

记忆的终极价值,是让 Agent 在长期使用中形成对用户的理解。前面说的都是基础设施,真正的个性体验来自对记忆的持续挖掘:比如基于用户历史选择推荐具体参数;基于过往纠错记录调整回复风格;基于项目历史主动提出下一步建议。

这个方向上,hindsight 记忆库的理念很有启发:在任务结束后做一次"事后反思",提炼出对后续任务有用的经验,而不是仅仅记录对话本身。这套机制让记忆从"流水账"升级为"经验库"。我现在做记忆模块,都会加一个事后反思节点,跑完一个完整任务后自动生成几条"经验条目",效果比单纯存对话好很多。

5.3 给刚开始的人三条落地建议

如果现在要从零开始做 Agent 记忆,我给三条建议。

第一,不要一上来就上向量库。很多情况用 SQLite 加规则判断就够,先跑通再迭代。向量库看起来高大上,但维护成本和排查难度都不低,数据量小的时候完全没必要。

第二,记忆方案和 Agent 架构解耦。不要和某个框架绑死,单独封装记忆模块,最好支持导出和迁移。这样以后换框架、换模型,记忆数据还能继续用。

第三,把"可调试性"放在第一位。每次用什么记忆、为什么用,都要能查能回溯,否则线上出了问题根本无法排查。我在记忆模块里专门加了一个 debug 接口,可以看到每次请求召回了哪些记忆条目、各自的得分,排查效率提升非常大。

我自己把这套记忆结构在不同项目里跑过几轮之后,最大的体会是:记忆系统没有银弹,但分层思路是绕不开的地基。无论你用 workbuddy 做本地迁移、用 hindsight 做经验提炼,还是用向量库做长期召回,背后的核心都是同一件事——别想用一个容器装下所有记忆,也别指望模型自己记住。把该归短期处理的交给短期,把该沉淀的交给长期,Agent 才真正开始像有记忆的样子。

如果你现在也被"Agent 总失忆"困扰,不妨从最小的方案开始:先记录用户偏好,再做跨会话召回,最后再考虑迁移和分享。记忆做到位了,用户对 Agent 的信任感会完全不一样。后面我会继续更新这个系列,如果你在实操中遇到有趣的问题,欢迎带着场景来聊。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询