DeepAgents.js教程04——状态保持(短期记忆)与多轮对话

2 阅读6分钟

DeepAgents.js教程04——状态保持(短期记忆)与多轮对话

承接上一节的流式输出,我们已经实现了智能体的实时交互体验。但目前的大模型还是 "一次性" 的 —— 每次调用都是全新的对话,它记不住之前说过什么。本节我们学习如何给智能体加上状态保持(短期记忆)能力,通过 Checkpointer 机制实现真正的多轮对话。


一、认识 Checkpointer(检查点保存器)

1. 什么是状态保持(短期记忆)?

默认情况下,ai大模型是 "无状态" 的。每次调用 invokestream 都是独立的请求,大模型不会保留任何对话历史。就像每次聊天都新开一个窗口,上一轮的信息完全丢失。

Checkpointer(检查点保存器) 就是解决这个问题的核心机制。它会在每次对话结束后,自动保存智能体的内部状态(主要是消息历史);下次调用时,通过同一个 thread_id 加载之前的状态,让对话可以延续下去。

2. 工作原理

整个流程可以概括为三步:

  1. 保存:每次调用结束后,智能体的完整状态(消息历史、工具调用记录等)会被 Checkpointer 持久化存储
  2. 标识:每个对话线程用唯一的 thread_id 标识,不同 thread_id 对应不同的会话
  3. 加载:下次调用时传入相同的 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

你可以尝试以下交互流程验证记忆与流式能力:

  1. 告诉 AI 你的名字、爱好等个人信息
  2. 隔几轮再问 AI "我刚才说了什么"、"我叫什么",验证记忆能力
  3. 观察回答的打字机输出效果与思考过程展示
  4. 输入 exit 退出

四、核心要点总结

1. 使用三步法

  1. 创建new MemorySaver() 创建检查点实例
  2. 注入:通过 checkpointer 参数传给 createDeepAgent
  3. 调用:每次 invoke/stream 时传入 config: { configurable: { thread_id: "xxx" } }

2. thread_id 使用技巧

  • 不同业务场景使用不同的 thread_id ,便于区分
  • 可以用用户 ID + 会话 ID 组合作为 thread_id,实现多用户隔离
  • 想要清空对话记忆时,换一个新的 thread_id 即可

3. MemorySaver 的局限

  • ❌ 程序重启后数据全部丢失
  • ❌ 多进程 / 多实例场景下无法共享
  • ✅ 开发调试、单机演示非常方便
  • 生产环境建议使用 SqliteSaverPostgresSaver 等持久化方案

五、常见问题排查

1. AI 记不住之前的对话

  • 检查是否每次调用都传入了相同的 configthread_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

下一节我们将介绍如何使用持久化存储实现长期记忆,以及如何管理多会话状态。