前面几篇把 Agent 的工具、结构化输出、中间件都讲过了。这一篇换一个更底层、也更关键的话题:记忆(Memory)。我们会讲清楚它是什么、为什么需要、有哪些分类,以及 LangChain 在架构上是怎么管理它的。短期记忆和长期记忆的具体实现,留到后面的文章分别展开。
一、先接受一个事实:模型是无状态的
大模型本身没有记忆。你每次调用它,它只看到这一次请求里携带的消息;请求结束,它就什么都不记得了。
所以「记忆」不是模型自己长出来的能力,而是一套外部的工程机制:
- 存什么:把值得记住的信息写到外部存储;
- 怎么用:在调用模型前,把相关的历史/事实检索出来、拼进上下文;
- 何时写:交互过程中按某种策略更新这些存储。
一句话概括:
记忆 = 外部存储 + 检索拼装 + 更新策略。
它不改模型参数,只是「帮模型把该看的信息,在对的时候递到它眼前」。
二、为什么需要记忆
没有记忆的 Agent,每次调用都是「初次见面」。这会带来几个直接问题:
- 多轮对话不连贯:上一轮说的名字、偏好、结论,下一轮就忘;
- 无法个性化:不知道用户是谁、喜欢什么风格、有什么约束;
- 跨会话无法延续:昨天做的任务,今天接不上;
- 重复劳动:每次都要用户重新交代背景,浪费时间和 Token。
先跑一下「没有记忆」是什么样子。下面这个 Agent 没有配置任何记忆机制:
const agent = createAgent({
model,
tools: [],
systemPrompt: "你是简洁的中文助手。",
});
const r1 = await agent.invoke({
messages: [{ role: "user", content: "我叫小明,住在杭州,请记住。" }],
});
// 助手:好的,小明,我记住了:你住在杭州。
const r2 = await agent.invoke({
messages: [{ role: "user", content: "我叫什么?住在哪?" }],
});
// 助手:你好!关于你的名字和住址,我并不知道,因为你还没有告诉我。
第一次它说「记住了」,第二次却完全不记得。不是模型在撒谎,而是第二次调用时,上一轮的消息根本没有出现在它的输入里。
要解决这个问题,就需要「记忆」——把历史保存下来,并在下次调用时带上。
三、记忆有哪些分类
记忆可以按不同维度分类。理解分类,才知道「该往哪存、存什么、什么时候存」。
3.1 按作用范围 / 生命周期(最常用的一刀切)
| 短期记忆(Short-term) | 长期记忆(Long-term) | |
|---|---|---|
| 作用范围 | 单个 thread / 会话 | 跨 thread / 跨会话 |
| 存什么 | 当前对话消息、临时状态 | 用户画像、稳定事实、经验、规则 |
| 生命周期 | 会话级 | 长期(直到被更新/删除) |
| 典型实现 | checkpointer + state | store(namespace + key) |
- 短期记忆(thread-scoped):只服务「当前这一段会话」,典型载体是对话消息历史。换一个会话就看不到。
- 长期记忆(cross-thread):跨会话共享,不绑定某个 thread,可以随时、在任意会话里被检索。
3.2 按内容类型(借鉴人类记忆 / CoALA 论文)
| 类型 | 存什么 | 例子 |
|---|---|---|
| 语义记忆(Semantic) | 事实与知识 | 用户叫小明、住在杭州、偏好 Go |
| 情景记忆(Episodic) | 经历与做过的事 | 上次这个任务是怎么一步步完成的,可作为 few-shot 例子 |
| 程序性记忆(Procedural) | 规则与方法 | 系统提示词、操作规范,甚至通过「反思」自我改写提示词 |
3.3 按写入时机
- 热路径(in the hot path):边交互边存。实时、可感知,但会增加延迟,且 Agent 要「分心」决定存什么。
- 后台(in the background):异步整理成记忆。不拖慢主流程,但新记忆有延迟,还要决定触发频率(定时 / 事件 / 手动)。
3.4 按组织形式(针对语义记忆)
- Profile(档案):一份持续更新的 JSON,字段固定,适合信息少而稳定的场景。
- Collection(集合):一条条独立记忆文档,适合信息多、持续增长的场景,但更新(去重/删除)和检索更复杂。
3.5 小结表
| 维度 | 选项 |
|---|---|
| 作用范围 | 短期(thread) / 长期(cross-thread) |
| 内容类型 | 语义(事实) / 情景(经历) / 程序性(规则) |
| 写入时机 | 热路径 / 后台 |
| 组织形式 | Profile / Collection |
| 常见载体 | 消息历史、摘要、向量库、Store、提示词 / 模型权重 |
四、LangChain 是怎么管理记忆的
LangChain 的 Agent 建立在 LangGraph 之上,记忆管理也复用了 LangGraph 的两块基础设施:状态(State) 和 持久化(Persistence)。
4.1 一切从 Agent State 开始
Agent 运行时有一份 state,最核心的字段就是 messages(对话历史)。这份 state 就是 Agent 的「工作记忆」——模型每一步看到的东西,都从这里来。
于是记忆管理要解决两件事:
- 这份 state 怎么跨调用保留下来?→
checkpointer(短期) - 怎么跨会话沉淀知识与偏好?→
store(长期)
4.2 短期记忆:state + checkpointer + thread
- checkpointer 会在每一步/每次调用后,把 state 存成「检查点」;
- 用
thread_id区分不同会话; - 下次带同一个
thread_id调用,state 会被恢复,历史自然接上。
心智模型:
invoke(thread_id=A) -> 读检查点 -> 跑 Agent -> 写检查点
invoke(thread_id=B) -> 另一段独立会话,互不影响
对应 createAgent 的参数:checkpointer。
4.3 长期记忆:store + namespace + key
- store 是一个键值/文档存储,存 JSON 文档;
- 用
namespace(类似文件夹,常放user_id/org_id)组织,用key(类似文件名)定位一条记忆; - 支持按内容 filter 过滤、按语义(向量)搜索;
- 和 thread 无关,因此可以跨会话共享。
心智模型:
store.put(namespace, key, { 事实 / 偏好 ... })
store.get(namespace, key)
store.search(namespace, { filter, query })
对应 createAgent 的参数:store。
4.4 上下文管理:别让历史无限膨胀
短期记忆不是「越多越好」。上下文窗口有限,而且长上下文更慢、更贵、更容易被无关内容干扰。所以还需要一套「裁剪 / 压缩」策略:
- 截断:只保留最近 N 条;
- 摘要:把旧对话压成一段摘要(
summarizationMiddleware,见 demo-15); - 清理工具结果:腾出被大段工具输出占用的空间(
contextEditingMiddleware,见 demo-16); - 按需检索:不把所有历史都带上,而是检索相关片段(RAG 思路)。
4.5 一个记忆的生命周期
读取记忆(恢复 state / 检索 store)
↓
拼装上下文(历史 + 相关长期记忆 + 系统提示)
↓
调用模型 / 执行工具
↓
更新记忆(写回 state;必要时写 store)
4.6 createAgent 上的三个相关入口
| 参数 | 负责的记忆 | 说明 |
|---|---|---|
checkpointer | 短期记忆 | 按 thread 持久化 state |
store | 长期记忆 | 跨 thread 的命名空间存储 |
stateSchema | 自定义工作记忆 | 扩展 state,放自己的记忆字段 |
五、代码骨架(预告)
下面只是「长什么样」的骨架,帮助建立整体印象,本文不展开具体实现。
短期记忆:checkpointer + thread_id
import { createAgent } from "langchain";
import { MemorySaver } from "@langchain/langgraph";
const agent = createAgent({
model,
tools: [],
checkpointer: new MemorySaver(), // 挂上短期记忆
});
const config = { configurable: { thread_id: "user-1" } };
await agent.invoke({ messages: [{ role: "user", content: "我叫小明" }] }, config);
await agent.invoke({ messages: [{ role: "user", content: "我叫什么?" }] }, config);
// 同一个 thread -> 记得上一轮
长期记忆:store + namespace
import { createAgent, InMemoryStore } from "langchain";
const store = new InMemoryStore();
await store.put(["user-1", "profile"], "basic", {
name: "小明",
city: "杭州",
});
const item = await store.get(["user-1", "profile"], "basic");
// item.value => { name: "小明", city: "杭州" }
const agent = createAgent({
model,
tools: [],
store, // 挂上长期记忆
});
后续 demo 会分别演示:短期记忆的对话延续、长期记忆的读写与检索。
六、Python 对应写法
Python 侧的概念和参数完全一致:
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.store.memory import InMemoryStore
agent = create_agent(
model="deepseek:deepseek-chat",
tools=[],
checkpointer=InMemorySaver(), # 短期记忆
store=InMemoryStore(), # 长期记忆
)
config = {"configurable": {"thread_id": "user-1"}}
agent.invoke({"messages": [{"role": "user", "content": "我叫小明"}]}, config)
store.put(("user-1", "profile"), "basic", {"name": "小明", "city": "杭州"})
item = store.get(("user-1", "profile"), "basic")
七、小结
- 记忆是什么:模型本身无状态,记忆是一套「外部存储 + 检索拼装 + 更新策略」的机制;
- 为什么需要:多轮连贯、个性化、跨会话延续、避免重复;
- 怎么分类:
- 作用范围 → 短期(thread)/ 长期(cross-thread)
- 内容类型 → 语义(事实)/ 情景(经历)/ 程序性(规则)
- 写入时机 → 热路径 / 后台
- 组织形式 → Profile / Collection
- LangChain 怎么管:
- Agent state(
messages)是工作记忆; - 短期记忆用
checkpointer+thread_id按会话持久化; - 长期记忆用
store+namespace+key跨会话存储与检索; - 历史太长时,再用截断 / 摘要 / 清理 / 检索来控制上下文。
- Agent state(
一句话:记忆不是让模型变聪明,而是让该被记住的信息,在对的时候被递到模型眼前。