3 年,8 种方案,1 次重构:LangChain 记忆方案如何从混乱走向清晰

一、从"无状态"到"有记忆"的演进

大语言模型天生没有记忆。

每次调用 API,模型看到的只有你当前传来的那一段文本。上一轮聊过什么、用户叫什么名字、中途提过什么需求------只要你不重新塞进 Prompt,模型一概不知。这是 Transformer 架构的无状态本质决定的,不是什么 bug,而是设计如此。

但真实的对话场景恰好需要记忆。用户不会每次都把"我叫小明,我在北京"重复一遍。所以,给 LLM 加记忆,本质上是做一件事:把历史对话以某种形式"带进"下一次请求。

LangChain 的记忆体系从 2022 年 10 月首发至今,经历了三个清晰可辨的阶段。第一阶段是 2023 年,围绕旧版 Chains API 构建了 8 种 BufferMemory 类,每种代表一种"怎么存历史"的策略。第二阶段是 2024 年,随着 LCEL(LangChain Expression Language)成为主流,推出了 RunnableWithMessageHistory,用依赖倒置的方式把存储从记忆策略中解耦出来。第三阶段是 2025 年 10 月 LangChain 1.0 发布至今,旧 Memory API 被移入 @langchain/classic,官方推荐转向 createAgent 的 checkpointer + store 两层架构。

这条演进路线背后有一个贯穿始终的问题:对话历史应该怎么存、存多少、存哪里。理解这三个问题的答案如何逐版变化,才能真正看懂 LangChain 记忆方案的设计意图。

二、旧版 8 种 Memory 类型详解

LangChain 早期(2023 Q1 至 Q4)陆续推出了 8 种 Memory API,按策略可以归为四类:存原文、压缩原文、抽知识、搜历史。这四类不是并列关系,而是递进关系------每一类都在解决前一类留下的问题。

2.1 存原文:Buffer 系

ConversationBufferMemory 是最原始的实现。做法极简:每轮对话的 input 和 output 原样追加进一个 buffer,下次调用时整个 buffer 塞给模型。优点只有一个------实现最简单,上下文零丢失。缺点也只有一个但致命:对话越长 Token 消耗线性增长,迟早超出模型的上下文窗口。

这就是 BufferMemory 的本质:以空间换简单。短对话演示、PoC 原型、智能客服里轮次可控的场景可以一用,再长就不行了。ConversationChain 默认就用它,也侧面说明了它的定位------入门级默认方案,但不是生产级方案。

ConversationBufferWindowMemory 试图解决 Buffer 无限膨胀的问题。引入一个 k 参数,只保留最近 k 轮对话,更早的自动丢弃。内存占用固定了,但新的问题也随之而来:早期信息被丢弃。k=2 时,用户三轮前提到的项目名称,第四轮再问,模型已经不知道了。k 调太大又退化回 Buffer------这是一个没有最优解的 trade-off。

所以 Window 的本质是:以信息换效率。丢老信息,换稳定内存。

ConversationTokenBufferMemory 把控制粒度从"轮次"升级为"Token 数"。设一个 max_token_limit,超了就裁剪最早的消息。这比按轮次裁剪更精准,尤其适合有明确 Token 预算的场景------API 按 Token 计费,你只关心花多少钱,不关心保留了几轮。

但问题在于 Token 计数因语言而异。中文一字约一 Token,英文一词一 Token,需要引入 tiktoken 等工具做精确计数。而且和 Window 一样,截断就可能丢上下文。

TokenBuffer 的本质是:以精度换可控。按 Token 而非轮次裁剪,成本可量化。

2.2 压缩原文:Summary 系

Buffer、Window、TokenBuffer 共享一个天花板:只要存原文,早晚要爆。Summary 系换了一种思路------不存原文,存摘要。

ConversationSummaryMemory 在每次对话更新时调用 LLM,把历史压缩成一段摘要,只传摘要给模型。这解决了"对话可以无限长"的问题,但引入了新的代价:摘要必然损失细节。用户的猫叫什么名字、上次提到的具体数字是多少,这类边角信息在压缩过程中可能被吞掉。而且每次记忆更新都要额外调用一次 LLM 做摘要,摘要本身就是一笔 Token 开销。

Summary 的本质是:以精度换长度。牺牲细节完整性,换取几乎无限的对话长度。

ConversationSummaryBufferMemory 是前五种方案中最务实的。它把近期对话保留原文,早期对话压缩成摘要,在 max_token_limit 范围内优先保留原文。这相当于同时拿到了 Buffer 的细节优势和 Summary 的长度优势。

代价是参数变多了:Token 上限、保留条数、摘要触发策略,都需要调。而且每次触发摘要时仍有 LLM 调用开销。但即便如此,它仍然是旧版 8 种 Memory 中生产环境长对话的首选。

SummaryBuffer 的本质是:以复杂度换平衡。细节和长度兼得,但配置成本最高。

2.3 抽知识:Knowledge 系

前面五种都是在"原文/摘要"层面操作。但对话越长,越容易记混"谁是谁"、"谁有什么"。Knowledge 系换了一种思维:不存原文,而是从对话中抽取结构化的知识点。

ConversationEntityMemory 用 LLM 自动抽取实体(人物、地点、事物)及其属性,存成"实体名→描述"的键值对。每轮对话只检索相关实体注入上下文,不关心对话顺序,只关心"谁是谁"、"谁有什么"。

这带来了三个好处:精准记住关键事实、可跨多轮动态更新、无冗余。但代价也很明显:每次新对话都需要 LLM 做实体抽取,纯闲聊场景是杀鸡用牛刀。而且实体覆盖问题------"张三调到了北京"会覆盖之前"张三在上海",是否为 bug 取决于业务场景。

EntityMemory 的本质是:以抽取换聚焦。不存原文,只存"知识点"。在金融风控、企业知识管理等需要结构化事实的场景中,这种思路比存原文高效得多。

ConversationKGMemory 在 EntityMemory 的基础上更进一步:不仅存实体,还存实体间的关系。它构建知识图谱(实体---关系---实体),能够从"张三的经理是李四,李四负责项目 X"中推理出"张三的上级负责项目 X"。

这是从"记事实"到"懂关系"的升级。但构建图谱和检索都更重,单次调用成本是 8 种 Memory 中最高的。图谱质量依赖 LLM 抽取精度,关系越复杂越容易出错。

KGMemory 的本质是:以关系换洞察。

2.4 搜历史:Vector 系

以上七种方案有一个共同点:都是"顺序/结构"记忆,检索依赖时间顺序或实体关系。VectorStoreRetrieverMemory 换了一种完全不同的思路:把历史对话存入向量库(FAISS/Chroma),按语义相似度检索最相关的片段。

不需要记住顺序,只需要找到"这段和当前问题最相关"。这突破了窗口和轮次的限制,能从海量历史里按语义精准召回。但引入向量库依赖和 Embedding 开销,检索噪声也不可避免------搜出来的不一定真相关。

VectorStore 的本质是:以检索换相关。不记顺序,只找语义最相关的片段。

三、问题链总结

这 8 种 Memory 不是零散的工具箱,而是一条层层递进的问题链。每个方案都在解决上一环的痛点,同时引入新的问题,驱动下一个方案诞生。

css 复制代码
问题链:LLM 无状态,记不住对话
    │
    ├─ 方案 A:存原文
    │   ├─ Buffer ──────── 内存爆炸 ────┐
    │   ├─ Window ──────── 丢早期信息 ──┤
    │   └─ TokenBuffer ─── Token 截断 ──┘
    │               ↓
    ├─ 方案 B:压缩原文
    │   ├─ Summary ─────── 丢细节 ──────┐
    │   └─ SummaryBuffer ─ 混合折中 ────┘
    │               ↓
    ├─ 方案 C:抽知识
    │   ├─ Entity ──────── 丢关系 ──────┐
    │   └─ KG ─────────── 越来越重 ─────┘
    │               ↓
    └─ 方案 D:搜历史
        └─ VectorStore ─── 检索噪声

选型速查表:

你的问题 选哪个
对话 < 10 轮 BufferMemory
只关心最近 BufferWindowMemory
要卡 Token 成本 TokenBufferMemory
超长对话,可以丢细节 SummaryMemory
长对话,必须保细节 SummaryBufferMemory(首选)
记住关键人物/属性 EntityMemory
记住实体间关系 KGMemory
语义召回海量历史 VectorStoreRetrieverMemory

四、RunnableWithMessageHistory:过渡方案

2024 年,LCEL 成为 LangChain 的主流编排方式。旧版 Memory API 是为 Chains 设计的,和 LCEL 的 Runnable 体系不兼容。于是 LangChain 推出了 RunnableWithMessageHistory。

它的设计思路和 8 种 Memory 完全不同。RunnableWithMessageHistory 不再关心"怎么存"------不负责截断、不负责摘要、不负责抽取。它只做一件事:包装任意 Runnable,在每次调用时,从存储加载历史消息,注入到 Prompt,执行链,然后把新消息写回存储。

"怎么存"的问题被交给了 BaseChatMessageHistory 这个抽象基类。开发者只需要实现三个方法------getMessages()、addMessage()、clear()------就能接入任意后端。LangChain 官方提供了三种实现:InMemoryChatMessageHistory(内存,适合开发调试)、基于 JSON 文件的持久化(每个 sessionId 一个 .json 文件,适合单机小项目)、Redis 持久化(key 格式 chat:history:{sessionId},支持 TTL 自动过期,适合生产环境分布式部署)。

这是一个典型的依赖倒置设计。高层模块(RunnableWithMessageHistory)不依赖低层模块(具体存储实现),两者都依赖抽象(BaseChatMessageHistory)。从这个设计开始,LangChain 的记忆方案正式从"选策略"转向了"定架构"。

但 RunnableWithMessageHistory 也有自身局限。它只管理消息历史,不管理 Agent 状态、不管理跨会话的长期记忆。当对话场景从简单的"一问一答"升级为"Agent 多步推理+工具调用"时,仅记录消息列表已经不够了------你需要记住 Agent 执行到了哪一步、中间状态是什么、工具调用结果如何。这正是 RunnableWithMessageHistory 的边界所在。

当 LangChain 在 2025 年 10 月发布 1.0 版本,LangGraph 成为底层状态引擎时,RunnableWithMessageHistory 在 @langchain/core v1.2.5 被标记为 Deprecated。它的使命已经完成------把记忆从"选哪种 Buffer"解耦为"接哪个存储后端",这个过渡为后续的 createAgent 架构铺平了道路。

五、v1.x 重构:@langchain/classic 与 createAgent

LangChain 1.0(2025 年 10 月)是一场大重构。旧版 8 种 Memory API 全部移入 @langchain/classic,仍可 import,但官方教程不再推荐。取而代之的是 createAgent,一个统一的 Agent 创建入口。

createAgent 的 API 签名很简洁:

ts 复制代码
createAgent({
  model,           // 模型
  tools,           // 工具列表
  systemPrompt?,   // 系统提示词
  checkpointer?,   // 短期记忆
  store?,          // 长期记忆
  contextSchema?   // 上下文校验
})

关键变化在于 checkpointer 和 store 的分层设计。这是 LangChain 记忆方案最重要的一次理念升级。

短期记忆靠 checkpointer。 通过 checkpointer 配置 MemorySaver,Agent 自动获得会话级消息持久化能力。每次调用时传入 thread_id,同一 thread 内自动保持上下文。checkpointer 存储的是完整的状态快照------消息、执行步骤、中间变量。作用域是单个 thread,框架透明管理,开发者不需要手动读写。

ts 复制代码
import { createAgent } from "langchain";
import { MemorySaver } from "@langchain/langgraph";

const agent = createAgent({
  model: "openai:gpt-4o-mini",
  tools: [getWeather],
  systemPrompt: "你是一个友好的助手,用中文回答。",
  checkpointer: new MemorySaver(),
});

const config = { configurable: { thread_id: "session-001" } };

// 第一轮
await agent.invoke(
  { messages: [{ role: "user", content: "你好!我叫小明,我在北京。" }] },
  config
);

// 第二轮------自动记住上下文
const r2 = await agent.invoke(
  { messages: [{ role: "user", content: "我叫什么名字?" }] },
  config
);
// → "你叫小明"

长期记忆靠 store。 checkpointer 只管单次会话,但真实场景中,用户画像、偏好、跨会话的知识需要持久化。store 就是为这个设计的。它通过 namespace + key 组织 JSON 文档,支持语义索引。工具通过 runtime.store 读写,配合 contextSchema 定义用户标识。

ts 复制代码
import { createAgent, tool, type ToolRuntime } from "langchain";
import { InMemoryStore } from "@langchain/langgraph";

const store = new InMemoryStore();
const contextSchema = z.object({ userId: z.string() });

const getUserInfo = tool(
  async (_, runtime: ToolRuntime<unknown, z.infer<typeof contextSchema>>) => {
    const userId = runtime.context.userId;
    const userInfo = await runtime.store.get(["users"], userId);
    return userInfo?.value ? JSON.stringify(userInfo.value) : "未找到";
  },
  { name: "get_user_info", description: "根据 userId 查找用户信息", schema: z.object({}) }
);

checkpointer 和 store 的分工很明确:前者管会话级状态快照,自动透明;后者管应用级自定义数据,通过工具显式读写。前者用 thread_id 隔离,后者用 namespace + key 组织。前者后端是 MemorySaver / SqliteSaver / PostgresSaver,后者是 InMemoryStore / PostgresStore。

这个两层设计解决了一个旧版 Memory 体系中始终存在的张力:短期上下文和长期知识到底该不该放在同一个存储机制里?旧版的做法是混在一起------EntityMemory 既存当前对话的实体,也存跨会话的实体。但短期和长期的生命周期不同、访问模式不同、一致性要求不同,混在一起必然导致设计上的妥协。createAgent 把它们拆成两层,各管各的,边界清晰。

维度 Checkpointer(短期) Store(长期)
作用域 单个 thread(会话级) 跨所有 thread(应用级)
存储内容 完整状态快照(消息、步骤、中间变量) 应用自定义数据(用户偏好、事实、知识)
典型用途 对话连续性、中断恢复、时间旅行调试 用户画像、跨会话知识、全局配置
API 模式 自动管理,框架透明 通过工具显式读写(runtime.store)
数据键 thread_id 自动隔离 namespace + 键(如 "users", userId)

设计哲学上,这是一次根本性的转变。旧版 8 种 Memory 让开发者在"8 种策略"里选------你选 Buffer 还是 Summary?你选 Entity 还是 KG?createAgent 让开发者在"两个层级"里定------短期记忆要不要持久化?长期记忆要不要跨会话?

从"选策略"到"定层级",从"怎么存对话"到"对话记忆、用户知识、行为规则各放各层",这就是 LangChain v1.x 记忆方案的核心设计意图。

把这些节点串起来,LangChain 记忆方案的完整演进时间线如下:

时间 里程碑
2022.10 LangChain 0.0.1 首发
2023 Q1-Q2 首批 Memory API 发布(Buffer、Window、Token、Summary)
2023 Q3 Knowledge 系 Memory 发布(Entity、KG)
2023 Q4 VectorStoreRetrieverMemory 发布,8 种全部就位
2024.01 0.1.0 首个稳定版,8 种 API 齐全
2024 LCEL 兴起,RunnableWithMessageHistory 推出
2025.10 1.0 大重构,旧 Memory 移入 @langchain/classic,createAgent 成为推荐入口
2026.08 RunnableWithMessageHistory 在 @langchain/core v1.2.5 标记 Deprecated;当前最新 langchain v1.4.2

六、两层架构的认知本质与未来方向

两层记忆架构并非 LangChain 独创。认知科学中,人类记忆也分工作记忆(短期)、情景记忆(事件)、语义记忆(知识)。在笔者看来,LangChain 的 checkpointer 和 store 恰好对应了前两种------短期工作记忆和长期情景记忆。这种对应关系并非偶然------工程实践确实在向认知模型的方向靠拢。

今天你用 createAgent 写新项目,短期记忆配上 MemorySaver,长期记忆配上 PostgresStore,看起来很简单。但如果你不知道这个简单背后,是 8 种 BufferMemory 在 2023 年踩过的坑、RunnableWithMessageHistory 在 2024 年做的解耦、以及 1.0 重构时砍掉的 API------你就很难理解为什么 checkpointer 和 store 要分开设计,为什么旧版 EntityMemory 的问题在 store 模型下被自然消解了。

LangChain 的记忆方案仍在演进。官方 2025 年发布的 LangMem SDK(目前仅 Python)将 Agent 记忆进一步细分为语义记忆(知识与事实)、事件记忆(过去的经验)、程序记忆(系统行为),试图在 createAgent 的 checkpointer + store 两层基础之上再叠一层认知模型。JS 生态中,agentmemory、mem0、TencentDB Agent Memory 等社区方案也在各自的方向上探索------零干预自动捕获、用户画像构建、中文场景优化。这些都可以看作是 createAgent 的 store 层的具体实现策略。

无论记忆方案怎么变,核心问题始终是那三个:怎么存、存多少、存哪里。但答案的范式变了------从"选哪种 Buffer"变成了"checkpointer 管短期、store 管长期"。如果你今天用 createAgent 开新项目,建议从 MemorySaver + PostgresStore 起步,先跑通短期记忆,再按需扩展长期 store。

相关推荐
小小工匠1 小时前
当 Agent 遇上逆向:拆解 reverse-skill 的技能路由架构
架构·逆向·reverse-skill
2501_937860941 小时前
MySQL数据库入门|从零搞懂数据库基础、架构与存储引擎
数据库·mysql·架构
Ai拆代码的曹操1 小时前
Agent 做错了怎么办?Self-Critique 机制拆解
后端·agent·ai编程
huabuyu1 小时前
SourceMap:从「看不懂线上报错」到「让 AI 定位真根因」
前端·javascript
天性1 小时前
AgentScope Java 源码深读:一次请求如何触发工具、Middleware 和子 Agent
后端
এ慕ོ冬℘゜1 小时前
前端树形二级列表渲染 + 页面传参完整实战解析(附原生jQuery源码)
前端·javascript·jquery
ckjoker2 小时前
我把Java多模态链路从0跑通了,结果先被4个坑狠狠干了一顿
后端·agent
奈斯先生Vector2 小时前
2026 开发者效能革命:基于创源AIGC 与开源生态的工程化实践、安全沙箱与代码智能体协同
人工智能·算法·架构·prompt·aigc
Gu Gu Study2 小时前
【agent认知】宏观Agent的基本两层架构(Agent Loop与Agent Harness)
python·架构