大模型记忆指南:从短时缓存到永久记忆,手把手带你吃透 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() 返回当前所有消息(数组),顺序为添加顺序。
  • 每次调用模型前,都要把 systemMessagehistory 合并成完整消息列表。
  • 模型返回的 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 用法完全一致:addMessagegetMessages

恢复历史(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 的实现脉络。如果在实际项目中遇到截断策略或向量检索的细节问题,欢迎在评论区交流!

相关推荐
武子康1 小时前
机器人策略 90% 与 92%:为什么两个百分点通常不足以证明更
人工智能·llm·agent
阿里云大数据AI技术1 小时前
PAI支持一键部署Qwen3.8-Flash-Next、GLM-5.3等最新开源模型
人工智能·开源·llm
SleepNW_POV1 小时前
家居科普|床垫溜边塌陷成因解析与边缘加固床垫选购指南
人工智能·科技·智能家居·睡眠
FII工业富联科技服务1 小时前
从 Vera Rubin NVL72 拆解高密度 AI 液冷:GPU 的热到底怎么被带走?
大数据·人工智能
xqchen1 小时前
Web Components 普及困境深度解析:技术标准与工程实践的落差
javascript
磐时信息技术1 小时前
SASETalk | 当智能驾驶驶入未知场景:谈谈关于SOTIF、AI安全与工程落地的思考
人工智能·安全·预期功能安全·sotif
爱丶不疚1 小时前
写给前端工程师的现代 Python 工程化最佳实践:从 pnpm 到 uv,从 CommonJS 到 src-layout
javascript·python·typescript
147API1 小时前
蒸馏模型量化上线前,先查精度退化和算子兼容
大数据·人工智能·深度学习·机器学习·蒸馏
橙时数据 FineInsight2 小时前
AI 能回答数据问题,但能解释业务原因吗?
大数据·人工智能·数据分析·指标平台·fineinsight