Skip to content

自动维护聊天记录 ​

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

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

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

快速上手 ​

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

  • RunnableLambda
  • RunnableMap
  • RunnableSequence
  • RunnablePassthrough

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

js
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::当链输出是对象时,指定对象里哪一个字段是消息(否则默认把顶层输出当消息)。

课堂演示

快速上手示例

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

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

1. 输入参数 (input)

类型:Input

与 invoke() 方法保持一致:

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

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

2. 配置参数

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

  • configurable:运行时传入的上下文配置(例如用户 ID、对话 ID,用于内存/持久化关联)。
  • tags:给运行标记,用于调试、Tracing。
  • metadata:附加元信息,方便日志或监控。
  • callbacks:传入回调函数(如 handleLLMNewToken 等),可用于实时处理 token。
  • maxConcurrency:并发控制。
  • timeout:超时设置。

实战案例 ​

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

快速上手 ​

js
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";
import { AIMessage, HumanMessage } from "langchain";

// 模型
const model = new ChatOllama({
    model: "llama3.2:latest",
    temperature: 0.7,
});

//  提示词
const pt = ChatPromptTemplate.fromMessages([
    SystemMessagePromptTemplate.fromTemplate(
        "你是一个健谈的中文 AI 助手,请结合上下文尽可能详细地使用中文回答用户问题",
    ), // 系统提示词
    new MessagesPlaceholder("history"), // 会话的历史记录,一开始是一个占位符
    HumanMessagePromptTemplate.fromTemplate("{input}"), // 用户输入内容
]);

// 解析器
const parser = new StringOutputParser();

//  创建链条
pt.pipe(model).pipe(parser);

const store = new Map(); // 为每一个sessionId 制定一个 chatMessageHistory 的实例对象

const withHistoryChain = new RunnableWithMessageHistory({
    runnable: pt.pipe(model).pipe(parser),
    getMessageHistory: async (sessionId) => {
        if (!store.has(sessionId)) {
            store.set(sessionId, new ChatMessageHistory());
        }
        return store.get(sessionId);
    },
    inputMessagesKey: "input", // 用户输入的内容在输入对象中的key
    historyMessagesKey: "history", // 会话历史在输入对象中的key
});

const cfg = {
    configurable: { sessionId: "zhangsan-sessions" },
};

await withHistoryChain.invoke(
    {
        input: "你知道大象吗",
    },
    cfg,
);

const res = await withHistoryChain.invoke(
    {
        input: "刚才我们聊了什么",
    },
    cfg,
);
console.log("🚀 ~ index1.js:59 ~ res:", res);