LangChain Memory 实战:用 InMemoryChatMessageHistory 管理多轮对话

LangChain Memory 实战:用 InMemoryChatMessageHistory 管理多轮对话

本文基于 ai/agent/memory/src/history.mjsreadme.md 整理。核心问题是:大模型没有天然的上下文记忆,如何让第二轮问题理解第一轮对话?答案是由应用保存消息,并在下一次调用时重新传入。本文代码和模型调用运行未验证。

文章目录

  • [LangChain Memory 实战:用 InMemoryChatMessageHistory 管理多轮对话](#LangChain Memory 实战:用 InMemoryChatMessageHistory 管理多轮对话)
    • [一、Memory 到底解决什么问题](#一、Memory 到底解决什么问题)
    • [二、`InMemoryChatMessageHistory` 的基本用法](#二、InMemoryChatMessageHistory 的基本用法)
    • 三、两轮对话的完整链路
    • [四、为什么 `getMessages()` 不能写 `.array`](#四、为什么 getMessages() 不能写 .array)
    • [五、`async/await` 在 Memory 示例中的作用](#五、async/await 在 Memory 示例中的作用)
    • [六、Memory 的边界:临时记忆不等于长期记忆](#六、Memory 的边界:临时记忆不等于长期记忆)
    • 七、常见错误排查表
    • 八、部署前自检清单
    • 结语

一、Memory 到底解决什么问题

大模型通常按当前请求中的消息生成答案。上一轮请求结束后,如果应用不保存并重新传入消息,模型就无法可靠知道之前发生过什么。

因此 Memory 的本质不是给模型增加永久记忆,而是管理发送给模型的上下文:

text 复制代码
用户输入 → 保存 HumanMessage
模型输出 → 保存 AIMessage
下一轮请求 → 取出历史并重新传给模型

当历史越来越长,还要面对上下文窗口和调用开销。笔记中提到的方向包括截断、总结和检索;当前示例只实现了内存保存。

二、InMemoryChatMessageHistory 的基本用法

源码创建了一个内存历史容器:

javascript 复制代码
const history = new InMemoryChatMessageHistory();

常用 API 可以这样理解:

API 作用
addMessage(message) 向历史追加消息
getMessages() 获取全部消息数组
model.invoke(messages) 使用消息数组调用模型

消息不是普通字符串,而是带有类型和内容的对象,例如 HumanMessageAIMessageSystemMessageToolMessage。源码中可以通过 element.typeelement.content 读取它们的类型与内容。

三、两轮对话的完整链路

第一轮的关键顺序是"先保存用户问题,再调用模型,最后保存模型回答":

javascript 复制代码
const userMessage1 = new HumanMessage("你今天吃什么?");
await history.addMessage(userMessage1);

const message1 = [
  systemMessage,
  ...(await history.getMessages()),
];

const response1 = await model.invoke(message1);
await history.addMessage(response1);

这里有一个容易忽略的点:model.invoke() 返回 AI 消息,但不会自动写入 history。必须显式执行:

javascript 复制代码
await history.addMessage(response1);

第二轮继续追加用户消息:

javascript 复制代码
const userMessage2 = new HumanMessage("好吃吗?");
await history.addMessage(userMessage2);

const message2 = [
  systemMessage,
  ...(await history.getMessages()),
];

const response2 = await model.invoke(message2);
await history.addMessage(response2);

此时历史中有四条消息:

text 复制代码
1. 第一轮用户消息
2. 第一轮 AI 回复
3. 第二轮用户消息
4. 第二轮 AI 回复

第二轮能够理解"好吃吗"所指的内容,原因不是模型永久记住了对话,而是应用把前面的消息重新放进了 message2

四、为什么 getMessages() 不能写 .array

getMessages() 返回的本身就是数组,正确遍历方式是:

javascript 复制代码
const allMessages = await history.getMessages();

allMessages.forEach((element, index) => {
  const prefix = element.type === "human" ? "用户:" : "助手:";
  console.log(`${index + 1}. ${prefix}${element.content}`);
});

错误写法如下:

javascript 复制代码
allMessages.array.forEach(...);

allMessages.array 并不存在,因此值是 undefined,继续读取 .forEach 就会产生 TypeError

另外,index 必须来自 forEach 的第二个回调参数:

javascript 复制代码
(element, index) => {}

五、async/await 在 Memory 示例中的作用

addMessage()getMessages()model.invoke() 都使用了 await。这表示当前 async 函数要等待对应的 Promise 完成后,再继续后面的逻辑。

javascript 复制代码
async function inMemoryDemo() {
  await history.addMessage(userMessage1);
  const messages = await history.getMessages();
  const response = await model.invoke(messages);
}

async 函数调用后一定得到 Promise。入口处的链式处理:

javascript 复制代码
inMemoryDemo()
  .catch(console.error)
  .finally(() => {
    console.log("done");
  });

.catch() 处理异常,.finally() 负责收尾。done 只能说明 Promise 流程结束,不能说明模型请求一定成功。

六、Memory 的边界:临时记忆不等于长期记忆

InMemoryChatMessageHistory 把数据放在当前 Node.js 进程的内存中,因此适合:

  • 学习和 Demo;
  • 单次运行的临时会话;
  • 验证消息拼接和多轮调用流程。

它不自动解决:

  • 程序重启后的数据恢复;
  • 多用户会话隔离;
  • 多实例之间共享历史;
  • 上下文过长时的裁剪和总结;
  • 长期记忆的检索策略。

如果要用于生产环境,需要进一步设计持久化存储、会话 ID、消息清理和上下文压缩方案。具体数据库和组件不在本次材料中,不能直接推断。

七、常见错误排查表

现象 原因 排查方向
第二轮不理解上一轮 没有传入历史,或没有保存 AI 回复 检查 addMessage(response)message2
undefined.forEach 错误访问 allMessages.array 直接使用 allMessages.forEach
序号报错 没有声明 index 接收 forEach 第二个参数
重启后历史消失 使用的是内存存储 接入持久化方案
done 但请求失败 finally 总会执行 结合 catch 和具体错误判断

八、部署前自检清单

  • 用户消息是否在调用模型前写入 history?
  • AI 回复是否在模型调用成功后写入 history?
  • 下一轮请求是否重新读取并展开历史数组?
  • 是否直接对 getMessages() 的返回值使用 forEach
  • 是否正确接收 forEachindex
  • 是否明确 InMemory 只覆盖当前进程?
  • 是否评估上下文窗口、成本和历史裁剪策略?
  • 代码和模型调用是否完成实际验证?

结语

多轮对话的关键不是"模型自己记住了",而是应用维护了一条消息链。InMemoryChatMessageHistory 用很少的代码展示了这条链路:addMessage 负责保存,getMessages 负责取出,model.invoke 负责调用,下一轮再把历史重新组装进请求。

掌握这条最小闭环后,再向持久化、总结、截断和检索扩展,Memory 才能从 Demo 走向可维护的 Agent 系统。

标签:LangChain, Memory, JavaScript, 多轮对话, 大模型

相关推荐
右耳朵猫AI17 分钟前
Web前端周刊2026W34 | CSS类前缀选择器采纳、Lovable迁移TanStack Start、Fastify 6.0 Alpha
前端·css
风骏时光牛马34 分钟前
智能任务协同Agent
前端
minji...41 分钟前
LangChain AI应用开发框架的使用(3) - 聊天模型的绑定,聊天模型的结构化输出及使用场景
langchain
web打印社区9 小时前
网页静默打印热敏小票:从 HTML 到出纸的完整指南
前端·javascript·vue.js·electron·pdf·html
剪刀石头布啊10 小时前
设计一个多计时任务的任务管理功能
前端
ModyQyW10 小时前
vite-plugin-uni-manifest 更新了什么
前端·typescript·uni-app
Asize11 小时前
AI 协作开发新范式:我用 SDD 做了个排版 npm 包
前端·人工智能
kyriewen13 小时前
我把今年流传的前端 AI 面试题整理了一遍——4 类场景题+回答框架(附速查表)
前端·面试·程序员
计算机魔术师13 小时前
国产多模态模型正面硬刚Opus旗舰:差距从30%缩到3%
前端