大模型记忆指南:从短时缓存到永久记忆,手把手带你吃透 LangChain Memory 管理

大模型记忆指南:从短时缓存到永久记忆,手把手带你吃透 LangChain Memory 管理

大模型很聪明,但它没有"记性"------每次对话都是"初见"。如何让 AI 拥有短期工作记忆、长期持久记忆,甚至能从海量历史中检索关键信息?本文结合 LangChain 源码实践,为你拆解 Memory 的三种核心玩法。


一、为什么大模型需要 Memory?

大模型(LLM)本质上是无状态 的------每次调用 model.invoke() 都像第一次见面,它不会记得你上一句说了什么。

我们平时能"连续对话",是因为我们把所有历史消息 都塞进了 messages 数组,再一股脑传给模型。这种"简单记忆"有两个致命缺陷:

  • 上下文窗口有限(比如 200k token),塞太多会超限,或者费用飙升。
  • 每次请求都重复发送历史,随着对话变长,延迟和成本线性增长。

所以,Memory 管理的目标就是:在有限的"脑容量"内,保留最有用信息,同时让 AI 能"记住"之前聊过什么,甚至跨会话记忆。

在 LangChain 的架构里,Agent = LLM + Harness(Tool + RAG + Memory + ...),Memory 是连接用户与模型的历史纽带。


二、Memory 的三种经典思路

策略 适用场景 实现手段
截断(Truncation) 短期对话,只保留最近 N 条或 N 个 token slice(-4) 或 trimMessages
总结(Summarization) 压缩长历史为简短摘要,保留核心事实 每隔 20 条调用一次 LLM 生成摘要
检索(Retrieval) 长期记忆,从向量库中召回相关历史 将历史嵌入向量,存入 Milvus/Pinecone

本文会重点展示截断 和文件持久化的代码实现,并引出向量检索的未来方向。


三、短时记忆:InMemoryChatMessageHistory

先看最简单的内存级记忆------它把消息存在 RAM 里,进程重启就消失,适合单次会话。

javascript 复制代码
import { InMemoryChatMessageHistory } from '@langchain/core/chat_history';
import { HumanMessage, SystemMessage } from '@langchain/core/messages';

核心代码解析(history-test.mjs)

javascript 复制代码
const history = new InMemoryChatMessageHistory();
const systemMessage = new SystemMessage("你是一个友好,幽默的做菜助手...");
  • SystemMessage:设置 AI 的"人设",它会始终生效(如果每次请求都带上)。
  • HumanMessage:用户输入。
  • AIMessage:模型输出。

添加消息到记忆:

javascript 复制代码
await history.addMessage(userMessage1);   // 加用户消息
const response1 = await model.invoke([systemMessage, ...(await history.getMessages())]);
await history.addMessage(response1);      // 加AI回复

关键点:

  • history.getMessages() 返回当前所有消息(数组),顺序为添加顺序。
  • 每次调用模型前,都要把 systemMessage 和 history 合并成完整消息列表。
  • 模型返回的 AIMessage 对象也要存回 history,否则下次模型就"失忆"了。

遍历历史:

javascript 复制代码
const allMessages = await history.getMessages();
allMessages.forEach((msg) => {
  const prefix = msg.type === 'human' ? '用户' : '助手';
  console.log(`${prefix}: ${msg.content}`);
});

这就是最基础的"数组 + 对象"记忆,但它仅存在于内存中。


四、文件持久化:FileSystemChatMessageHistory

如果你希望跨会话(比如用户第二天回来)还能记住之前的聊天,就得把历史持久化到磁盘。

LangChain 提供了 FileSystemChatMessageHistory,它会将消息序列化为 JSON 文件。

javascript 复制代码
import { FileSystemChatMessageHistory } from '@langchain/community/stores/message/file_system';
import path from 'node:path';

写入历史(history-test2.mjs)

javascript 复制代码
const filePath = path.join(process.cwd(), "chat_history.json");
const sessionId = "user_session_001";   // 区分不同用户
const history = new FileSystemChatMessageHistory({ filePath, sessionId });
  • sessionId:允许同一个文件存储多个用户的对话(内部会用 sessionId 做 key)。
  • 首次运行会创建文件,后续会追加。

接下来就与 InMemory 用法完全一致:addMessage、getMessages。

恢复历史(history-test3.mjs)

javascript 复制代码
const restoredHistory = new FileSystemChatMessageHistory({ filePath, sessionId });
const restoredMessages = await restoredHistory.getMessages();
console.log(`恢复了 ${restoredMessages.length} 条消息`);

甚至可以接着聊------新消息会追加到文件末尾。

文件内容示例 (chat_history.json):

json 复制代码
[
  ["user_session_001", ["HumanMessage", "红烧肉怎么做"]],
  ["user_session_001", ["AIMessage", "首先准备五花肉..."]],
  ...
]

💡 适用场景:个人助手、客服机器人,用户希望保留长期偏好。


五、上下文截断:控制 token 开销

当对话超过窗口限制时,必须裁剪历史。两种常见策略:按消息条数截断、按 token 数量截断。

5.1 按消息条数截断(简单粗暴)

javascript 复制代码
const maxMessages = 4;
const trimmed = allMessages.slice(-maxMessages);   // 只留最后4条

slice(-4) 保留最近 4 条消息,丢弃更早的。优点是简单,缺点是"条数"不精确------有些消息很长,有些很短,同样条数占用的 token 可能天差地别。

5.2 按 Token 数量精确截断(推荐)

LangChain 提供了 trimMessages 工具,配合 tiktoken 精确计算 token。

javascript 复制代码
import { trimMessages } from '@langchain/core/messages';
import { getEncoding } from 'js-tiktoken';

自定义 token 计数器:

javascript 复制代码
function countTokens(messages, encoder) {
  let total = 0;
  for (const msg of messages) {
    const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
    total += encoder.encode(content).length;
  }
  return total;
}

const enc = getEncoding("cl100k_base");   // OpenAI 的编码器
const trimmedMessages = await trimMessages(allMessages, {
  maxTokens: 100,
  tokenCounter: async (msgs) => countTokens(msgs, enc),
  strategy: "last"   // 保留最后 maxTokens 个 token
});

trimMessages 原理:

  • 它会从消息列表的末尾 开始,逆向累积 token 数,直到达到 maxTokens 上限。
  • 如果某条消息超长,可能连它本身都放不下,则会抛出错误或截断该消息(可配置)。
  • 最终返回一个裁剪后的消息数组,不修改原始 history,你可以用新的数组调用模型。

执行结果:

javascript 复制代码
console.log(`保留 token 数:${countTokens(trimmedMessages, enc)}`);
// 输出 ≤ 100

🔧 为什么用 tiktoken 而不是 len(content)?

不同模型的分词器不同(如 cl100k_base 对应 GPT-4/GPT-3.5-turbo),只有用对应的编码器才能精确计算实际 token 消耗,避免超限。


六、进阶之路:总结与向量检索

6.1 总结(Summarization)

当对话特别长(例如 200 条),截断会丢失早期的重要上下文。这时可以每隔一定轮数(比如每 20 条)调用一次 LLM,生成"对话摘要",然后用摘要替换早期消息。

伪代码:

javascript 复制代码
if (history.length % 20 === 0) {
  const summary = await model.invoke([SystemMessage("请总结以下对话"), ...history]);
  history.clear();   // 清空旧消息
  history.addMessage(new SystemMessage(`历史摘要:${summary.content}`));
}

这样既保留了核心信息,又控制了 token 数量。

6.2 向量检索(Retrieval)------长期记忆的终极方案

将历史消息(或用户偏好、文档)嵌入成向量 ,存入向量数据库(如 Milvus)。每次用户提问时,用问题向量去库中检索最相关的若干条历史,再拼接到 prompt 中。

这种方案超越了"截断"和"总结"的局限,实现了按需召回。

readme.md 中提到了用 Milvus 做向量存储,并计划开发一个聊天应用 codex:每 20 条触发一次总结,生成摘要并存入 Milvus,下次对话时从 Milvus 拉取相关历史。

安装 Milvus(Docker Compose):

bash 复制代码
docker compose -f ./milvus-standalone-docker-compose.yml up -d

项目会使用 @zilliz/milvus2-sdk-node 连接 Node 应用,配合 Attu GUI 工具管理数据。


七、总结:Memory 选型指南

需求 推荐方案 工具/库
单次对话,轻量 InMemoryChatMessageHistory LangChain 内置
跨会话,低频访问 FileSystemChatMessageHistory @langchain/community
严格控制 token 开销 trimMessages + tiktoken LangChain + js-tiktoken
长对话保留核心信息 周期性总结 + 截断结合 自定义 + trimMessages
大规模、跨会话、智能检索 向量数据库 + 嵌入检索 Milvus + @zilliz/milvus2-sdk-node

实践中通常组合使用:

  • 短期记忆(内存)保持流畅。
  • 定期备份到文件或数据库实现持久化。
  • 通过截断或总结控制上下文窗口。
  • 关键历史存入向量库实现长期智能召回。

最后,用一张图总结 Memory 的工作流:

graph LR User[用户消息] --> History[Memory 管理器] History -->|截断/总结| Trimmed[压缩后历史] History -->|向量检索| Retrieved[相关历史片段] Trimmed --> Prompt[组装 Prompt] Retrieved --> Prompt Prompt --> LLM[大模型] LLM --> Response[回复] Response --> History History -->|定期| File[(文件持久化)] History -->|嵌入| VectorDB[(向量数据库)]

掌握了这些,你的 AI 应用就能从"金鱼脑"进化为"大象记忆",持续为用户提供连贯、个性化的服务。


希望这篇文章帮你理清了 Memory 的实现脉络。如果在实际项目中遇到截断策略或向量检索的细节问题,欢迎在评论区交流!

相关推荐
QY_Research_37 分钟前
2026房地产NFT平台创业机遇与落地指南分析报告
大数据·人工智能·物联网·机器人·制造
机器人零零壹2 小时前
技术白皮书:iRobotCAM如何解决机器人激光切割应用中的核心难点,从CAD离线编程到双目视觉定位与轨迹自适应提升复杂空间曲线切割的精度、效率与柔性化水平
人工智能·算法
电子元件小说家3 小时前
2026年四季度电感市场观察:AI算力与汽车电子驱动下的选型与验证建议智能硬件
人工智能·汽车
网易CodeWave-小码哥3 小时前
支持多模块 Java 微服务导入|CodeWave SDD V4.5.10 上线
人工智能
AI你一生一世3 小时前
不止于时序存储:DolphinDB 如何成为 AI 时代的工业智能底座
java·人工智能·struts·时序数据库·工业智能·dolphindb·ai基础设施
belldeep3 小时前
python:genesis-world, Genesis-Embodied-AI
人工智能·python·ai·genesis
怕浪猫3 小时前
小程序成长计划上新:10 亿 Token 现在能调 DeepSeek-v4.1-flash了
人工智能·aigc·ai编程
ss2734 小时前
为什么PostgreSQL适合AI场景?
数据库·人工智能·postgresql
Dream-Y.ocean4 小时前
鸿蒙PC平台宅匠 3D 室内设计适配实战:基于 Electron 壳方案的 Three.js 双渲染引擎与参数化户型编辑器开发
javascript·3d·harmonyos
七牛云行业应用5 小时前
DeepSeek Super-agent 与 Grok Bot:开源 Agent 架构和聊天助手有何区别
人工智能·ai·架构·ai编程