Agent 最让人头疼的问题之一,就是它"不记事"。
用户上一轮告诉你"我偏好用 Python 写后端",下一轮对话 Agent 又问你"你用什么语言"。用户说"帮我查一下上周那个工单的状态",Agent 反问"哪个工单?"——因为每个新会话都是一张白纸。
如果你的 Agent 接入的是客服、运维、或者任何需要持续服务的场景,这种"每次对话都失忆"的问题,直接决定了用户愿不愿意用第二次。
对话级记忆和跨会话记忆的区别
很多团队已经在做一件事:在单次对话里管理上下文。比如用滑动窗口截断历史、把旧对话摘要塞进系统提示词、或者用 Compaction API 压缩长对话。这些手段解决的是"这次对话别超 Token 上限"的问题,但解决不了"这次对话认不出上次对话的人"。
这是两个不同的记忆维度:
- 会话内记忆(within-thread):当前对话的上下文,包括历史消息、中间状态、工具调用结果。对话结束就清空。
- 跨会话记忆(cross-thread):跨越多次对话的持久化信息,比如用户偏好、关键事实、历史行为。对话结束,这些信息还在。
会话内记忆靠的是上下文窗口管理,而跨会话记忆需要的是一个存储层。以前这个存储层得自己搭——选数据库、设计 Schema、写 CRUD、还要考虑怎么把存进去的东西跟 Agent 的推理流程衔接起来。LangGraph v0.5 发布的 MemoryStore,就是把这件事从"你自己搭"变成了"框架帮你接好"。
MemoryStore 做了什么
LangGraph 之前已经有 Checkpoint 机制,用来保存每个 step 的执行状态,支持断点续跑。但 Checkpoint 是按 thread 隔离的,thread A 看不到 thread B 的数据。MemoryStore 是独立于 thread 的持久化存储,所有 thread 共享同一个 store,天然支持跨会话数据共享。
它的核心 API 只有三个操作:
store.put(namespace, key, value) # 写入记忆
store.get(namespace, key) # 读取单条记忆
store.search(namespace, query) # 搜索记忆
Namespace 是一个元组,用来做数据隔离。最常见的用法是按用户隔离:
store.put(
namespace=("user", user_id, "preferences"),
key="programming_language",
value={"lang": "Python", "framework": "FastAPI"}
)
这样每个用户的记忆互不干扰,不同 namespace 之间天然隔离。同一个 namespace 下的多条 key 可以按 query 搜索,框架内置了向量搜索能力,不依赖外部向量数据库。
从单次对话到持续记忆的工程转变
接入 MemoryStore 之后,Agent 的工作流多了一个"记忆层"。
以前一个典型的 Agent 循环是:
接入 MemoryStore 之后,第 2 步之前多了一个步骤:从 store 中检索当前用户的相关记忆,注入到上下文中。第 4 步工具执行完之后,如果产生了有价值的信息,同步写入 store。
这个变化看起来不大,但对 Agent 的行为影响是质的:
- 用户说"按上次的方案来",Agent 真的知道"上次的方案"是什么
- 用户说"不要发邮件,用 Slack 通知",Agent 记住这个偏好,之后所有对话都不再问
- 用户说"帮我查一下昨天那个异常",Agent 知道"昨天那个异常"指的是哪个
记忆检索的时机和粒度
MemoryStore 的 search 方法支持按 query 做语义匹配,不是简单的前缀匹配。这意味着 Agent 可以检索"和当前问题相关的记忆",而不是"和关键词完全匹配的记忆"。
但这里有一个工程上容易踩的坑:检索时机。
如果每轮对话都全量检索,Token 消耗会迅速膨胀。特别是当 store 里积累了上百条记忆时,把所有记忆一股脑塞进上下文,和没做记忆管理的效果一样差——Token 照样爆。
比较好的做法是分层检索:
- 高频记忆:在 Agent 初始化时加载,比如用户的语言偏好、通知偏好、时区设置。这些信息几乎每次对话都会用到,适合常驻上下文。
- 场景记忆:根据当前对话的意图触发检索。比如用户提到"工单"时,才去检索和历史工单相关的记忆。
- 临时记忆:工具执行过程中产生的中间结果,只在当前 step 使用,不需要持久化。
MemoryStore 的 namespace 设计天然支持这种分层。高频记忆放在 ("user", uid, "profile"),场景记忆放在 ("user", uid, "context", "tickets"),search 的时候限定 namespace 范围,减少无效检索。
记忆写入的判定标准
比检索更难的,是"什么时候该写记忆"。
LLM 的回复里包含大量信息,但并不是所有信息都值得记住。如果每轮对话都把 Agent 的回复原文写进 store,store 很快就会变成一堆垃圾数据,检索时反而引入噪声。
MemoryStore 自己不做"什么值得记"的判定——这个判定需要应用层来做。常见的做法是让 Agent 在完成关键工具调用后,通过一个专门的"记忆写入"工具来触发存储:
class WriteMemory(Tool):
"""当发现对后续对话有价值的用户信息时,写入记忆。"""
def execute(self, namespace: tuple, key: str, value: dict):
store.put(namespace, key, value)
return "记忆已保存"
这个工具的调用权交给 LLM,由 LLM 判断"这条信息值得记住"。实际效果取决于 Prompt 中对"什么值得记"的定义。如果 Prompt 写得太宽泛,LLM 会把什么都记进去;写得太严格,又可能漏掉关键信息。
一个更可控的方式是:在工具执行结果返回后,在应用层用规则做一次过滤,只有满足条件的信息才写入 store。比如"用户明确表达偏好"、"工具返回了关键数据"、"用户的指令对后续对话有约束力"。
记忆的更新和冲突
跨会话记忆还有一个容易被忽略的问题:记忆更新。
用户在第一次对话中说"我偏好 Python",store 里记了 {"lang": "Python"}。第五次对话时用户说"最近切到 Go 了",这时候应该更新还是追加?
MemoryStore 的 put 方法是按 key 覆盖的,同一个 key 的多次 put 以最后一次为准。这符合大多数场景的需求——用户的最新偏好覆盖旧偏好。但有些场景需要保留历史版本,比如用户偏好频繁变化时,需要知道变化轨迹。
这种情况下,可以用时间戳作为 key 的一部分,或者自己在 value 里维护版本号。MemoryStore 本身不做版本管理,这部分需要应用层自己处理。
生产环境的三个关注点
第一,存储后端的选择。 InMemoryStore 只适合开发和测试。生产环境需要对接持久化存储,LangGraph 提供了对 PostgreSQL、Redis 等后端的支持。选择时需要考虑跨线程/跨进程的并发写入冲突——同一个用户的两次对话可能同时写入 store,需要做好乐观锁或最后写入者胜出的策略。
第二,检索的延迟预算。 MemoryStore 的 search 如果走向量检索,延迟会比 key-value 的 get 高一个数量级。在 Agent 的主循环里,每一步的延迟都是累加的。如果每次检索记忆都要等 200ms,十步对话就多出两秒。建议高频记忆走 get(毫秒级),场景记忆走 search(可接受百毫秒级),并根据对话的实时性要求做取舍。
第三,记忆量的增长管理。 跨会话记忆会随着时间持续增长,不像会话内记忆在对话结束后就释放。需要定期做记忆的归档和清理。比如三个月前的用户偏好如果再也没有被更新过,可以归档到冷存储;空 namespace 可以清理掉。这部分不是 MemoryStore 的能力范围,需要在应用层配合定时任务。
什么时候该上跨会话记忆
不是所有 Agent 都需要跨会话记忆。如果你的 Agent 场景是"每次对话独立、用户不关心上下文延续",比如一次性的文档问答、单次代码生成,那会话内记忆就够用了。
但如果你正在做的是用户持续使用的服务型 Agent——客服助手、运维机器人、个人助理、开发辅助——跨会话记忆就是必需品。没有它,用户每次对话都要重新交代背景,体验和第一版聊天机器人没有区别。
LangGraph MemoryStore 的价值不在于它做了什么特别复杂的事,而在于它把"跨会话记忆"这个原本需要自己从头搭建的能力,变成了框架内的一等公民。三个 API 方法、一个 namespace 概念,接上就能用。对于已经在用 LangGraph 的团队,这是一个低成本、高回报的工程选择——前提是搞清楚记忆的写入策略和检索边界,不要一股脑全存、全搜。
资料展示
下面是我整理的AI大模型学习资料和工具包预览,适合收藏后按主题逐步学习。



如果你想看完整资料目录,可以在评论区留言「资料」;也欢迎告诉我你更关注AI大模型里的哪类内容。

网硕互联帮助中心







评论前必须登录!
注册