Skip to content

09-记忆 (Memory):对话历史

💡 注意: 本教程适用于 LangChain.js v1.x,需要 Node.js 20+

🎯 本章目标

  • ✅ 理解为什么需要记忆
  • ✅ 掌握手动管理消息数组的方法
  • ✅ 实现多轮对话

🤔 为什么需要记忆?

没有记忆时

javascript
// 第一轮
const response1 = await model.invoke([
  new HumanMessage("我叫小明")
]);
// AI: 你好小明!

// 第二轮
const response2 = await model.invoke([
  new HumanMessage("我喜欢什么?")  // ❌ AI 不记得第一轮说的话
]);
// AI: 我不知道你喜欢什么...

有记忆后

javascript
const messageHistory = [];

// 第一轮
messageHistory.push(new HumanMessage("我叫小明"));
const response1 = await model.invoke(messageHistory);
// AI: 你好小明!

// 第二轮
messageHistory.push(new HumanMessage("我喜欢什么?"));  // ✅ AI 记得历史
const response2 = await model.invoke(messageHistory);
// AI: 你还没有告诉我你喜欢什么,但我们可以聊聊...

📦 LangChain v1.x 中的记忆管理

重要说明: LangChain v1.x 中 ChatMessageHistory 已被移除,推荐使用手动管理消息数组的方式。

基本用法

javascript
import { HumanMessage, AIMessage, SystemMessage } from "@langchain/core/messages";

// 创建消息数组
const messageHistory = [];

// 添加系统消息
messageHistory.push(new SystemMessage("你是一个友好的 AI 助手。"));

// 添加用户消息
messageHistory.push(new HumanMessage("你好"));

// 添加 AI 消息
messageHistory.push(new AIMessage("你好!有什么可以帮助你的?"));

// 获取所有消息
const messages = messageHistory;

💡 完整示例

javascript
import { ChatOpenAI } from "@langchain/openai";
import { SystemMessage, HumanMessage, AIMessage } from "@langchain/core/messages";

const model = new ChatOpenAI({
  modelName: "MiniMax/MiniMax-M2.5",
  apiKey: process.env.MODELSCOPE_API_KEY,
  temperature: 0.7,
  configuration: {
    baseURL: "https://api-inference.modelscope.cn/v1",
  },
});

// 手动管理消息数组
const messageHistory = [];

// 添加系统消息(设定角色)
messageHistory.push(
  new SystemMessage("你是一个友好的 AI 助手,记住用户告诉你的信息。")
);

async function chat(userMessage) {
  // 添加用户消息
  messageHistory.push(new HumanMessage(userMessage));
  
  // 获取回复
  const response = await model.invoke(messageHistory);
  
  // 保存 AI 回复
  messageHistory.push(new AIMessage(response.content));
  
  return response.content;
}

// 第一轮
console.log("用户:我叫小明,今年 25 岁");
let response = await chat("我叫小明,今年 25 岁");
console.log("AI:", response);

// 第二轮
console.log("\n用户:你喜欢我吗?");
response = await chat("你喜欢我吗?");
console.log("AI:", response);

// 第三轮 - 测试记忆
console.log("\n用户:我今年多大?");
response = await chat("我今年多大?");
console.log("AI:", response);  // AI 会记得:你今年 25 岁

🎯 封装为对话类

javascript
class ChatBot {
  constructor(model) {
    this.model = model;
    this.messageHistory = [];
    
    // 添加系统消息
    this.messageHistory.push(
      new SystemMessage("你是一个友好的 AI 助手。")
    );
  }

  async chat(userMessage) {
    // 添加用户消息
    this.messageHistory.push(new HumanMessage(userMessage));
    
    // 获取回复
    const response = await this.model.invoke(this.messageHistory);
    
    // 保存 AI 回复
    this.messageHistory.push(new AIMessage(response.content));
    
    return response.content;
  }

  // 清空历史
  clear() {
    this.messageHistory = [this.messageHistory[0]];  // 保留系统消息
  }

  // 获取历史
  getHistory() {
    return this.messageHistory;
  }

  // 获取历史条数
  getMessageCount() {
    return this.messageHistory.length;
  }
}

// 使用示例
const bot = new ChatBot(model);

console.log(await bot.chat("你好"));
console.log(await bot.chat("我叫小明"));
console.log(await bot.chat("记住我的名字"));
console.log(await bot.chat("我叫什么?"));  // AI 会记得

📋 限制历史长度

对话历史太长会消耗更多 token,可以限制长度:

javascript
class LimitedChatBot {
  constructor(model, maxMessages = 10) {
    this.model = model;
    this.messageHistory = [];
    this.maxMessages = maxMessages;
  }

  async chat(userMessage) {
    // 添加用户消息
    this.messageHistory.push(new HumanMessage(userMessage));
    
    // 限制历史长度(保留系统消息 + 最近的 N 条)
    if (this.messageHistory.length > this.maxMessages + 1) {
      // 保留第一条(系统消息)和最近的 maxMessages 条
      const systemMsg = this.messageHistory[0];
      const recentMsgs = this.messageHistory.slice(-this.maxMessages);
      this.messageHistory = [systemMsg, ...recentMsgs];
    }
    
    // 获取回复
    const response = await this.model.invoke(this.messageHistory);
    
    // 保存 AI 回复
    this.messageHistory.push(new AIMessage(response.content));
    
    return response.content;
  }
}

⚙️ 消息类型详解

消息类型说明示例
SystemMessage系统指令new SystemMessage("你是助手")
HumanMessage用户消息new HumanMessage("你好")
AIMessageAI 消息new AIMessage("你好!")

AIMessage 带元数据

javascript
// AIMessage 可以包含额外信息
const aiMsg = new AIMessage("你好!", {
  name: "助手",  // 可选:AI 的名字
  // 其他元数据...
});

🔄 查看对话历史

javascript
// 打印完整对话历史
console.log("=== 完整对话历史 ===");
messageHistory.forEach((msg, index) => {
  const type = msg.constructor.name;
  console.log(`${index + 1}. [${type}]: ${msg.content}`);
});

输出:

=== 完整对话历史 ===
1. [SystemMessage]: 你是一个友好的 AI 助手。
2. [HumanMessage]: 我叫小明
3. [AIMessage]: 你好小明!很高兴认识你!
4. [HumanMessage]: 我今年多大?
5. [AIMessage]: 你今年 25 岁呀!

⚠️ 注意事项

1. Token 限制

对话历史太长会超出模型的 token 限制:

javascript
// 定期清理历史
if (messageHistory.length > 50) {
  // 只保留系统消息和最近的 10 条
  const systemMsg = messageHistory[0];
  const recentMsgs = messageHistory.slice(-10);
  messageHistory = [systemMsg, ...recentMsgs];
}

2. 系统消息位置

系统消息应该始终在开头:

javascript
// ✅ 正确
const messages = [
  new SystemMessage("你是助手"),
  ...history,
  new HumanMessage("新消息")
];

// ❌ 错误
const messages = [
  ...history,
  new SystemMessage("你是助手"),  // 可能被忽略
  new HumanMessage("新消息")
];

3. 持久化存储

默认情况下,消息数组只存在于内存中。程序重启后历史会丢失。

保存到文件:

javascript
import fs from 'fs';

// 保存
function saveHistory(filename = 'history.json') {
  const history = messageHistory.map(msg => ({
    type: msg.constructor.name,
    content: msg.content
  }));
  fs.writeFileSync(filename, JSON.stringify(history, null, 2));
}

// 加载
function loadHistory(filename = 'history.json') {
  try {
    const history = JSON.parse(fs.readFileSync(filename, 'utf-8'));
    messageHistory = history.map(msg => {
      if (msg.type === 'HumanMessage') {
        return new HumanMessage(msg.content);
      } else if (msg.type === 'AIMessage') {
        return new AIMessage(msg.content);
      } else if (msg.type === 'SystemMessage') {
        return new SystemMessage(msg.content);
      }
    });
  } catch (e) {
    console.log("没有历史对话,从头开始");
  }
}

📝 本章小结

  • LangChain v1.x 推荐手动管理消息数组
  • 使用 push() 添加消息,invoke() 发送对话
  • 可以限制历史长度节省 token
  • 可以封装为 ChatBot 类
  • 可以持久化保存到文件

🏃 下一章

[10-工具 (Tools) →](./10-工具 (Tools).md)

基于 LangChain.js v1.x 版本