🧠 破解无状态困局:如何用 LangChain 为 DeepSeek 大模型注入“记忆”能力

🧠 破解无状态困局:如何用 LangChain 为 DeepSeek 大模型注入"记忆"能力

摘要: 在开发基于 LLM(大语言模型)的应用时,我们常遇到一个痛点:模型似乎得了"失忆症"。上一秒你告诉它你的名字,下一秒提问它却一问三不知。这是因为底层的 HTTP API 请求本质上是**无状态(Stateless)**的。本文将结合代码实战,带你从零开始,利用 LangChain 框架,为 DeepSeek 模型构建一套高效的记忆系统。


1. 痛点分析:为什么模型总是"金鱼脑"?

在最基础的 API 调用中(如文档 index.js 所示),每一次请求都是独立的事件。

javascript 复制代码
// index.js - 无记忆的调用
const res = await model.invoke('我叫张三,我喜欢打游戏'); 
const res2 = await model.invoke('我叫什么名字'); // 模型不知道上下文,回答失败

问题本质: LLM 本身并不存储用户的历史对话。每一次 model.invoke 都是一次全新的对话。模型只能看到当前这一次发送的 Token,无法感知之前的交互。这就是所谓的"无状态"特性。


2. 解决方案:维护对话历史(Memory)

既然模型没有记忆,我们就需要在应用层手动维护一份"对话历史记录(Messages History)"。核心思路是**"滚雪球"策略**:将过往的所有对话(用户输入 + 模型回复)打包,作为上下文(Context)一起发送给模型。

根据 readme.md 的原理,我们需要构建如下结构的数据:

json 复制代码
messages = [
  { role: 'user', content: '我叫张三,喜欢喝白兰地' },
  { role: 'assistant', content: '好的,我知道了。' },
  { role: 'user', content: '你知道我是谁吗?' } // 模型此时能看到之前的自我介绍
]

3. 代码实战:构建有记忆的 DeepSeek 助手

虽然手动拼接 Messages 数组可行,但这非常繁琐且难以管理。LangChain 提供了优雅的封装。参考文档 1.js,我们将演示如何利用 RunnableWithMessageHistory 实现自动化记忆管理。

核心组件架构:

组件 作用
ChatDeepSeek 指定使用的模型(DeepSeek)及参数(Temperature)。
ChatPromptTemplate 构建提示词模板,预留 {history} 占位符。
InMemoryChatMessageHistory 记忆的"容器",用于存储特定会话的历史记录。
RunnableWithMessageHistory 核心逻辑层,自动将历史记录注入到当前请求中。

实现步骤详解:

第一步:初始化模型与提示词 首先,我们需要定义模型,并在提示词中明确告诉 AI 我们要引入历史记录。

javascript 复制代码
import { ChatDeepSeek } from "@langchain/deepseek";
import { ChatPromptTemplate } from "@langchain/core/prompts";

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

// 关键点:在提示词中加入 {history} 占位符
const prompt = ChatPromptTemplate.fromMessages([
  ['system', '你是一个有记忆的助手'],
  ['placeholder', '{history}'], // 历史记录将自动插入此处
  ['human', '{input}']
]);

第二步:构建带记忆的执行链(Chain) 这是最关键的一步。我们使用 RunnableWithMessageHistory 将模型、历史存储和提示词串联起来。

javascript 复制代码
import { RunnableWithMessageHistory } from "@langchain/core/runnables";
import { InMemoryChatMessageHistory } from "@langchain/core/chat_history";

// 1. 创建历史记录容器(通常在生产环境中会替换为数据库)
const messageHistory = new InMemoryChatMessageHistory();

// 2. 构建核心执行链
const chain = new RunnableWithMessageHistory({
  runnable: prompt.pipe(model), // 定义处理流程:Prompt -> Model
  getMessageHistory: async () => messageHistory, // 指定历史存储位置
  inputMessagesKey: "input", // 输入键名
  historyMessagesKey: "history" // 历史键名(对应Prompt中的占位符)
});

第三步:多轮对话测试 现在,我们可以通过保持相同的 sessionId 来维持上下文连贯性。

javascript 复制代码
// 第一次对话:建立记忆
const res1 = await chain.invoke(
  { input: "我叫张三,喜欢打游戏" },
  { configurable: { sessionId: "makefriend" } } // 会话ID
);

// 第二次对话:利用记忆
const res2 = await chain.invoke(
  { input: "我叫什么名字" },
  { configurable: { sessionId: "makefriend" } } // 必须使用相同的ID
);

console.log(res2.content); // 输出:你叫张三。

4. 深度解析:记忆背后的机制

  1. Session ID 的作用 : 在上述代码中,sessionId 就像是一个"钥匙"。LangChain 利用这个 ID 来区分不同的用户或不同的对话场景。如果 ID 不同,模型就会开启一段全新的、互不干扰的对话。

  2. Token 开销的权衡(Context Window) : 文档 readme.md 提到了一个重要的工程考量:"滚雪球"效应 。 随着对话轮次增加,历史记录会越来越长,这会消耗大量的 Token 预算,增加成本并拖慢响应速度。虽然本文使用的是 InMemoryChatMessageHistory(内存存储,适合演示),但在实际生产中,你可能需要考虑:

    • 窗口化记忆(Window Memory):只保留最近 N 轮对话。
    • 摘要记忆(Summary Memory):将长历史总结成几句话传给模型。
    • 向量数据库:存储海量历史,仅在需要时检索相关片段。

5. 总结

通过本文的实践,我们成功解决了 LLM 无状态的问题。利用 LangChain 的 RunnableWithMessageHistory,我们不仅让 DeepSeek 模型记住了用户的名字,更建立了一套可复用的上下文管理框架

核心启示:

大模型的"智能"不仅取决于其参数量,更取决于你如何喂给它数据。通过精心设计的 PromptHistory 管理,你可以将一个"金鱼脑"的模型,变成一个拥有长期记忆的私人助理。

希望这篇教程能助你在 AI 应用开发的道路上更进一步!🚀

相关推荐
码路飞2 小时前
AI 编程怎么选模型?Claude、GPT-5.4、DeepSeek 我全试了,这是我的真实体验
人工智能·claude
镜花水月linyi2 小时前
一口气讲清楚 Agent、RAG、Skill、MCP 到底是什么?
人工智能·agent·mcp
Narrastory2 小时前
明日香 - Pytorch 快速入门保姆级教程(九)
人工智能·pytorch·深度学习
Codebee2 小时前
企业微信、钉钉、飞书三大平台的IM Skills与Apex深度融合
人工智能
用户5757303346242 小时前
🚀 告别“意大利面条”代码:用 LangChain 像搭乐高一样玩转大模型
人工智能
蕤葳-2 小时前
深度解析:基于AI人才标准,为职场新人规划一级与二级认证的报考路径
人工智能
只与明月听2 小时前
RAG深入学习之向量数据库
前端·人工智能·python
月诸清酒2 小时前
别让你的 Coding Agent 瞎忙活,你最缺的可能是这套 Harness 规则
人工智能
极客老王说Agent2 小时前
别被OpenClaw的30万Star晃了眼!AI产业逻辑重写后,打工人更该看清谁在“真干活”
人工智能·ai·chatgpt