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

news/2025/11/14 15:02:30/文章来源:https://www.cnblogs.com/Answer1215/p/19222127

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

  • 把用户发言添加到 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.mzph.cn/news/965412.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年燃生物质有机热载体锅炉生产厂家权威推荐榜单:燃生物质热水锅炉/生物质专用锅炉/生物质热水锅炉源头厂家精选

在“双碳”目标持续推进的背景下,燃生物质有机热载体锅炉凭借其清洁、可再生、低碳排放的特性,正成为工业供热领域的重要选择。 根据行业报告数据,2025年生物质能在中国可再生能源消费中的占比预计将稳步提升,其中…

二进制掩码规律

& 0x1 = & 0b1 → 保留 1 位 (范围: 0-1) & 0x3 = & 0b11 → 保留 2 位 (范围: 0-3) & 0x7 = & 0b111 → 保留 3 位 (范围: 0-7) & 0xF = & 0b1111 …

jenkins构建生成docker镜像

pipeline {agent anyenvironment {CODE_DIR = "/jenkins_data/springboot_test"DATE = new Date().format(YYYYMMddHHmmss)TAG = "${DATE}"IMAGE_NAME = springtestIMAGE_NAME_ALIYUN = "reg…

2025年复合风管板权威推荐榜单:铝箔复合风管/酚醛复合风管/彩钢酚醛复合风管源头厂家精选

复合风管板作为现代建筑通风系统中不可或缺的核心材料,其市场需求正随着绿色建筑标准的提升和建筑节能要求的加强而持续增长。本文将基于详实的行业数据,为您推荐2025年度在复合风管板领域表现卓越的Top 3制造厂,通…

在线文档大全

go文档 go高级编程 go中文文档 go框架gin go-zero 开源项目地址 https://github.com/Mikaelemmmm/go-zero-looklook goframeweb3.0

AI大事记12:Transformer 架构——重塑 NLP 的革命性技能(下)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

记一次多线程插入或者更新数据库表操作优化过程

背景:有个数百万的数据,需要尽快入库,使用了多线程处理,先查询数据库是否存在,存在则更新;否则插入; 问题:数据库相同key的数据,有时候插入多条。 解决办法:String lockKey =getLockKey(t); //根据md5算法,…

2025年进口干冰机代理工厂权威推荐榜单:干冰清洗机/干冰制造机源头厂家精选

工业干冰机作为现代清洁与表面处理领域的重要设备,其市场需求正随着制造业升级和环保要求的提升而持续增长。本文将基于详实的行业视角,为您推荐2025年度在进口干冰机代理领域表现卓越的Top 3服务商,通过客观分析和…

接口调试利器,Postman免安装,免登陆 - 详解

接口调试利器,Postman免安装,免登陆 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &qu…

微算法科技(NASDAQ MLGO)在委托权益证明DPoS主链上引入PoW轻节点验证,提升抗量子攻击能力

随着量子计算技术的飞速发展,传统区块链的加密算法面临着前所未有的威胁。量子计算机强大的计算能力有可能在短时间内破解现有的加密机制,导致区块链的安全性遭受重创。委托权益证明(DPoS)作为一种高效的共识机制,…

字的bi-gram可能是个馊主意

续之前的贴子,我们有了部精修词典,二至六字词共169872个。 foreach 词,用字的2-gram去检查首选是否正确,结果很差,可在〔这里〕下载 123094个不一样的。部分结果:䴔䴖 交警 𫘝𫘨 抉剔 吖嗪 阿嗪 腌菜 言采 腌…

常见的几种硬盘接口类型

常见的几种硬盘接口类型IDE 接口:40 个针脚,通过 PATA 协议控制数据传输理论最大传输速率 133MB/s,实际更低,已经被淘汰了。SATA 接口:22 个针脚,15 针用于供电,7 针用于数据传输。通过AHCI 协议控制数据传输,…

2025年w70钨铜棒制造企业权威推荐榜单:钨铜导电块/钨铜块/93钨合金源头厂家精选

在高端制造业快速发展的背景下,W70钨铜棒作为关键功能材料,其市场需求持续增长。根据QYResearch最新报告显示,全球钨铜合金市场规模预计将在2025年达到新高度,年复合增长率保持在5.8% 的稳定水平。 W70钨铜棒凭借其…

嵌入式系统profinet转devicenet固件与硬件接口的连接案例

本案例适用于智能仓储物流系统,西门子S7-1200PLC通过Profinet连接DeviceNet主站网关,网关下联DeviceNet从站型AGV(自动导引车)驱动模块和扫码枪,实现AGV的路径控制与货物信息采集。核心需求是基于网关的嵌入式固件…

Proxmox VE9.0优化-功耗切换到智能模式

本文介绍了如何通过脚本切换Proxmox VE9.0主机功耗模式模式介绍 PVE9.0目前支持下面几种模式,初始安装默认是performance。performance (高性能, 频率接近最大值。PVE默认/推荐) powersave (低功耗, 频率接近最低值) …

KMPlayer下载教程(2025新版)——全功能安装配置与使用经验详解

前言: 在多媒体工具领域,KMPlayer 一直是一款被广泛使用的视频播放器。 无论是从兼容性、解码能力还是自由性来看,它都在同类播放器中有着不错的口碑。尤其是面对如今格式多样化、高码率视频普及的情况,一个性能稳…

一个通过强制使用符号来避免链接器忽略符号的方法

一个通过强制使用符号来避免链接器忽略符号的方法 虽然如果链接器在链接库时将符号忽略了一般是由于设置了 --as-needed 选项,或者编译时的优化太激进了,所以一般还是要去分析编译和链接过程来解决。 但如果确实不好…

安卓非原创--基于Android Studio 实现的天气预报App - 教程

安卓非原创--基于Android Studio 实现的天气预报App - 教程2025-11-14 14:44 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !importan…

10.7万条轨迹+4大机器人构型!RoboMIND开源数据集破解机器人通用操作难题 | 附一键复现指南

开发鲁棒且通用的操作策略是机器人领域的关键目标。为实现有效的泛化能力,构建包含大量 演示轨迹 和 在复杂真实环境中完成多样化任务 的综合数据集至关重要。尽管现有研究已致力于整合各类机器人数据集,但仍 缺乏统…

2025年全屋定制橱柜优质厂家权威推荐榜单:全屋定制门窗/高端整装定制/整装全屋定制源头厂家精选

在消费升级与居住需求多元化的推动下,全屋定制行业正迎来新一轮发展机遇。根据行业数据显示,2025年中国定制家居市场规模预计将突破5000亿元,其中橱柜品类作为核心组成部分,市场份额占比稳定在30%以上。 全屋定制橱…