DeepAgents.js教程04——状态保持(短期记忆)与多轮对话
承接上一节的流式输出,我们已经实现了智能体的实时交互体验。但目前的大模型还是 "一次性" 的 —— 每次调用都是全新的对话,它记不住之前说过什么。本节我们学习如何给智能体加上状态保持(短期记忆)能力,通过 Checkpointer 机制实现真正的多轮对话。
一、认识 Checkpointer(检查点保存器)
1. 什么是状态保持(短期记忆)?
默认情况下,ai大模型是 "无状态" 的。每次调用 invoke 或 stream 都是独立的请求,大模型不会保留任何对话历史。就像每次聊天都新开一个窗口,上一轮的信息完全丢失。
而 Checkpointer(检查点保存器) 就是解决这个问题的核心机制。它会在每次对话结束后,自动保存智能体的内部状态(主要是消息历史);下次调用时,通过同一个 thread_id 加载之前的状态,让对话可以延续下去。
2. 工作原理
整个流程可以概括为三步:
- 保存:每次调用结束后,智能体的完整状态(消息历史、工具调用记录等)会被 Checkpointer 持久化存储
- 标识:每个对话线程用唯一的
thread_id标识,不同thread_id对应不同的会话 - 加载:下次调用时传入相同的
thread_id,智能体自动从 Checkpointer 中加载历史状态,继续对话
3. MemorySaver:内存级存储
Deep Agents 提供了多种 Checkpointer 后端,最简单的就是 MemorySaver:
- 数据存在程序内存中,程序重启后数据丢失
- 无需额外配置,开箱即用
- 适合开发测试、本地演示场景
- 生产环境建议替换为 SQLite、PostgreSQL 等持久化存储(下一章讲解)
💡 可以把
thread_id理解为聊天软件的 "会话窗口"—— 不同窗口有不同的聊天记录,同一个窗口内消息是连续的。
二、从零实现多轮对话
我们基于之前的项目结构,一步步实现带记忆能力的智能体。
2.1 导入依赖
首先引入核心组件:
import "dotenv/config";
import { createDeepAgent } from "deepagents";
import { MemorySaver } from "@langchain/langgraph";
import { model } from "./shared/model.js";
说明:
MemorySaver来自@langchain/langgraph包,是 LangGraph 提供的内存检查点实现- Deep Agents 原生兼容所有 LangGraph 的 Checkpointer 实现
2.2 创建 Checkpointer 并注入智能体
创建一个 MemorySaver 实例,通过 checkpointer 参数传给 createDeepAgent:
// 创建内存检查点保存器
const checkpointer = new MemorySaver();
// 创建带记忆能力的智能体
const agent = createDeepAgent({
model: model,
systemPrompt: "你是一个有记忆能力的助手,可以记住用户告诉你的信息,并在后续对话中使用。",
checkpointer: checkpointer, // 注入检查点
});
只需多加一行配置,智能体就具备了状态保持能力。
2.3 调用时传入 thread_id
关键一步:每次调用时,都要在 config 中传入 thread_id,用来标识当前会话。
async function streamAgent(agent, input, config) {
const stream = await agent.streamEvents(
input,
{ ...config, version: "v3" }
);
await Promise.all([
// 监听消息流:打印思考过程与回答文本
(async () => {
for await (const message of stream.messages) {
if (message.reasoning) {
process.stdout.write("\n💭 思考: ");
for await (const thinkToken of message.reasoning) {
if (thinkToken) process.stdout.write(thinkToken);
}
}
if (message.text) {
process.stdout.write("\n🤖 回答: ");
for await (const textToken of message.text) {
if (textToken) process.stdout.write(textToken);
}
}
}
})(),
]);
console.log("\n");
}
async function main() {
// 会话配置,同一个 thread_id 维持上下文
const config = { configurable: { thread_id: "demo-session-001" } };
// 第一轮对话
console.log("🧑 用户:我叫小明,今年 25 岁");
const input1 = { messages: [{ role: "user", content: "我叫小明,今年 25 岁" }] };
await streamAgent(agent, input1, config);
// 第二轮对话,复用 thread_id
console.log("\n🧑 用户:我叫什么名字?今年多大?");
const input2 = { messages: [{ role: "user", content: "我叫什么名字?今年多大?" }] };
await streamAgent(agent, input2, config);
}
main().catch(console.error);
运行这段代码,你会发现第二轮对话中,AI 能准确回答出你的名字和年龄 —— 说明记忆生效了。
三、进阶:流式交互式多轮对话
下面我们结合上一节学到的 streamEvents 流式能力,做一个完整的命令行交互式对话程序,支持实时打字机效果、思考过程展示、工具调用状态监控,同时保留历史查看、退出等功能。
3.1 完整代码
新建 04-multi-turn.js 文件:
/**
* Deep Agents 教程 04:短期记忆与多轮对话
*/
import "dotenv/config";
import { createDeepAgent } from "deepagents";
import { MemorySaver } from "@langchain/langgraph";
import { createInterface } from "node:readline";
import { model } from "./shared/model.js";
// ============================================================
// 1. 创建 checkpointer 并注入 Agent
// ============================================================
const checkpointer = new MemorySaver();
const agent = createDeepAgent({
model: model,
systemPrompt: "你是一个有记忆能力的助手,可以记住用户告诉你的信息,并在后续对话中使用。回答简洁友好。",
checkpointer: checkpointer,
});
// ============================================================
// 2. 流式输出辅助函数
// ============================================================
async function streamAgent(agent, input, config) {
const stream = await agent.streamEvents(
input,
{ ...config, version: "v3" }
);
await Promise.all([
// 监听消息流:打印思考过程与回答文本
(async () => {
for await (const message of stream.messages) {
if (message.reasoning) {
process.stdout.write("\n💭 思考: ");
for await (const thinkToken of message.reasoning) {
if (thinkToken) process.stdout.write(thinkToken);
}
}
if (message.text) {
process.stdout.write("\n🤖 回答: ");
for await (const textToken of message.text) {
if (textToken) process.stdout.write(textToken);
}
}
}
})(),
]);
console.log("\n");
}
// ============================================================
// 3. 交互式对话主函数
// ============================================================
async function main() {
// 创建命令行输入接口
const rl = createInterface({
input: process.stdin,
output: process.stdout,
});
// 生成唯一会话 ID——也可以自定义固定值
const threadId = `thread-${Date.now()}`;
const config = { configurable: { thread_id: threadId } };
// 欢迎信息
console.log("\n" + "=".repeat(60));
console.log("🎯 多轮对话已启动");
console.log("=".repeat(60));
console.log(`🆔 会话 ID: ${threadId}`);
console.log(`💡 输入 'exit' 或 'quit' 退出对话`);
console.log("=".repeat(60) + "\n");
// 封装提问函数
const askQuestion = (question) => {
return new Promise((resolve) => {
rl.question(question, (answer) => resolve(answer.trim()));
});
};
// 对话循环
while (true) {
const userInput = await askQuestion("🧑 您: ");
// 退出命令
if (!userInput || userInput.toLowerCase() === "exit" || userInput.toLowerCase() === "quit") {
console.log("\n👋 再见!");
break;
}
// 流式输出对话
console.log("\n🤖 助手思考中...");
await streamAgent(agent, { messages: [{ role: "user", content: userInput }] }, config);
}
rl.close();
}
main().catch(console.error);
3.2 运行测试
在终端执行:
node 04-multi-turn.js
你可以尝试以下交互流程验证记忆与流式能力:
- 告诉 AI 你的名字、爱好等个人信息
- 隔几轮再问 AI "我刚才说了什么"、"我叫什么",验证记忆能力
- 观察回答的打字机输出效果与思考过程展示
- 输入
exit退出
四、核心要点总结
1. 使用三步法
- 创建:
new MemorySaver()创建检查点实例 - 注入:通过
checkpointer参数传给createDeepAgent - 调用:每次 invoke/stream 时传入
config: { configurable: { thread_id: "xxx" } }
2. thread_id 使用技巧
- 不同业务场景使用不同的
thread_id,便于区分 - 可以用用户 ID + 会话 ID 组合作为 thread_id,实现多用户隔离
- 想要清空对话记忆时,换一个新的
thread_id即可
3. MemorySaver 的局限
- ❌ 程序重启后数据全部丢失
- ❌ 多进程 / 多实例场景下无法共享
- ✅ 开发调试、单机演示非常方便
- 生产环境建议使用
SqliteSaver或PostgresSaver等持久化方案
五、常见问题排查
1. AI 记不住之前的对话
- 检查是否每次调用都传入了相同的
config和thread_id - 确认
checkpointer参数正确注入到了createDeepAgent中 - 注意
thread_id是嵌套在configurable对象里的,层级不能错
2. 报错 Cannot find module '@langchain/langgraph'
- 缺少依赖包,执行
npm install @langchain/langgraph安装即可
3. 程序重启后对话历史没了
- 这是正常现象,
MemorySaver是内存存储,重启丢失是预期行为 - 需要持久化请使用 SQLite 或 PostgreSQL 后端
4. 不同用户串对话了
- 确保每个用户 / 每个会话使用独立的
thread_id - 不要全局共享同一个
thread_id
下一节我们将介绍如何使用持久化存储实现长期记忆,以及如何管理多会话状态。