🌈在当前人工智能应用开发中,大语言模型(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);
这段代码做了两件事:
- 第一次调用告诉模型:“我叫 Jing_Rainbow,喜欢彩虹”。
- 第二次调用问模型:“我叫什么名字?”
但由于每次 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.js 和 1.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助手了!