【AI-24 LangChain-3/Lesson84(2025-12-25)让大语言模型拥有记忆:基于 LangChain 与 LLM 的多轮对话实现详解🌈

13 阅读7分钟

🌈在当前人工智能应用开发中,大语言模型(LLM)已成为构建智能助手、客服系统、内容生成器等产品的核心引擎。然而,一个关键的挑战在于:LLM 本质上是无状态的。每一次 API 调用都像是一次“初次见面”——模型无法记住你上一句话说了什么,除非你主动告诉它。

那么,如何让 LLM “记住”用户的历史对话?这正是本文要深入探讨的核心问题。我们将以 LangChain 框架为工具,结合 DeepSeek 大模型,从最基础的无状态调用出发,逐步构建一个具备持久对话记忆能力的智能聊天系统。所有代码均基于真实可运行的示例,并辅以详细原理剖析。


🔌 无状态调用:LLM 的“健忘症”

首先,让我们看看最原始的 LLM 调用方式。以下代码来自 index.js

import 'dotenv/config';
import { ChatDeepSeek } from "@langchain/deepseek";

const model = new ChatDeepSeek({ 
  model: 'deepseek-chat', 
  temperature: 0 
});

const res = await model.invoke('我叫Jing_Rainbow,喜欢彩虹');
console.log(res.content);

console.log('--------------------');

const res2 = await model.invoke('我叫什么名字');
console.log(res2.content);

这段代码做了两件事:

  1. 第一次调用告诉模型:“我叫 Jing_Rainbow,喜欢彩虹”。
  2. 第二次调用问模型:“我叫什么名字?”

但由于每次 invoke 都是独立的 HTTP 请求,模型并不知道两次调用属于同一个用户或同一段对话。因此,第二次调用时,模型没有上下文,只能根据通用知识回答,很可能回复“我不知道”或胡乱猜测。

💡 关键点:LLM 的 API 调用本身不具备会话状态管理能力。每一次请求都是“从零开始”。

这就是典型的“无状态”行为——就像你每次走进一家咖啡店,店员都不记得你上次点了什么。要解决这个问题,我们必须手动维护对话历史,并在每次请求时将历史信息一并发送给模型。


📚 对话历史:让模型“记住”你说过的话

为了让模型具备记忆,我们需要在每次调用时,将之前的对话记录(即 messages 数组)作为上下文传入。例如:

[
  {"role": "user", "content": "我叫Jing_Rainbow,喜欢彩虹"},
  {"role": "assistant", "content": "很高兴认识你,Jing_Rainbow!彩虹真美。"},
  {"role": "user", "content": "我叫什么名字?"}
]

这样,模型就能从上下文中提取“Jing_Rainbow”这个关键信息,并正确回答。

但手动拼接消息不仅繁琐,还容易出错。更严重的是,随着对话轮数增加,消息长度会不断增长,导致 Token 消耗剧增,甚至超出模型的最大上下文窗口(如 DeepSeek 的 32768 tokens)。因此,我们需要一个结构化、可扩展、且能自动管理历史的解决方案。

这就是 LangChain 登场的时机。


🧩 LangChain:构建有记忆的 AI 应用框架

LangChain 是一个专为 LLM 应用开发设计的开源框架,提供了模块化组件,帮助开发者轻松处理提示工程、记忆管理、工具调用等复杂任务。

1.js 中,我们使用了 LangChain 的几个核心模块来实现带记忆的对话:

✅ 核心组件解析

1. ChatPromptTemplate:结构化提示模板

const prompt = ChatPromptTemplate.fromMessages([
  ['system', "你是一个有记忆的助手"],
  ['placeholder', "{history}"],
  ['human', "{input}"]
]);
  • system 消息:设定模型角色和行为准则。
  • placeholder:占位符,用于动态插入对话历史。
  • human:当前用户的输入。

这个模板定义了每次发送给模型的完整消息结构。

2. InMemoryChatMessageHistory:内存中的对话历史存储

const messageHistory = new InMemoryChatMessageHistory();

这是一个简单的内存存储,用于保存某一会话(session)的所有消息。虽然只存在于内存中(程序重启即丢失),但对于演示和小型应用足够。

📌 在生产环境中,你可以替换为 Redis、数据库等持久化存储。

3. RunnableWithMessageHistory:带历史记录的可运行链

const chain = new RunnableWithMessageHistory({
  runnable,
  getMessageHistory: async () => messageHistory,
  inputMessagesKey: 'input',
  historyMessagesKey: 'history',
});

这个包装器自动完成以下工作:

  • 接收用户新输入(input
  • 获取对应会话的历史消息(通过 getMessageHistory
  • 将历史 + 新输入注入到 Prompt 模板中
  • 调用模型
  • 将模型回复追加到历史中(自动)

整个过程对开发者透明,极大简化了记忆管理逻辑。


🧪 实际运行效果对比

让我们分别看 index.js1.js 的输出差异。

index.js(无记忆)输出可能为:

你好!很高兴认识喜欢彩虹的你!🌈
--------------------
我不太确定你的名字,但你可以告诉我吗?

模型在第二次提问时完全“失忆”。

1.js(有记忆)输出为:

>>> 最终传给模型的信息(Prompt 内存)
{
  input: '我叫Jing_Rainbow,喜欢彩虹',
  history: []
}
你好!Jing_Rainbow,很高兴认识你!你喜欢彩虹,真是个充满色彩的名字!🌈
--------------------
>>> 最终传给模型的信息(Prompt 内存)
{
  input: '我叫什么名字',
  history: [
    { role: 'user', content: '我叫Jing_Rainbow,喜欢彩虹' },
    { role: 'ai', content: '你好!Jing_Rainbow,很高兴认识你!你喜欢彩虹,真是个充满色彩的名字!🌈' }
  ]
}
你的名字是 Jing_Rainbow!😊

可以看到:

  • 第一次调用后,历史为空。
  • 第二次调用时,history 已包含前一轮的 user 和 ai 消息。
  • 模型成功从上下文中提取出名字并准确回答。

此外,代码中还加入了一个调试节点(console.log(">>> 最终传给模型的信息...")),帮助开发者清晰看到实际发送给模型的完整 Prompt,这对调试和优化至关重要。


🔑 环境变量与 API 安全

.env 文件中,我们配置了 DeepSeek 的 API 密钥:

DEEPSEEK_API_KEY=sk-xxx

通过 import 'dotenv/config';,该密钥被自动加载到 process.env.DEEPSEEK_API_KEY 中,供 ChatDeepSeek 类使用。

⚠️ 安全提示.env 文件绝不应提交到版本控制系统(如 Git)。务必将其加入 .gitignore


📖 总结:从无状态到有记忆的演进路径

阶段特点问题解决方案
原始 API 调用每次独立请求无法跨轮次记忆手动拼接历史消息
手动维护 messages可实现记忆代码冗余、易错、Token 膨胀使用 LangChain 抽象层
LangChain + Memory自动管理历史、结构清晰、可扩展内存存储不持久替换为 Redis/DB 存储

通过 LangChain 的 RunnableWithMessageHistory + InMemoryChatMessageHistory,我们仅用几十行代码就实现了会话级记忆,且代码结构清晰、易于维护。


🚀 进阶方向

虽然本文使用的是内存存储,但在真实应用中,你可能需要:

  • 持久化存储:使用 RedisChatMessageHistory 或自定义数据库适配器。
  • 会话隔离:通过 sessionId(如 'makefriend')区分不同用户或对话线程。
  • Token 压缩:当历史过长时,使用摘要(summarization)或滑动窗口(sliding window)策略裁剪历史。
  • 多模态记忆:未来可结合图像、音频等多模态信息构建更丰富的上下文。

🌈 结语

让 LLM 拥有记忆,不仅是技术实现的问题,更是用户体验的关键。一个能记住你偏好的助手,远比一个每次都“重置”的机器人更值得信赖。

通过 LangChain 这样的现代 AI 开发框架,我们得以站在巨人的肩膀上,快速构建出具备上下文感知能力的智能应用。而 DeepSeek 等国产大模型的崛起,也为开发者提供了高性能、低成本的推理选择。

正如用户名字 “Jing_Rainbow” 所象征的那样——记忆让对话如彩虹般绚烂多彩,而非转瞬即逝的雨滴。🌈

代码已验证可运行,环境依赖如下(见 readme.md):

npm init -y
# package.json 需设置 "type": "module"
pnpm i langchain @langchain/core @langchain/deepseek dotenv

现在,你也可以轻松打造属于自己的“有记忆”AI助手了!