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, 多轮对话, 大模型

相关推荐
不会就选b7 小时前
算法日常・每日刷题--<动态规划>22
算法·动态规划
释厄6237 小时前
字画同体论——字即画·几何即字·漫画与小说=文本层
人工智能·算法
CENTER SAYS7 小时前
企业400电话办理全流程详解与材料准备(实操向)
前端·crm
:mnong7 小时前
Web 三维可视化技术对比:ParaView Web vs 本项目 wasm + Three.js
前端·webgl·opengl·opencl·科学可视化
沫璃染墨7 小时前
从零入门数据结构与算法(一):复杂度分析——从算法效率到时间与空间复杂度
c语言·开发语言·数据结构·笔记·算法·飞书
Shirley~~8 小时前
three.js的调试面板工具
前端·3d
怕浪猫8 小时前
12个UI组件库,Star最低的下载最多
前端·javascript·面试
溪语流沙8 小时前
【Web全栈进阶】Vue3起步:组件、模板语法、响应式数据
前端·javascript·vue.js·css3
众链网络8 小时前
Agent 重试的工程解法:幂等、断点续跑与熔断怎么落地
java·前端·数据库·人工智能
User_芊芊君子8 小时前
JiuwenSwarm 协同智能体落地金融投研全流程:SwarmFlow 工作流编排与人机交互面板实测(A2UI v0.8 Web 通道适配)
前端·金融·人机交互