当前位置: 首页 > news >正文

[LangChian] 18. 自动维护聊天记录

上一节我们体验了“手动维护聊天记录”,每次都要:

  • 把用户发言添加到 history
  • 把模型输出添加到 history
  • 每轮都手动调用 getMessages() 构造上下文
await history.addMessage(new HumanMessage(input));
await history.addMessage(fullRes);

虽然原理简单,但实际开发中太过繁琐。尤其在构建多轮对话 Agent 时,这种手动维护非常不优雅。所以这一节,我们引入 LangChain.js 提供的“自动加记忆”工具 —— RunnableWithMessageHistory

快速上手

前面我们有介绍过 Runnable 相关的接口,例如

  • RunnableLambda
  • RunnableMap
  • RunnableSequence
  • RunnablePassthrough

这里的 RunnableWithMessageHistory 也属于 Runnable 家族的一员。在实例化的时候,接收一个配置对象:

new RunnableWithMessageHistory({runnable: baseChain, // 原始链getMessageHistory: (sessionId) => chatHistory, // 指定聊天记录inputMessagesKey: "input", // 用户输入字段名historyMessagesKey: "chat_history", // Prompt 中历史记录占位符
});

配置对象通常需要配置这几个参数:

  • runnable:需要被包裹的 chain,可以是任意 chain
  • getMessageHistory(sessionId):传入会话 ID,返回一个 BaseChatMessageHistory 实例
  • inputMessagesKey:本轮用户消息在哪个 key,调用后会被追加进历史。
  • historyMessagesKey:历史注入到输入对象这个 key,下游用 new MessagesPlaceholder("<同名>") 接住。
  • outputMessagesKey::当链输出是对象时,指定对象里哪一个字段是消息(否则默认把顶层输出当消息)。

课堂演示

快速上手示例

import { ChatMessageHistory } from "@langchain/classic/memory";
import { StringOutputParser } from "@langchain/core/output_parsers";
import {ChatPromptTemplate,HumanMessagePromptTemplate,MessagesPlaceholder,SystemMessagePromptTemplate,
} from "@langchain/core/prompts";
import { RunnableWithMessageHistory } from "@langchain/core/runnables";
import { ChatOllama } from "@langchain/ollama";const pt = ChatPromptTemplate.fromMessages([SystemMessagePromptTemplate.fromTemplate("你是一个健谈的中文 AI 助手,请结合上下文尽可能详细地使用中文回答用户问题。"),new MessagesPlaceholder("history"),HumanMessagePromptTemplate.fromTemplate("{input}"),
]);const model = new ChatOllama({model: "llama3",temperature: 0.7,
});const parser = new StringOutputParser();const chain = pt.pipe(model).pipe(parser);const store = new Map();const withHistoryChain = new RunnableWithMessageHistory({runnable: chain,getMessageHistory: (sessionId) => {if (!store.has(sessionId)) {store.set(sessionId, new ChatMessageHistory());}return store.get(sessionId);},inputMessagesKey: "input",historyMessagesKey: "history",
});const config = {configurable: {sessionId: "1234567890",},
};await withHistoryChain.invoke({input: "Hello, what is Rustlang?",},config
);const res = await withHistoryChain.invoke({input: "what did we just talk about?",},config
);console.log(res);

stream() 方法,方法签名如下:

stream(input: Input,options?: RunnableConfig
): AsyncGenerator<StreamEvent<Output>>

1. 输入参数 (input)

类型:Input

invoke() 方法保持一致:

  • 如果是 LLM:可以传字符串、BaseMessageBaseMessage[]
  • 如果是 Chain / Runnable:则是该 Chain 约定的输入对象(例如 { input: "..." }
  • 如果是 Embeddings:通常是字符串或字符串数组

换句话说,input 的类型由具体的 Runnable 实例 决定。

2. 配置参数

类型:RunnableConfig(可选)
常见字段包括:

  • configurable:运行时传入的上下文配置(例如用户 ID、对话 ID,用于内存/持久化关联)。
  • tags:给运行标记,用于调试、Tracing。
  • metadata:附加元信息,方便日志或监控。
  • callbacks:传入回调函数(如 handleLLMNewToken 等),可用于实时处理 token。
  • maxConcurrency:并发控制。
  • timeout:超时设置。
import { ChatMessageHistory } from "@langchain/classic/memory";
import {ChatPromptTemplate,HumanMessagePromptTemplate,MessagesPlaceholder,SystemMessagePromptTemplate,
} from "@langchain/core/prompts";
import { ChatOllama } from "@langchain/ollama";
import readline from "readline-sync";
import { HumanMessage, AIMessage } from "@langchain/core/messages";// 1. 模型
const model = new ChatOllama({model: "llama3",temperature: 0.7,
});// 2. 提示词
const pt = ChatPromptTemplate.fromMessages([SystemMessagePromptTemplate.fromTemplate("你是一个健谈的中文 AI 助手,请结合上下文尽可能详细地使用中文回答用户问题。"), // 系统提示词new MessagesPlaceholder("history"), // 会话的历史记录,一开始是一个占位符HumanMessagePromptTemplate.fromTemplate("{input}"), // 用户输入的内容
]);// 3. 存储会话历史
const history = new ChatMessageHistory();// 4. 创建一个chain
const chain = pt.pipe(model);async function chatLoop() {console.log("开始会话,输入内容后回车;输入 /clear 清空历史,/exit 退出。");while (true) {const input = readline.question("用户:").trim();if (!input) continue;if (input === "/exit") {console.log("拜拜");break;}if (input === "/clear") {await history.clear();console.log("历史记录已清空");continue;}let fullRes = ""; // 记录完整的信息try {const values = {input, // 用户本次的输入history: await history.getMessages(), // 获取之前会话记录};const stream = chain.streamEvents(values, { version: "v2" });process.stdout.write("助理:");for await (const event of stream) {if (event.event === "on_chat_model_stream") {process.stdout.write(event.data?.chunk?.content || "");}}console.log("\n");} catch (err) {console.error("调用大模型失败☹️", err);}// 将本轮会话记录到历史里面await history.addMessage(new HumanMessage(input));await history.addMessage(new AIMessage(fullRes));}
}
chatLoop();

实战案例

把上节课的对话练习改为 RunnableWithMessageHistory

import { ChatMessageHistory } from "@langchain/classic/memory";
import {ChatPromptTemplate,HumanMessagePromptTemplate,MessagesPlaceholder,SystemMessagePromptTemplate,
} from "@langchain/core/prompts";
import { ChatOllama } from "@langchain/ollama";
import readline from "readline-sync";
import { RunnableWithMessageHistory } from "@langchain/core/runnables";
import { StringOutputParser } from "@langchain/core/output_parsers";// 1. 模型
const model = new ChatOllama({model: "llama3",temperature: 0.7,
});// 2. 提示词
const pt = ChatPromptTemplate.fromMessages([SystemMessagePromptTemplate.fromTemplate("你是一个健谈的中文 AI 助手,请结合上下文尽可能详细地使用中文回答用户问题。"), // 系统提示词new MessagesPlaceholder("history"), // 会话的历史记录,一开始是一个占位符HumanMessagePromptTemplate.fromTemplate("{input}"), // 用户输入的内容
]);// 3. 存储会话历史
const history = new ChatMessageHistory();const parser = new StringOutputParser();// 4. 创建一个chain
const chain = pt.pipe(model).pipe(parser);const store = new Map();const withHistoryChain = new RunnableWithMessageHistory({runnable: chain,getMessageHistory: (sessionId) => {if (!store.has(sessionId)) {store.set(sessionId, new ChatMessageHistory());}return store.get(sessionId);},inputMessagesKey: "input",historyMessagesKey: "history",
});const config = {configurable: {sessionId: "1234567890",},
};async function chatLoop() {console.log("开始会话,输入内容后回车;输入 /clear 清空历史,/exit 退出。");while (true) {const input = readline.question("用户:").trim();if (!input) continue;if (input === "/exit") {console.log("拜拜");break;}if (input === "/clear") {const { sessionId } = config.configurable;store.set(sessionId, new ChatMessageHistory());console.log("历史记录已清空");continue;}try {const stream = await withHistoryChain.stream({ input }, config);process.stdout.write("助理:");for await (const chunk of stream) {process.stdout.write(chunk);}console.log("\n");} catch (err) {console.error("调用大模型失败☹️", err);}}
}
chatLoop();

-EOF-

http://www.jsqmd.com/news/40298/

相关文章:

  • 二进制掩码规律
  • jenkins构建生成docker镜像
  • 在线文档大全
  • AI大事记12:Transformer 架构——重塑 NLP 的革命性技能(下)
  • 记一次多线程插入或者更新数据库表操作优化过程
  • 2025年进口干冰机代理工厂权威推荐榜单:干冰清洗机/干冰制造机源头厂家精选
  • 接口调试利器,Postman免安装,免登陆 - 详解
  • 微算法科技(NASDAQ MLGO)在委托权益证明DPoS主链上引入PoW轻节点验证,提升抗量子攻击能力
  • 字的bi-gram可能是个馊主意
  • 常见的几种硬盘接口类型
  • 2025年w70钨铜棒制造企业权威推荐榜单:钨铜导电块/钨铜块/93钨合金源头厂家精选
  • 嵌入式系统profinet转devicenet固件与硬件接口的连接案例
  • KMPlayer下载教程(2025新版)——全功能安装配置与使用经验详解
  • 一个通过强制使用符号来避免链接器忽略符号的方法
  • 安卓非原创--基于Android Studio 实现的天气预报App - 教程
  • 10.7万条轨迹+4大机器人构型!RoboMIND开源数据集破解机器人通用操作难题 | 附一键复现指南
  • 2025年全屋定制橱柜优质厂家权威推荐榜单:全屋定制门窗/高端整装定制/整装全屋定制源头厂家精选
  • c++初学者的随笔记录_4
  • 2025 最新多孔筋增强管生产线厂家权威推荐:国际测评认证 + 技术创新双驱,全周期服务优选榜单缠绕管承插口生产线 / 承插口注塑设备生产线公司推荐
  • 自动化控制Devicenet转Profinet—PLC分布式控制架构的网关连接案例
  • 2025年专业的卷被机工厂权威推荐榜单:好的卷被机/不错的卷被机/卷被机品牌厂家精选
  • 工业网络通信中profinet转devicenet基于总线协议转换的网关连接技术研究
  • 2025 年 11 月 Pogopin 弹簧针厂家推荐排行榜,精密测试针,医疗传感器,手机连接器,声学弹簧,触摸仪表,手表锁具,座椅检测优质公司推荐
  • 国标GB28181算法算力平台EasyGBS如何赋能现代应急指挥体系?
  • 2025 年钢结构源头厂家最新推荐排行榜:聚焦全产业链服务与核心产能,七大实力企业权威甄选
  • xcode 打包 报错 main.jsbundle does not exist.
  • 2025年简易激光切管机供应商权威推荐榜单:高速激光切管机/拉料式激光切管机/迷你激光切管机设备源头厂家精选
  • 2025年东莞东城搬家公司权威推荐榜单:同城搬运/长安搬家/本地搬家源头公司精选
  • hgg
  • 2025 年 11 月高尔夫学院最新推荐榜单,高尔夫培训,高尔夫教学,高尔夫教练,专业指导与课程体验深度解析