Skip to content

Memory基础 ​

大模型本身是无状态的,每次调用都只看当前输入。如果要在多轮对话中维持上下文,就需要 Memory 模块来存储和管理对话历史。

LangChain.js 针对 Memory 提供了多个工具类,先来学习最常用的 ChatMessageHistory

快速上手案例:

js
import { ChatMessageHistory } from "langchain/stores/message/in_memory";
import {
  HumanMessage,
  AIMessage,
  isAIMessage,
  isHumanMessage,
} from "@langchain/core/messages";

const history = new ChatMessageHistory();

await history.addMessage(new HumanMessage("你好,我叫小明"));
await history.addMessage(new AIMessage("你好,小明!有什么我可以帮助你的吗?"));

const messages = await history.getMessages();

messages.forEach((msg, i) => {
  const role = isHumanMessage(msg)
    ? "用户"
    : isAIMessage(msg)
    ? "AI"
    : "❓ Unknown";

  console.log(`[${i + 1}] ${role} ➜ ${msg.content}`);
});

常用方法

文档:https://v03.api.js.langchain.com/classes/_langchain_core.chat_history.BaseChatMessageHistory.html

  1. addMessage:添加消息
  2. addMessages:添加消息组
  3. getMessages:获取所有消息
  4. clear:清除所有消息

实战案例

和大模型进行对话,大模型可以记住前面的对话内容

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

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

1. 输入参数 (input)

类型:Input

与 invoke() 方法保持一致:

  • 如果是 LLM:可以传字符串、BaseMessage、BaseMessage[]

    js
    const model = new Ollama(model: "llama3");
    model.stream("你好");
  • 如果是 Chain / Runnable:则是该 Chain 约定的输入对象(例如 { input: "..." })

  • 如果是 Embeddings:通常是字符串或字符串数组

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

2. 配置参数

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

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

readline-sync 包:接收用户在控制台的输入,这边是同步的

实战示例 ​

js
import { ChatMessageHistory } from "@langchain/classic/memory";
import {
    ChatPromptTemplate,
    HumanMessagePromptTemplate,
    MessagesPlaceholder,
    SystemMessagePromptTemplate,
} from "@langchain/core/prompts";
import { ChatOllama } from "@langchain/ollama";
import { AIMessage, HumanMessage } from "langchain";
import readlineSync from "readline-sync";

// 1. 模型
const model = new ChatOllama({
    model: "llama3.2:latest",
    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 = readlineSync.question("user:").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 = await chain.stream(values);
            process.stdout.write("助理:");
            for await (const chunk of stream) {
                process.stdout.write(chunk.content);
                fullRes += chunk.content;
            }
            console.log("\n");
        } catch (err) {
            console.log("报错", err);
        }

        // 将本轮会话记录到历史里面
        await history.addMessage(new HumanMessage(input));
        await history.addMessage(new AIMessage(fullRes));
    }
}

chatLoop();