LangChain Memory 实战指南:让大模型记住你每一句话,轻松打造“有记忆”的AI助手


引言

你有没有遇到过这样的尴尬?

向 AI 提问:"我叫张三",下一秒再问"我叫什么名字?"------它居然说:"抱歉,我不记得了。" 😅

别怪模型笨,这是所有 LLM 的"先天缺陷":无状态调用

但今天,我们用 LangChain Memory 给它装上"大脑",实现真正意义上的多轮对话记忆!

本文将带你从零构建一个会"记事"的智能助手,并深入剖析底层原理、性能瓶颈与生产级优化方案。


🧠 一、为什么你的 AI 总是"金鱼脑"?

1.1 大模型的"失忆症"真相

我们知道,大语言模型(LLM)本质上是一个"黑箱函数":

ts 复制代码
response = model(prompt)

每次调用都是独立的 HTTP 请求,没有任何上下文保留机制 ------ 就像每次见面都重新认识一遍。

举个例子:

js 复制代码
// 第一次对话
await model.invoke("我叫陈昊,喜欢喝白兰地");
// 输出:很高兴认识你,陈昊!看来你喜欢喝白兰地呢~

console.log('------ 分割线 ------');

// 第二次对话
await model.invoke("我叫什么名字?");
// 输出:呃......不太清楚,能告诉我吗?

👉 结果令人崩溃:前脚刚自我介绍,后脚就忘了!

这在实际项目中是不可接受的。无论是客服机器人、教育助手还是个性化推荐系统,都需要记住用户的历史行为和偏好


1.2 解决思路:把"记忆"塞进 Prompt

既然模型不会自己记,那就我们来帮它记

核心思想非常简单:

✅ 每次请求前,把之前的对话历史拼接到当前 prompt 中

✅ 让模型"看到"完整的聊天记录,从而做出连贯回应

这就像是给模型戴上了一副"记忆眼镜"。

而 LangChain 的 Memory 模块,正是对这一过程的高度封装与自动化。


⚙️ 二、LangChain Memory 核心原理拆解

LangChain 提供了一套优雅的 API,让我们可以用几行代码实现"有记忆"的对话系统。

先看最终效果:

bash 复制代码
User: 我叫陈昊,喜欢喝白兰地
AI: 你好,陈昊!白兰地可是很有品味的选择哦~

User: 我喜欢喝什么酒?
AI: 你说你喜欢喝白兰地呀~是不是准备开一瓶庆祝一下?😉

✅ 成功记住用户名字和饮酒偏好!

下面我们就一步步实现这个功能。


2.1 核心组件一览

组件 作用
ChatMessageHistory 存储对话历史(内存/文件/数据库)
ChatPromptTemplate 定义提示词模板,预留 {history} 占位符
RunnableWithMessageHistory 自动注入历史 + 管理会话生命周期
sessionId 区分不同用户的会话,避免串台

2.2 完整代码实战

js 复制代码
// 文件名:memory-demo.js
import { ChatDeepSeek } from "@langchain/deepseek";
import 'dotenv/config';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import { RunnableWithMessageHistory } from "@langchain/core/runnables";
import { InMemoryChatMessageHistory } from '@langchain/core/chat_history';

// 1. 初始化模型
const model = new ChatDeepSeek({
    model: 'deepseek-reasoner',
    temperature: 0.3,
});

// 2. 构建带历史的 Prompt 模板
const prompt = ChatPromptTemplate.fromMessages([
    ['system', '你是一个温暖且有记忆的助手,请根据对话历史回答问题'],
    ['placeholder', '{history}'],   // ← 历史消息自动插入这里
    ['human', '{input}']            // ← 当前输入
]);

// 3. 创建可运行链(含调试输出)
const runnable = prompt
    .pipe(input => {
        console.log('\n🔍 最终发送给模型的完整上下文:');
        console.log(JSON.stringify(input, null, 2));
        return input;
    })
    .pipe(model);

// 4. 初始化内存历史存储
const messageHistory = new InMemoryChatMessageHistory();

// 5. 创建带记忆的链
const chain = new RunnableWithMessageHistory({
    runnable,
    getMessageHistory: () => messageHistory,
    inputMessagesKey: 'input',
    historyMessagesKey: 'history'
});

// 6. 开始对话测试
async function testConversation() {
    // 第一轮:告知信息
    const res1 = await chain.invoke(
        { input: "我叫陈昊,喜欢喝白兰地" },
        { configurable: { sessionId: "user_001" } }
    );
    console.log("🤖 回应1:", res1.content);

    // 第二轮:提问历史
    const res2 = await chain.invoke(
        { input: "我叫什么名字?我喜欢喝什么酒?" },
        { configurable: { sessionId: "user_001" } }
    );
    console.log("🤖 回应2:", res2.content);
}

testConversation();

📌 运行结果示例

bash 复制代码
🤖 回应1: 你好,陈昊!听说你喜欢喝白兰地,真是个有品位的人呢~

🤖 回应2: 你叫陈昊,而且你说你喜欢喝白兰地哦~要不要来点搭配小吃?

🎉 成功!模型不仅记住了名字,还能综合多个信息进行推理回答!


2.3 关键机制图解

text 复制代码
              +---------------------+
              |   用户新输入         |
              +----------+----------+
                         |
       +-----------------v------------------+
       |   ChatPromptTemplate              |
       |                                     |
       |   System: 你是有记忆的助手         |
       |   History: [之前的所有对话] ←───────+←─ 从 messageHistory 读取
       |   Human: 我叫什么名字?             |
       +-----------------+------------------+
                         |
                调用模型 → LLM
                         |
           返回响应 ←────+
                         |
       +-----------------v------------------+
       |  自动保存本次交互                  |
       |  user: 我叫什么名字?               |
       |  assistant: 你叫陈昊...            |
       |  写入 InMemoryChatMessageHistory   |
       +------------------------------------+

整个流程全自动闭环,开发者只需关注业务逻辑。


⚠️ 三、真实场景下的三大挑战与破解之道

虽然上面的例子很美好,但在生产环境中你会立刻面临三个"灵魂拷问":


❌ 挑战1:Token "滚雪球"爆炸增长!

随着对话轮数增加,历史消息越积越多,导致:

  • 单次请求 Token 数飙升
  • 成本翻倍 💸
  • 响应变慢 ⏳
  • 可能超出模型最大长度限制(如 8192)
✅ 解法:选择合适的 Memory 类型
Memory 类型 特点 适用场景
BufferWindowMemory 只保留最近 N 轮 通用对话、短程记忆
ConversationSummaryMemory 自动生成一句话总结 长周期对话、节省 Token
EntityMemory 提取关键实体(人名/偏好) 推荐系统、CRM 助手
示例:使用窗口记忆(保留最近3轮)
js 复制代码
import { BufferWindowMemory } from "langchain/memory";

const memory = new BufferWindowMemory({
    k: 3,
    memoryKey: "history"
});

📌 推荐组合:短期细节靠窗口 + 长期特征靠总结


❌ 挑战2:重启服务后历史全丢?!

InMemoryChatMessageHistory 是临时存储,服务一重启,记忆清零。

✅ 解法:持久化到数据库或文件
方案一:本地文件存储(轻量级)
js 复制代码
import { FileChatMessageHistory } from "@langchain/core/chat_history";

const getMessageHistory = async (sessionId) => {
    return new FileChatMessageHistory({
        filePath: `./history/${sessionId}.json`
    });
};
方案二:Redis / MongoDB(高并发推荐)
bash 复制代码
npm install @langchain/redis
js 复制代码
import { RedisChatMessageHistory } from "@langchain/redis";

const getMessageHistory = async (sessionId) => {
    return new RedisChatMessageHistory({
        sessionId,
        client: redisClient // 已连接的 Redis 客户端
    });
};

💡 生产环境强烈建议使用 Redis:高性能、支持过期策略、分布式部署无忧。


❌ 挑战3:多人同时聊天会不会串消息?

当然会!如果所有用户共用同一个 messageHistory,就会出现 A 用户看到 B 用户的对话。

✅ 解法:用 sessionId 隔离会话
js 复制代码
await chain.invoke(
    { input: "我饿了" },
    { configurable: { sessionId: "user_123" } }  // 每个用户唯一 ID
)

await chain.invoke(
    { input: "我饿了" },
    { configurable: { sessionId: "user_456" } }  // 不同用户,不同历史
)

✅ 安全隔离,互不干扰!


🛠️ 四、Memory 的典型应用场景(附案例灵感)

场景 如何使用 Memory
客服机器人 记住订单号、投诉进度、用户情绪变化
教育辅导 追踪学习章节、错题记录、掌握程度
电商导购 记住预算、品牌偏好、尺码需求
编程助手 保持代码上下文、函数定义、项目结构
心理咨询 理解用户情绪演变、关键事件回顾

💡 创新玩法:结合 SummaryMemory + VectorDB,实现"长期人格记忆"------让 AI 记住你是内向还是外向、喜欢幽默还是严谨。


📘 五、高频面试题(LangChain 方向)

  1. LLM 为什么需要 Memory?它的本质是什么?
  2. Buffer vs Summary Memory 的区别?什么时候用哪种?
  3. 如何设计一个支持百万用户在线的记忆系统架构?
  4. 如何防止敏感信息被 Memory 记录?(安全考量)

📝 结语:让 AI 真正"懂你",从一次对话记忆开始

"智能不是回答问题的能力,而是理解上下文的艺术。"

LangChain Memory 虽然只是整个 LLM 应用中的一个小模块,但它却是通往拟人化交互的关键一步。

从"无状态"到"有记忆",我们不只是在写代码,更是在塑造一种新的沟通方式。

未来属于那些能让 AI 记住你、理解你、陪伴你的产品。

而现在,你已经有了打造它的钥匙。


相关推荐
四瓣纸鹤2 小时前
F2图表在Vue3中的使用方法
前端·javascript·vue.js·antv/f2
それども2 小时前
浏览器CSR和SSR渲染区别
javascript·lua
shanLion2 小时前
从 iframe 到 Shadow DOM:一次关于「隔离」的前端边界思考
前端·javascript
OpenTiny社区2 小时前
Vue2/Vue3 迁移头秃?Renderless 架构让组件 “无缝穿梭”
前端·javascript·vue.js
鱼鱼块2 小时前
二叉搜索树:让数据在有序中生长的智慧之树
javascript·数据结构·面试
敲代码的独角兽2 小时前
当 Web Worker 遇上异步,如何突破单线程限制?
javascript
雪花desu2 小时前
深度解析RAG(检索增强生成)技术
人工智能·深度学习·语言模型·chatgpt·langchain
一生躺平的仔2 小时前
Nestjs 风云录:前端少侠的破局之道
javascript
yxorg2 小时前
vue自动打包工程为压缩包
前端·javascript·vue.js