从InMemory内存记忆到文件持久化,手把手教你管理AI的“记忆”

全文导读:大模型真的"记忆力"超群吗?No!它只是个金鱼脑。本文将带你从0到1掌握LangChain记忆系统的核心原理,深入剖析内存记忆与文件持久化两种方案的实现细节,并给出生产环境下的避坑指南。读完本文,你将彻底搞懂AI记忆管理的那些事儿。


一、灵魂拷问:大模型真的记得你上一句说了啥吗?

答:它真不记得!

大模型本质上是一个无状态函数 ------你给它输入,它吐输出,每次调用都是"重新做人"。那为什么ChatGPT能记住我们的上下文呢?秘密就是------Memory(记忆系统)

用个生活化的比喻:

大模型就像一个健忘的大厨,每次做菜都要重新问你想吃啥。而Memory系统,就是他的随身小本本,记录着你每次点菜的历史。但这个本本也有讲究------记在脑子里(内存)、记在纸上(文件)、还是记在图书馆里(向量数据库),就各有千秋了。


二、为啥我们需要Memory?------三个业务痛点直击

在真实业务场景中,Memory管理至少解决了以下三大痛点:

痛点 说明 没有Memory的惨状
上下文断裂 用户连续提问需要关联性 每次都要重新自我介绍,像个失忆症患者
个性化丧失 需要记住用户偏好和历史 永远无法提供个性化服务
成本失控 上下文窗口有限(如200k tokens) 每次都塞进全部历史,token费用爆炸

核心公式Agent = LLM + Harness(Tool + RAG + Memory)

Memory是Agent大脑中不可或缺的一环,没有它,AI就是个"一次性用品"。


三、Memory的两大核心维度

四、重难点①:InMemoryChatMessageHistory------最优雅的"数组升级"

4.1 核心原理剖析

先看最基础的内存记忆实现:

typescript

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

// 创建记忆容器------本质上就是一个封装好的数组
const history = new InMemoryChatMessageHistory()

// 系统提示词(始终保留)
const systemMessage = new SystemMessage({
  content: '你是一个友好、幽默的做菜助手'
})

// 👇 第一轮对话
const userMessage = new HumanMessage({ content: '你今天吃什么' })
await history.addMessage(userMessage)  // 添加用户消息

// 构造完整对话上下文 = 系统提示词 + 全部历史
const messages1 = [systemMessage, ...(await history.getMessages())]
const response1 = await model.invoke(messages1)

// ⚠️ 关键!必须把AI的回复也存进去,否则下轮对话没有上下文
await history.addMessage(response1)

4.2 设计者为什么这么写?

💡 核心设计思想InMemoryChatMessageHistory 就是把一个普通数组 升级成了自带CRUD能力的消息容器

typescript

typescript 复制代码
// 本质上它在内部维护了这样一个数组
class InMemoryChatMessageHistory {
  private messages: BaseMessage[] = []  // 👈 核心数据结构
  
  async addMessage(message: BaseMessage) {
    this.messages.push(message)  // 添加
  }
  
  async getMessages() {
    return this.messages  // 获取全部
  }
}

设计智慧

  • 统一了消息的存储接口(addMessage/getMessages)
  • 屏蔽了底层实现细节(内存/文件/数据库切换无感知)
  • 支持多模态消息类型(Human/AI/System/Tool)

4.3 🚨 新手最容易犯的错

typescript

csharp 复制代码
// ❌ 错误示范:只存用户消息,不存AI回复
const userMsg = new HumanMessage({ content: '你好' })
await history.addMessage(userMsg)
const response = await model.invoke([...history.getMessages()])
// 忘记存 response!下一轮对话AI完全不记得自己说过什么

// ✅ 正确示范:双向存储
await history.addMessage(userMsg)
const response = await model.invoke([...history.getMessages()])
await history.addMessage(response)  // 必须存!

五、重难点②:FileSystemChatMessageHistory------让记忆"断电不丢失"

5.1 文件持久化实现

typescript

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

const filePath = path.join(process.cwd(), 'chat_history.json')
const sessionId = 'user_session_001'  // 👈 多用户隔离

const history = new FileSystemChatMessageHistory({
  filePath,    // 存储路径
  sessionId,   // 会话ID,不同用户互不干扰
})

// 使用方式与InMemory完全一致!
await history.addMessage(new HumanMessage('红烧肉怎么做'))
const allMessages = await history.getMessages()  // 自动从文件读取

5.2 文件存储结构解密

chat_history.json 内部存储格式:

json

css 复制代码
[  {    "type": "human",    "content": "红烧肉怎么做"  },  {    "type": "ai",    "content": "红烧肉是一道经典的家常菜..."  }]

5.3 多用户隔离原理

typescript

arduino 复制代码
// 同一个文件,不同sessionId自动隔离
const historyA = new FileSystemChatMessageHistory({
  filePath: './chat.json',
  sessionId: 'user_001'  // 用户A的会话
})

const historyB = new FileSystemChatMessageHistory({
  filePath: './chat.json',
  sessionId: 'user_002'  // 用户B的会话
})

// 内部实现:getMessages() 时只返回匹配 sessionId 的消息

5.4 完整的存储+恢复闭环

六、重难点③:Memory管理的"三把刀"------截断策略详解

上下文窗口是有限的(GPT-4是128K,Claude是200K),无限制增长最终会撑爆窗口或耗尽token预算。以下是三种主流截断策略:

6.1 策略一:按消息数量截断

typescript

ini 复制代码
// 最简单粗暴:只保留最近N条消息
const maxMessages = 4
const trimmedMessages = allMessages.slice(-maxMessages)

适用场景:消息长度均匀,对时效性要求高。

6.2 策略二:按Token数量截断(精确控制成本)

typescript

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

async function countTokens(messages, enc) {
  let total = 0
  for (const msg of messages) {
    const content = typeof msg === 'string' ? msg.content : JSON.stringify(msg.content)
    total += enc.encode(content).length  // 精确token计数
  }
  return total
}

const enc = getEncoding('cl100k_base')  // OpenAI使用的编码

const trimmed = await trimMessages(allMessages, {
  maxTokens: 100,  // token上限
  tokenCounter: async (msgs) => countTokens(msgs, enc),
  strategy: 'latest'  // 保留最新的消息
})

为什么用二分查找?

trimMessages内部使用二分查找算法,在保证不超token上限的前提下,尽可能多地保留消息:

text

markdown 复制代码
1. 从所有消息开始
2. 如果总token > maxTokens,移除最早的一条
3. 重复直到token数符合要求
4. 返回截断后的消息列表

6.3 三种策略对比

七、面试高频考点

Q1:InMemoryChatMessageHistory和FileSystemChatMessageHistory的区别和适用场景?

维度 InMemory FileSystem
存储位置 内存 磁盘文件
持久化 ❌ 重启丢失 ✅ 永久保存
读写速度 极快 相对慢(I/O)
适用场景 单次会话、临时对话 客服系统、长期记忆
多会话隔离 需要手动管理 sessionId自动隔离

Q2:为什么要用await history.getMessages()而不是直接读取内部数组?

getMessages()是一个抽象方法,不同实现类有不同的获取逻辑:

  • InMemoryChatMessageHistory:直接返回内存数组
  • FileSystemChatMessageHistory:从文件读取并反序列化
  • 向量数据库实现:执行相似性检索

统一使用getMessages()保证了多态性,切换存储方式无需修改业务代码。

Q3:截断时为什么推荐使用Token计数而非消息数量?

:因为不同消息的长度差异巨大

  • "你好" ≈ 1-2 tokens
  • "请详细解释量子力学的波函数坍缩..." ≈ 50+ tokens

按数量截断可能导致token超限,而按Token计数截断是精确可控的。


八、避坑指南------生产环境血泪经验

🕳️ 坑1:文件并发写入导致数据损坏

typescript

javascript 复制代码
// ❌ 多请求同时写入同一文件
const history = new FileSystemChatMessageHistory({
  filePath: './chat.json',
  sessionId: 'global'  // 所有用户共享sessionId
})
// 并发写入 → 文件损坏

// ✅ 每个用户独立sessionId
const history = new FileSystemChatMessageHistory({
  filePath: './chat.json',
  sessionId: `user_${userId}`  // 用户隔离
})

🕳️ 坑2:忘记添加SystemMessage

typescript

csharp 复制代码
// ❌ 缺少系统提示词
const messages = await history.getMessages()
// AI不知道自己的角色定位

// ✅ 始终在第一条放SystemMessage
const messages = [systemMessage, ...(await history.getMessages())]

🕳️ 坑3:Token计算使用错误的编码

不同模型的tokenizer不同:

  • OpenAI系列 → cl100k_base
  • Claude系列 → claude 专用tokenizer
  • 开源模型 → 需使用对应tokenizer

typescript

javascript 复制代码
// ✅ 正确获取编码
import { getEncoding } from 'js-tiktoken'
const enc = getEncoding('cl100k_base')  // OpenAI GPT-4/Turbo

九、总结一张图看懂整个架构

相关推荐
神秘的猪头2 小时前
新版 LangChain Agent 核心架构:State、Context、ToolRuntime 与 Middleware
langchain·llm·fastapi
武子康3 小时前
Agent 的工具没变,SGLang 缓存为什么没命中?
人工智能·llm·agent
MomentYY3 小时前
大模型 Memory 管理:它凭什么知道你之前说过什么?
llm·agent·ai编程
桃西西呀4 小时前
换个会话就失忆?拆开 Agent 记忆的 4 层与 4 个流派,附9个坑的自检清单
人工智能·llm·ai编程
桃西西呀4 小时前
风控模型说自己 80% 准,却漏掉了 76% 该拦的人:一次把模型评估讲透
人工智能·机器学习·llm
lucas_AI8 小时前
把提示词「编译」成函数:Compile by Training 让大模型退居二线
llm
修远客8 小时前
持久化与缓存:Agent的数据底座 — 没有持久化的Agent像金鱼记忆,重启就忘
llm·agent·ai编程
武子康9 小时前
同一份长文问两次,SGLang 怎样少算一遍
人工智能·llm·agent
张彦峰ZYF10 小时前
从“记住对话”到“经营组织经验”:TencentDB Agent Memory 的团队级记忆架构、工程取舍与企业落地边界
人工智能·架构·llm·agent·skill·agent memory·tencentdb