Agent 的短期记忆与长期记忆:概念、设计与实现
从 State、Checkpointer 和 Store 出发,理解 Agent 记忆的作用域、持久化、检索与上下文注入,并梳理面试中的易错点。
面试题
Memory 是 Agent 的一个关键模块。请问如何为 Agent 设计短期记忆和长期记忆系统?可以借助哪些外部工具或技术?
先用一句话理解
自己的理解:短期记忆像当前会话的工作记录,长期记忆像跨会话使用的用户档案和经验库。
它们的核心区别是信息的使用作用域,不是数据存放在内存还是数据库,也不是保存时间的长短。下面以 LangChain / LangGraph 为例说明。官方记忆概念说明
| 对比项 | 短期记忆 | 长期记忆 |
|---|---|---|
| 主要用途 | 连续完成当前会话或任务 | 在后续会话中复用有价值的信息 |
| 常见内容 | 对话消息、工具结果、当前任务进度 | 用户偏好、事实、历史经验、可复用规则 |
| 默认作用域 | 某个 thread_id 对应的会话 | 应用定义的用户、组织或其他共享范围 |
| LangGraph 中的常见实现 | State + Checkpointer + thread_id | Store + namespace + key + value |
| 读取方式 | 恢复当前会话的状态 | 按键读取、条件筛选或语义检索 |
| 是否必须使用向量数据库 | 不需要 | 也不需要,语义检索只是可选能力 |
1、短期记忆怎么设计
用 State 管理状态,用 Checkpointer 保存状态
可以先记住这三个概念:
- State:当前会话的运行状态。默认包含
messages,也可以扩展任务进度等字段。 - Checkpointer:把运行状态保存成检查点,支持后续读取和恢复,不只是保存最终聊天记录。
- thread_id:标识这份状态属于哪个逻辑会话。
例如,第一轮用户说“我叫张三”,第二轮问“我叫什么”。在同一套检查点存储中,两次调用使用相同的 thread_id,Agent 就可以恢复之前的状态,再处理新问题。
这里的 thread 是逻辑会话,不是操作系统线程。thread_id 标识一条会话下的检查点序列,而不是某一份唯一的状态快照。短期记忆文档
是否重启就丢失,取决于存储方式
InMemorySaver:保存在当前 Python 进程的内存里,进程退出后数据会丢失。PostgresSaver:保存在 PostgreSQL 中。只要检查点仍然保留,程序重启后连接同一存储、使用对应的会话配置,就可以恢复状态。
因此,短期记忆可以长期保存在数据库里,但仍然是短期记忆,因为它服务的是某个会话内的连续执行。
反过来,InMemoryStore 可以演示跨会话长期记忆,但进程退出后数据仍会丢失。“长期记忆”这个名称并不自动保证持久化。
保存全部历史,不等于每次都发给模型
对话越来越长时,需要控制输入模型的上下文大小:
- 裁剪:只保留最近或最相关的一部分消息。
- 删除:从当前状态中移除指定消息。
- 摘要:把较早的消息压缩成摘要,再保留近期原始消息。
裁剪和删除的区别不能简单理解成“一个在调用前、一个在调用后”。是否改变保存的状态,取决于实现:只调整本次模型请求,可以保留完整 State;把裁剪结果写回 State,则会改变后续读取的当前状态。
还要注意:摘要可能遗漏信息;从当前 State 删除消息,也不代表历史检查点、日志和备份中的内容已被物理清除。如果有隐私删除要求,需要额外设计数据清理流程。
2、长期记忆怎么设计
长期记忆不是把所有聊天记录原封不动再存一遍,而是选择值得跨会话复用的信息,例如用户偏好、明确确认的事实,以及经过验证的任务经验。
用 namespace、key、value 组织信息
LangGraph Store 中,一条记忆可以这样理解:
namespace = ("users", "Alice", "memories")
key = "preferences"
value = {"sports": "跑步", "food": "酸奶"}
namespace:记忆所在的逻辑分组,类似目录。key:这条记录在该分组中的标识。value:实际保存的结构化内容。
namespace + key 共同定位一条记录。同一个 namespace 下使用相同 key 再次 put(),通常是在更新这条记录;get() 可以按 namespace 和 key 精确读取。
Alice 开启新会话后,即使 thread_id 改变,应用仍可以访问 Alice 对应的 namespace,复用她的偏好。Bob 则应访问 Bob 自己的记忆。
所以,长期记忆是在规定范围内跨会话复用,不是“所有会话、所有用户都能看见”。namespace 负责组织数据,不能代替身份认证和权限校验;应用仍需根据可信用户身份限制读写范围。长期记忆文档
精确查询与语义检索,各有用途
如果已经知道要读取“用户偏好”这条记录,按 key 查询就够了,不需要先做向量检索。
如果想寻找“与当前问题相关的历史经验”,则可以使用嵌入模型和语义检索:
- 保存记忆时,把配置中选定的 value 字段转成向量。
- 查询时,用兼容的嵌入模型把问题转成向量。
- 在允许访问的范围内比较相似度,召回相关记录。
- 将召回记录的文本内容提供给模型,而不是把向量本身当作提示词。
这里比较的是问题向量与被索引内容的向量,不一定是整个 value:索引字段配置决定了哪些内容参与检索。相似度高也不代表内容一定正确,还需要考虑来源、时效和相关性。Store 与语义检索文档
还要设计“什么时候写、写什么”
- 用户明确要求记住偏好时,可以在当前流程中立即写入。
- 从大量历史记录中提炼经验,可以放到后台任务中处理。
- 记录必要的来源和更新时间,处理重复、冲突、过期与删除。
- 不要把模型猜测直接当作用户事实,也不要无差别保存敏感信息。
这些读写规则需要应用通过工具、中间件或后台任务实现。仅仅给 Agent 配置一个 store,不会自动完成记忆提取、检索和上下文注入。
3、两种记忆怎样配合
一次带记忆的 Agent 调用,可以按下面的过程理解:
- 确认用户身份和
thread_id。 - 从 Checkpointer 恢复当前会话 State,合入本轮输入。
- 从允许访问的长期记忆范围内,按需读取或检索相关信息。
- 组合系统指令、相关历史、长期记忆和本轮问题,构建模型上下文。
- 模型生成回答或提出工具调用;工具结果返回后,必要时继续调用模型。
- 框架在执行过程中保存状态检查点;应用按规则把值得复用的信息写入长期记忆。
长期记忆也可以在工具执行过程中按需读取,不一定全部在第一次模型调用前准备好。
存储记忆和让模型看到记忆,是两件事。 State 中的自定义字段、Store 中的记录,并不会天然全部进入模型上下文;需要相应的消息构造、工具或中间件逻辑。
因此,记忆系统更适合放在上下文工程中理解:它既涉及存储、更新和检索,也涉及本次究竟给模型看什么。提示词工程侧重如何表达指令;记忆系统不只是写提示词,更不是修改模型参数、让模型本身永久学会这些信息。
4、可以选择哪些工具
学习和验证时,可以使用 InMemorySaver 管理会话检查点,使用 InMemoryStore 演示长期记忆读写。
需要跨进程持久化时,可以使用 PostgreSQL:通过 PostgresSaver 保存检查点,通过 PostgresStore 保存长期记忆。两者可以使用同一种数据库,但职责不同,不能互相替代。
需要语义检索时,再配置嵌入模型和支持向量索引的存储。向量检索是长期记忆的一种召回手段,不是长期记忆的定义,也不是每个场景都必需。
面试时可以这样回答
我会把 Agent 记忆分成会话级状态和跨会话可复用信息。短期记忆保存消息、工具结果和任务进度,用 State 管理,通过 Checkpointer 按 thread_id 保存与恢复,并用裁剪、摘要等方式控制上下文大小。
长期记忆保存用户偏好、事实和经验,按用户或组织划分 namespace,使用数据库或 Store 管理。已知记录用精确查询,相关经验可以用嵌入模型做语义检索,再把需要的内容放入本次模型上下文。同时设计记忆写入、更新、去重、过期和权限控制。
实现上,学习环境可以使用内存存储,持久化可以选择 PostgreSQL,语义检索按需引入向量索引。两种记忆的关键区别是作用域,而不是保存时间;长期记忆也不意味着所有用户共享。