让大语言模型拥有"记忆":多轮对话与 LangChain 实践指南
在当前人工智能应用开发中,大语言模型(LLM)因其强大的自然语言理解与生成能力,被广泛应用于聊天机器人、智能客服、个人助理等场景。然而,一个常见的问题是:LLM 本身是无状态的------每一次 API 调用都独立于前一次,就像每次 HTTP 请求一样,模型无法"记住"你之前说过什么。
那么,如何让 LLM 拥有"记忆",实现真正的多轮对话体验?本文将从原理出发,结合 LangChain 框架的实践代码,深入浅出地讲解这一关键技术。
一、为什么 LLM 默认没有记忆?
大语言模型(如 DeepSeek、GPT、Claude 等)在设计上遵循"输入-输出"模式。当你调用其 API 时,仅传递当前的问题或指令,模型不会自动保留之前的交互内容。例如:
ini
ts
编辑
const res1 = await model.invoke('我叫王源,喜欢喝白兰地');
const res2 = await model.invoke('我叫什么名字?');
第二次调用时,模型完全不知道"王源"是谁,因此很可能回答:"我不知道你的名字。"
这是因为两次调用之间没有任何上下文传递。
二、实现"记忆"的核心思路:维护对话历史
要让 LLM 表现出"有记忆"的行为,最直接的方法是:在每次请求中显式传入完整的对话历史 。通常以 messages 数组的形式组织:
css
json
编辑
[ {"role": "user", "content": "我叫王源,喜欢喝白兰地"}, {"role": "assistant", "content": "很高兴认识你,王源!白兰地是很优雅的选择。"}, {"role": "user", "content": "你知道我是谁吗?"}]
模型通过分析整个对话上下文,就能准确回答:"你是王源,喜欢喝白兰地。"
但这种方法存在明显问题:随着对话轮次增加,输入 token 数量不断膨胀,不仅增加计算成本,还可能超出模型的最大上下文长度限制(如 32768 tokens)。这就像滚雪球,越滚越大。
三、LangChain 提供的解决方案:模块化记忆管理
为了解决上述问题,LangChain 等 AI 应用框架提供了专门的 Memory 模块,帮助开发者高效管理对话历史,并支持多种存储策略(内存、数据库、向量化摘要等)。
以下是一个使用 @langchain/deepseek 和 RunnableWithMessageHistory 的完整示例:
1. 初始化模型与提示模板
javascript
ts
编辑
import { ChatDeepSeek } from '@langchain/deepseek';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import { RunnableWithMessageHistory } from '@langchain/core/runnables';
import { InMemoryChatMessageHistory } from '@langchain/core/chat_history';
const model = new ChatDeepSeek({
model: 'deepseek-chat',
temperature: 0,
});
// 定义包含历史记录占位符的提示模板
const prompt = ChatPromptTemplate.fromMessages([
['system', '你是一个有记忆的助手'],
['placeholder', '{history}'], // 历史消息将插入此处
['human', '{input}']
]);
2. 构建带记忆的可运行链
dart
ts
编辑
const runnable = prompt.pipe(model);
// 创建内存中的对话历史存储
const messageHistory = new InMemoryChatMessageHistory();
// 封装成支持会话记忆的链
const chain = new RunnableWithMessageHistory({
runnable,
getMessageHistory: async () => messageHistory,
inputMessagesKey: 'input',
historyMessagesKey: 'history',
});
3. 执行多轮对话
php
ts
编辑
// 第一轮:用户自我介绍
const res1 = await chain.invoke(
{ input: '我叫王源,喜欢喝白兰地' },
{ configurable: { sessionId: 'makefriend' } }
);
console.log(res1.content); // "你好,王源!白兰地确实很经典。"
// 第二轮:提问名字
const res2 = await chain.invoke(
{ input: '我叫什么名字?' },
{ configurable: { sessionId: 'makefriend' } }
);
console.log(res2.content); // "你叫王源。"
✅ 关键点:
sessionId用于区分不同用户的会话。同一个sessionId下的所有交互共享同一段历史。
四、进阶思考:如何优化长对话的记忆效率?
虽然内存存储简单易用,但在生产环境中,面对海量用户和长期对话,我们需要更高效的策略:
- 滑动窗口记忆:只保留最近 N 轮对话。
- 摘要压缩:定期将历史对话总结成一段摘要,替代原始记录。
- 向量数据库 + 语义检索:将关键信息存入向量库,按需检索相关上下文(适用于知识密集型对话)。
- 混合记忆:结合短期(最近几轮)+ 长期(摘要/数据库)记忆。
LangChain 已支持多种 Memory 类型,如 BufferWindowMemory、SummaryMemory、VectorStoreRetrieverMemory 等,可根据场景灵活选择。
五、结语
让 LLM 拥有"记忆",本质上是将无状态的模型调用转化为有状态的会话系统。通过维护对话历史并合理控制上下文长度,我们可以在成本与体验之间取得平衡。
LangChain 等框架极大简化了这一过程,使开发者能专注于业务逻辑,而非底层状态管理。未来,随着上下文窗口的扩大和记忆机制的智能化(如自动遗忘、重点记忆),AI 助手将越来越像一个真正"记得你"的朋友。
正如我们在代码中看到的那样------当模型说出"你叫王源"时,那一刻,它仿佛真的记住了你。