创见博客
LangChain 记忆(Memory)入门
七崽爱吃小饼干2026/10/18阅读 0专栏 LangChain 开发实战

前面几篇把 Agent 的工具、结构化输出、中间件都讲过了。这一篇换一个更底层、也更关键的话题:记忆(Memory)。我们会讲清楚它是什么、为什么需要、有哪些分类,以及 LangChain 在架构上是怎么管理它的。短期记忆和长期记忆的具体实现,留到后面的文章分别展开。

一、先接受一个事实:模型是无状态的

大模型本身没有记忆。你每次调用它,它只看到这一次请求里携带的消息;请求结束,它就什么都不记得了。

所以「记忆」不是模型自己长出来的能力,而是一套外部的工程机制:

  • 存什么:把值得记住的信息写到外部存储;
  • 怎么用:在调用模型前,把相关的历史/事实检索出来、拼进上下文;
  • 何时写:交互过程中按某种策略更新这些存储。

一句话概括:

记忆 = 外部存储 + 检索拼装 + 更新策略。

它不改模型参数,只是「帮模型把该看的信息,在对的时候递到它眼前」。

二、为什么需要记忆

没有记忆的 Agent,每次调用都是「初次见面」。这会带来几个直接问题:

  1. 多轮对话不连贯:上一轮说的名字、偏好、结论,下一轮就忘;
  2. 无法个性化:不知道用户是谁、喜欢什么风格、有什么约束;
  3. 跨会话无法延续:昨天做的任务,今天接不上;
  4. 重复劳动:每次都要用户重新交代背景,浪费时间和 Token。

先跑一下「没有记忆」是什么样子。下面这个 Agent 没有配置任何记忆机制:

js
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 + statestore(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 的「工作记忆」——模型每一步看到的东西,都从这里来。

于是记忆管理要解决两件事:

  1. 这份 state 怎么跨调用保留下来?→ checkpointer(短期)
  2. 怎么跨会话沉淀知识与偏好?→ 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

js
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

js
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 侧的概念和参数完全一致:

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 跨会话存储与检索;
    • 历史太长时,再用截断 / 摘要 / 清理 / 检索来控制上下文。

一句话:记忆不是让模型变聪明,而是让该被记住的信息,在对的时候被递到模型眼前。

评论
0/100