篇四:记忆 —— 让 Agent 记住之前说过什么

14 阅读5分钟

前三篇我们解决了 Agent 的三个核心问题:第一篇搞清楚了 Agent 是什么(大模型 + 工具调用能力),第二篇手写了"模型决策 + 代码执行"的循环,第三篇把循环交给了 LangGraph 框架。这一篇我们要解决最后一个问题:怎么让 Agent 记住之前说过什么、做过什么。

先放结论:LangChain 的记忆机制已经发生了根本性变化——旧版 ConversationBufferMemory 已被淘汰,新版记忆是 LangGraph 图状态的一部分,天然支持持久化、断点续跑、多会话隔离。

为什么大模型天生"失忆"

做个实验就能验证:

// 第一次调用
await model.invoke('你好,我叫张三');
// 第二次调用
await model.invoke('我叫什么名字?');

第二次调用时,模型完全不知道"张三"是谁。原因很简单:大模型本身是无状态的,每次调用都是一次全新的请求,它不记得上一轮对话说了什么。

我们平时用的 ChatGPT、通义千问这些产品之所以能"记住",是因为应用程序在模型外面建了一套记忆系统——把每一轮对话存下来,下一次调用时把历史消息一起发给模型。

LangChain 的记忆模块,做的就是这件事。

旧版记忆:ConversationBufferMemory(已淘汰)

早期的 LangChain 用 ConversationBufferMemory 来管理记忆:

// 旧版写法,已废弃
import { ConversationBufferMemory } from 'langchain/memory';
import { ConversationChain } from 'langchain/chains';

const memory = new ConversationBufferMemory();
const chain = new ConversationChain({ llm, memory });
await chain.invoke({ input: '你好,我叫张三' });
await chain.invoke({ input: '我叫什么名字?' }); // 能记住

它的问题很明显:

  • 只能存内存:程序一重启,全部丢失
  • 附加在 Chain 上:和 Agent/Graph 体系割裂,多轮工具调用时记忆容易乱
  • 没有会话隔离:多用户场景下记忆会串
  • 不支持断点恢复:程序崩溃后无法从中间状态继续

所以新版 LangChain 已经把它淘汰了。

新版记忆:LangGraph Checkpoint

新版记忆的核心思路是:把记忆变成图状态的一部分,而不是外挂的组件。

第三篇我们定义的 AgentState 里,messages 字段就是记忆:

const AgentState = {
    messages: {
        value: (x, y) => x.concat(y), // Reducer:追加,不覆盖
        default: () => [],
    },
};

所有节点读写同一个中央状态,多轮对话历史天然就存在 messages 数组里。但这还不够——默认情况下状态只存在于内存,程序重启就丢。要让它持久化,需要加一个 Checkpointer

从内存到数据库:三种存储后端

1. 内存存储(测试用)

import { InMemorySaver } from '@langgraph/checkpoint-memory';

const checkpointer = new InMemorySaver();
const app = workflow.compile({ checkpointer });

// 调用时传入 thread_id 区分会话
const config = { configurable: { thread_id: 'session_1' } };
await app.invoke({ messages: [...] }, config);

InMemorySaver 把状态存在内存里,同一个 thread_id 的多次调用能共享历史。但程序重启后数据丢失,只适合测试。

2. SQLite 存储(开发/轻量生产)

import { SqliteSaver } from '@langgraph/checkpoint-sqlite';

const checkpointer = SqliteSaver.fromConnString('./agent.db');
const app = workflow.compile({ checkpointer });

一行代码,状态自动持久化到 SQLite 文件。程序重启后,用同一个 thread_id 调用,能完整恢复之前的对话历史和工具调用记录。

3. PostgreSQL 存储(生产环境)

import { PostgresSaver } from '@langgraph/checkpoint-postgres';

const checkpointer = PostgresSaver.fromConnString(
    'postgresql://user:pass@localhost:5432/mydb'
);
checkpointer.setup(); // 自动创建表结构
const app = workflow.compile({ checkpointer });

setup() 会自动创建四张表:

  • checkpoints:主表,存每个 thread 的 checkpoint 快照
  • checkpoint_blobs:存复杂 channel 值(比如很长的消息列表)
  • checkpoint_writes:存中间写入/pending writes
  • checkpoint_migrations:迁移版本表

生产环境推荐 PostgreSQL,支持 ACID 事务、高可用、水平扩展。

实战:让 Agent 记住用户名字

import { createAgent } from '@langchain/core/agents';
import { InMemorySaver } from '@langgraph/checkpoint-memory';

const agent = createAgent({
    model: modelWithTools,
    tools: [readFileTool],
    checkpointer: new InMemorySaver(),
});

const config = { configurable: { thread_id: 'user_zhangsan' } };

// 第一轮
await agent.invoke({
    messages: [new HumanMessage('你好,我叫张三')],
}, config);

// 第二轮——Agent 能记住
const result = await agent.invoke({
    messages: [new HumanMessage('我叫什么名字?')],
}, config);

console.log(result.messages[result.messages.length - 1].content);
// 输出:你叫张三。

关键点:

  • checkpointer:创建 Agent 时传入,开启记忆功能
  • thread_id:调用时通过 config.configurable.thread_id 传入,区分不同用户/会话
  • 同一个 thread_id 的多次调用共享状态,不同 thread_id 完全隔离

记忆的两个维度:短期 vs 长期

上面的 Checkpoint 机制解决的是短期记忆(线程级上下文)——记住当前会话里之前说了什么。但生产环境还需要长期记忆

维度存储位置作用
短期记忆Checkpointer(内存/SQLite/Postgres)记住当前会话的对话历史
长期记忆LangGraph Store / 向量数据库记住跨会话的用户偏好、关键事实

长期记忆的典型实现是用 LangGraph Store,通过 store.put()store.search() 主动读写:

// 存储用户偏好
await store.put(['user_preferences', 'user_123'], 'likes_music', {
    value: '轻音乐',
    context: '用户主动声明',
});

// 检索用户偏好
const prefs = await store.search(['user_preferences', 'user_123']);

短期记忆保证"当前对话不断片",长期记忆保证"换了会话还记得你是谁"。两者配合,Agent 才真正拥有"记忆"。

记忆管理的三个策略

对话轮次多了之后,messages 数组会越来越长,最终超出模型的上下文窗口。这时候需要管理策略:

窗口截断:只保留最近 N 轮对话,自动淘汰老旧内容。简单粗暴,但会丢失早期关键信息。

摘要压缩:用 LLM 把历史对话生成摘要,用摘要替代原始消息。节省 Token,但可能丢失细节,且每轮都要额外调用一次模型。

混合策略(生产首选) :近期对话完整保留(窗口模式),超出窗口的历史自动生成摘要。兼顾细节保留与长度控制。

小结

  • 旧版 ConversationBufferMemory 已淘汰,新版记忆是 LangGraph 图状态的一部分
  • 短期记忆靠 CheckpointerInMemorySaver(测试)、SqliteSaver(开发)、PostgresSaver(生产),通过 thread_id 区分会话
  • 长期记忆靠 LangGraph Store:跨会话存储用户偏好、关键事实
  • 记忆管理三策略:窗口截断、摘要压缩、混合策略(生产首选)