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

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

本文基于 ai/agent/memory/src/history.mjs 与 readme.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) 使用消息数组调用模型

消息不是普通字符串,而是带有类型和内容的对象,例如 HumanMessage、AIMessage、SystemMessage 和 ToolMessage。源码中可以通过 element.type 和 element.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?
  • 是否正确接收 forEach 的 index?
  • 是否明确 InMemory 只覆盖当前进程?
  • 是否评估上下文窗口、成本和历史裁剪策略?
  • 代码和模型调用是否完成实际验证?

结语

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

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

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

相关推荐
软件派9 小时前
生产动态数据智能风控:阈值判别、趋势预测、异常识别算法工程落地实战
前端·数据库·算法
ZhengEnCi17 小时前
模型训练完整流程:从数据准备到模型上线的 10 个关键步骤(全文可跑)
后端·算法
花开盛夏^.^18 小时前
电脑打开多个浏览器Web窗口占用较大的运行内存的原因
前端·浏览器
云和数据.ChenGuang18 小时前
深入解析 Python 的 dir():从用法到 CPython 源码实现
开发语言·人工智能·python·langchain
馒头不想说话18 小时前
实体店老板的24小时AI运营员工,写、剪、发全自动,省下一整个团队工资
前端
吞下星星的少年·-·20 小时前
Div.3 CF 1131A题(排序,模拟)
算法·模拟·排序
椒哥21 小时前
一天被问几十次"允许",我统计了 517 次,然后写了个插件
前端·javascript·github
风骏时光牛马21 小时前
生成一个AI稳定性治理及疑难问题分析标题,只需要一个标题,不需要其他的任何文本。
前端
aichitang202421 小时前
代数拓扑学与现代前沿视角
考研·算法·数学建模·ai·拓扑学·高考