LangChain / LangGraph Memory 记忆体系技术文档(作用域判定 · 官方现版本实现 · 工程坑点)
本文在保留原稿「作用域判定」核心思想的基础上,按 LangChain 官方现版本(0.3+ / 1.0) 重写了实现层。 ⚠️ 原稿第一节列出的
ConversationBufferMemory等 7 个 Memory 类 已被官方废弃并在 1.0 移除 , 现版本from langchain.memory import ...会直接抛ImportError。详见 §2.4 废弃对照表。
一、核心判定原则(全文纲领,原稿此节完全正确)
短期记忆 / 长期记忆的划分,与底层存储介质无关;判定标准只有业务作用域:是否支持跨会话(cross-session)复用。
| 类型 | 作用域 | 官方对应机制 | 是否跨会话 |
|---|---|---|---|
| 短期记忆 | 绑定单一会话线程(thread_id) | Checkpointer | ❌ 换 thread_id 即清空 |
| 长期记忆 | 跨多个会话线程(namespace) | Store | ✅ 新开对话仍可召回 |
- 短期记忆 :维持本轮对话上下文。即使持久化入库(如存 PG),也仅供当前 thread 断点续传,不会给全新会话复用。
- 长期记忆 :新开对话依然可召回;不会一次性全塞进 Prompt,采用按需检索,仅将相关片段注入上下文。
🎯 重要呼应 :原稿这套「按作用域划分、与介质无关」的思想,恰好就是 LangChain 官方新架构的设计哲学 。 官方把持久化明确拆成两套互补系统: - Checkpointers ------ 将线程的图状态持久化为检查点,用于线程内 的短期记忆; - Stores ------ 在图状态之外持久化应用定义的数据,用于跨线程 的长期记忆。 官方原文:"Most applications can use both: checkpointer to track the current thread, store to track persistent information across threads."
二、短期记忆(会话内记忆)
2.1 业务作用与数据特点
保存当前会话上下文,让大模型知晓本轮对话历史、Agent 工具调用过程、上一轮返回结果,是维持单轮对话连贯性的基础。
| 维度 | 说明 |
|---|---|
| 数据内容 | 完整对话消息(含 ToolMessage) |
| 注入方式 | 全量注入 LLM 上下文窗口 |
| 约束 | 受 Token 上限约束,需裁剪/摘要 |
| 作用域 | 单个 thread_id |
2.2 现版本官方实现:LangGraph Checkpointer
python
from langgraph.checkpoint.memory import InMemorySaver # 开发
from langgraph.checkpoint.postgres import PostgresSaver # 生产
from langgraph.graph import StateGraph, MessagesState, START
DB_URI = "postgresql://user:pwd@host:5432/db?sslmode=disable"
with PostgresSaver.from_conn_string(DB_URI) as checkpointer:
checkpointer.setup() # ⚠️ 首次使用必须调用
graph = builder.compile(checkpointer=checkpointer)
graph.invoke({"messages": [{"role": "user", "content": "hi, I'm Bob"}]},
{"configurable": {"thread_id": "user-1-thread-a"}})
thread_id 就是会话主键。 同一个 thread_id 续聊,换一个 thread_id 就是全新会话。
2.3 存储介质选型
| 介质 | 实现 | 适用 | 说明 |
|---|---|---|---|
| 内存 | InMemorySaver(旧名 MemorySaver) |
仅开发/测试 | 进程重启即丢失 |
| SQLite | SqliteSaver |
本地需持久化的开发 | 单文件,跨重启 |
| PostgreSQL | PostgresSaver / AsyncPostgresSaver |
生产多实例首选 | 多副本共享状态 |
| Redis | Redis checkpointer | 高频短会话 | 需自行实现/选型 |
⚠️ 原稿说「Redis 生产首选」需加限定 :Redis 适合带 TTL 的短会话缓存 ;一旦需要断点续传/人在回路/时间旅行 (LangGraph 的核心能力),必须落到 PG------因为 checkpointer 存的是图状态快照,不是简单消息列表,Redis 做 TTL 自动过期会把未完成流程的状态抹掉。
2.4 ⚠️ 废弃对照表(原稿第一节的 7 个类 → 官方替代)
| 原稿 Memory 类 | 现版本状态 | 官方替代 |
|---|---|---|
ConversationBufferMemory |
v0.3.1 弃用 / 1.0 移除 | LangGraph Checkpointer(MemorySaver / PostgresSaver) |
ConversationBufferWindowMemory |
同上 | Checkpointer + 节点内 trim_messages(max_tokens=...) |
ConversationTokenBufferMemory |
同上 | Checkpointer + trim_messages(按 token 显式裁剪) |
ConversationSummaryMemory |
同上 | 自建摘要节点:定期摘要并写回 state |
ConversationSummaryBufferMemory |
同上 | 摘要节点 + trim_messages(工程推荐组合) |
ConversationEntityMemory |
同上 | Store(语义记忆)+ 实体抽取节点 |
ConversationKGMemory |
同上 | 图数据库 / Store(外部集成) |
关键事实:
LangChain 1.0:from langchain.memory import ConversationBufferMemory
→ ImportError(不是 DeprecationWarning!)
这些 legacy 类连同 ConversationChain 一起迁到独立的 langchain-classic 包。
官方迁移一行总结:"History lives in the graph state, not in a memory object."
为什么这套 API 会被淘汰? (值得写进复盘) 每一代 LangChain 记忆方案都耦合在具体编排层上 :
BufferMemory→ 绑定ConversationChain;RunnableWithMessageHistory→ 绑定 LCEL;Checkpointer→ 绑定 LangGraph。 编排层一重构,记忆方案就跟着碎。 这是结构性问题,不是 bug。 → 工程启示:尽量把记忆内容的 schema 与业务语义握在自己手里(PG 表结构),让框架只负责存取,别把记忆长在框架的抽象里。
2.5 短期记忆管理(官方三种手段)
| 手段 | 作用 | 适用 |
|---|---|---|
| Trim messages | 按 token/条数裁剪 | 通用,最简单 |
| Delete messages | 删除指定消息 | 清理敏感/无效轮次 |
| Summarize messages | 老轮次压成摘要 | 长会话,节省 token |
三、长期记忆(跨会话记忆)
3.1 业务作用与数据特点
持久存储跨会话信息,新会话可召回:用户偏好、历史任务经验、业务事实、执行策略。
| 维度 | 短期记忆 | 长期记忆 |
|---|---|---|
| 作用域 | 单 thread | 跨 thread(namespace) |
| 注入方式 | 全量注入 | 按需检索,仅命中片段 |
| 数据形态 | 原始消息序列 | 结构化事实 / 向量片段 / 规则 |
| 典型介质 | 内存 / Redis / PG | PG 表 / PGVector / Store |
为什么长期记忆必须按需检索而不全塞? 除了 token 成本,还有个硬原因:Lost in the Middle 。研究显示模型的准确率呈 U 形曲线 ------ 相关信息在输入开头或结尾 时准确率最高,埋在中间时急剧下降 。 所以"上下文窗口够大"不等于"可以全塞进去",放对位置、做精选,和容量本身一样重要。
3.2 现版本官方实现:LangGraph Store
python
from langgraph.store.memory import InMemoryStore # 开发
from langgraph.store.postgres import PostgresStore # 生产
store = InMemoryStore() # 或 PostgresStore
namespace = (user_id, "memories") # ⚠️ 是 tuple,不是字符串
store.put(namespace, str(uuid.uuid4()), {"food_preference": "I like pizza"})
items = store.search(namespace, query="用户喜欢吃什么?", limit=3)
⚠️ 只传 checkpointer 是最常见的架构错误 ------ 那样只得到"持久化的聊天记录", 不会自动得到跨会话的用户知识。两者必须同时配置:
python
graph = builder.compile(checkpointer=checkpointer, store=store)
3.3 三类长期记忆(Agent 架构常用分类,原稿分类与官方一致 ✅)
| 类型 | 英文 | 存什么 | 例子 | 推荐存储 |
|---|---|---|---|---|
| 语义记忆 | Semantic | 事实、用户偏好 | 用户习惯简洁回答、技术栈偏好 | PG 结构化表(profile,schema 严格) |
| 情景记忆 | Episodic | 过往任务、历史交互 | 上次执行的任务、报错与处理过程 | PGVector(collection,向量检索) |
| 程序记忆 | Procedural | 流程、规则、执行策略 | Agent 工具调用规范、任务处理步骤 | 配置表 / 知识库 / 可改写 system prompt |
LangMem(LangChain 官方长期记忆 SDK)把这三类都实现了,其中程序记忆(Agent 根据反馈改写自己的 system prompt)是它独有的能力。
3.4 存储介质选型(含集成路径变更)
| 介质 | 适用 | 备注 |
|---|---|---|
| PostgreSQL 普通表 | 结构化长记忆:用户偏好、基础配置 | 工程落地首选 |
| PGVector | 情景记忆、历史交互片段 | 复用现有 PG,无需独立向量库 |
| 向量数据库(Chroma / Milvus / Qdrant) | 海量片段检索(千万级以上) | 中小规模不必引入 |
| Markdown / JSON 文件 | 仅原型 Demo | 生产不推荐:无并发、无多用户管理 |
| 图数据库 | 实体关系、知识图谱 | 多跳关系场景 |
⚠️ PGVector 集成路径已变更(原稿未覆盖):
| 路径 | 状态 |
|---|---|
langchain_community.vectorstores.PGVector |
已弃用(langchain-community 归档) |
langchain_postgres.PGVector |
正在被淘汰 |
✅ langchain_postgres.PGEngine + PGVectorStore |
新项目应直接用这个 |
⚠️ 驱动坑 :langchain-postgres 要求 psycopg3(包名 psycopg) ,不是 psycopg2 也不是 asyncpg。 连接串要写成 postgresql+psycopg://...,安装 psycopg[binary,pool]。 大量旧教程仍用 psycopg2,照抄会直接报错。
四、常见误区澄清
| # | 误区 | 正解 |
|---|---|---|
| 1 | ❌ 存数据库 = 长期记忆,放内存 = 短期记忆 | ✅ 介质不决定记忆类型 。PG 存 thread_id 绑定的会话消息,只用于当前会话断点续传 → 短期记忆 ;PG 存用户偏好,新会话可读 → 长期记忆 |
| 2 | ❌ 长记忆只能用 MD 文件保存 | ✅ MD 仅适合原型。正式工程:结构化长记忆用 PG,历史片段用 PGVector |
| 3 | ❌ 配了 checkpointer 就有跨会话记忆了 | ✅ 最常见的架构错误 。checkpointer 只管单 thread;跨会话必须另外配 Store |
| 4 | ❌ 长期记忆应该全量注入 prompt | ✅ 按需检索。全量注入除了 token 成本,还会触发 Lost in the Middle(中间信息准确率骤降) |
| 5 | ❌ MemorySaver 可以上生产 |
✅ 它是 InMemorySaver,进程重启即失 ;生产用 PostgresSaver |
| 6 | ❌ 记忆写在请求同步路径上 | ✅ 记忆抽取要放后台(对话结束后异步跑),否则拖慢响应 |
五、记忆体系坑点清单(工程必读)
5.1 摘要记忆丢失细节(不可逆)
摘要是有损压缩 ,一旦压掉就找不回来。LangMem 官方也点出两难:过度抽取 → 精度下降;抽取不足 → 召回率低。
| 风险 | 表现 |
|---|---|
| 数字/ID/代码被改写 | 摘要里"用户提到订单号"丢失了具体单号 |
| 时序被压平 | "先做了 A 再做了 B" 压成 "做了 A 和 B" |
| 否定被吞 | "不要用 MySQL" 压成 "数据库选型" |
对策 :关键事实(数字、ID、代码、时间、否定判断)必须结构化入库,摘要只用于压缩"过程性叙述",不能作为唯一事实源。
5.2 向量检索召回错误
| 坑 | 说明 | 对策 |
|---|---|---|
| 相似度 ≠ 相关性 | embedding 模型决定召回天花板,语义相近但业务无关会被召回 | 相似度 + 重要性 + 时效强度(近期/频繁使用加权)组合打分 |
| 多跳关系召回不到 | 「Acme 的负责人是 Dana,Dana 偏好 Slack」------单条片段里没有完整链路,纯向量检索抓不到 | 引入图结构(实体关系)辅助召回 |
| 过滤导致结果不足(over-filtering) | 向量检索叠加 WHERE 过滤后,返回条数不够 |
pgvector 0.8+ 的 iterative index scan (hnsw.iterative_scan) |
| 索引与算子不匹配 | 用 vector_cosine_ops 建的索引,查询用 L2 距离 → 退化成全表扫描 |
opclass 与查询算子必须对齐(cosine ↔ <=>) |
5.3 记忆冲突与过时(长期记忆第一大坑)
用户偏好会变:第 3 轮说"用 Python",第 47 轮说"改用 TypeScript"------两条都在库里,相似度检索可能返回任意一条,Agent 给出自相矛盾的回答。
⚠️ 注意 2026 年的变化 :Mem0 v2.0.0(2026-04)把抽取改成了单次 ADD-only (一次 LLM 调用抽出新事实直接追加), 不再自动与已有记忆做 UPDATE/DELETE 调和 ;官方去重仅剩精确匹配(MD5) 。 好处是延迟减半、评测分数大涨;代价是记忆库不再自动自纠,矛盾事实会并存。
→ 记忆一致性成了你自己流水线的责任,不再是数据库的能力。必须自建调和机制(定期 LLM 对账 / 版本号 + 失效标记 / 按时间优先)。
5.4 Store 使用坑(官方细节,极易踩)
| 坑 | 说明 |
|---|---|
limit 默认 10,超出静默截断 |
没有任何溢出信号 。要显式设大或 offset 分页 |
namespace_prefix 是前缀匹配不是精确匹配 |
查 ("alice",) 会连 ("alice","memories")、("alice","preferences") 一起返回 → 命名空间污染 |
| 默认排序依赖后端实现 | PostgresStore 按 updated_at 降序 (最新在前);InMemoryStore 按插入顺序(最新在最后 )→ 不要跨实现依赖顺序,需排序就客户端按 updated_at 排 |
index=False 存了但搜不到 |
Store 语义搜索需配置 index={"embed":..., "dims":..., "fields":[...]};不配置或关闭则该条不可被语义检索 |
5.5 Checkpointer 使用坑
| 坑 | 说明 |
|---|---|
首次必须 checkpointer.setup() |
PostgresSaver 第一次使用要建表 |
langgraph dev 可能静默忽略自定义 checkpointer |
状态落在内存里,每次热重载销毁 → 你以为有持久化,实际没有 |
| Redis TTL 会抹掉未完成流程 | checkpointer 存的是图状态快照,不是消息列表;TTL 过期 = 未完成的 human-in-the-loop 流程丢失 |
5.6 LangMem 性能坑(直接决定架构)
LangMem 检索延迟可能达到接近 1 分钟级别,且版本尚未到 1.0。
→ 只能放后台异步整理记忆,绝不能放在用户同步请求路径上。 推荐架构:请求路径只读 记忆(PG/PGVector 直查,毫秒级);记忆抽取/整理放后台任务(对话结束后触发)。
六、工程选型建议(本 AG / Agent 项目)
| 层次 | 选型 | 理由 |
|---|---|---|
| 短期会话记忆 | PostgresSaver(checkpointer)+ Redis 做热会话缓存 |
需要断点续传 / 人在回路 → 必须持久化;Redis 只做加速,不唯一持有 |
| 结构化长期记忆(用户偏好、配置) | PostgreSQL 普通表 | schema 严格、可审计、可直接 JOIN |
| 历史任务情景记忆 | PGVector(复用现有 PG) | 中小规模无需独立向量库;注意用新集成路径 PGVectorStore |
| 记忆抽取链路 | 后台异步(对话结束后触发) | 避免拖慢用户响应;LangMem 延迟高,不能同步用 |
| 一致性维护 | 自建调和流水线(定期对账 / 版本 + 失效标记) | 现成库不再自动去重矛盾 |
落地最小骨架
python
# 短期:checkpointer 按 thread_id
# 长期:store 按 (user_id, "memories") namespace
# 两者同时编译进图
checkpointer = PostgresSaver.from_conn_string(DB_URI)
store = PostgresStore.from_conn_string(DB_URI) # 语义搜索配 index
graph = builder.compile(checkpointer=checkpointer, store=store)
config = {"configurable": {"thread_id": thread_id, "user_id": user_id}}
result = await graph.ainvoke({"messages": [...]}, config=config)
# 响应返回后再异步触发记忆抽取(不阻塞用户)
asyncio.create_task(extract_and_consolidate_memories(user_id, result["messages"]))
七、精简摘要(可用于 PPT)
LangChain 记忆划分不依赖存储介质,核心看作用域 。短期记忆绑定单一会话线程(thread_id / Checkpointer),维持本轮上下文,可放内存、Redis、PostgreSQL;长期记忆支持跨会话召回(namespace / Store),存储用户偏好与历史任务,可用 PG、PGVector。短期记忆全量注入 Prompt,长期记忆按需检索、仅召回相关片段。
一句话记忆 :Checkpointer 管"这次聊到哪",Store 管"这个用户是谁"。两者都要配。
八、版本与迁移速查
| 项 | 结论 |
|---|---|
ConversationBufferMemory |
v0.3.1 弃用,1.0 移除 → 迁 langchain-classic 或改用 Checkpointer |
ConversationChain / LLMChain |
已移除 → LCEL / LangGraph |
RunnableWithMessageHistory |
自 v0.3 起标记弃用 → 用 Checkpointer |
MemorySaver |
现名 InMemorySaver,仅开发用 |
langchain_community.vectorstores.PGVector |
已弃用 → langchain_postgres.PGVectorStore |
| psycopg 驱动 | langchain-postgres 需 psycopg3 (postgresql+psycopg://) |
检测手段 :pytest -W error::DeprecationWarning ------ 把弃用警告变成测试失败,任何残留的旧模式在上线前就会暴露。