一文搞懂 Agent Memory 管理:从临时记忆到长期记忆

Agent = LLM + Harness (Tool + RAG + Memory + ...)

前言

在 AI Agent 开发中,我们经常遇到一个问题:大模型是无状态的。每次调用都是一次独立的请求,模型不会记住之前的对话内容。

那么问题来了:

  • 如何让 Agent 记住用户之前说过什么?
  • 如何在多轮对话中保持上下文连贯?
  • 如何控制记忆的存储和清理?

这就是 Memory 管理 要解决的问题。


为什么需要 Memory?

大模型的无状态特性

javascript 复制代码
// 第一次调用
const response1 = await model.invoke([new HumanMessage("我叫张三")]);
// 模型回答:你好张三!

// 第二次调用
const response2 = await model.invoke([new HumanMessage("我叫什么?")]);
// 模型回答:我不知道你叫什么名字...

模型不会自动记住之前的对话,每次调用都是"失忆"状态。

Memory 的作用

Memory 就是给 Agent 配一个"记忆系统":

  • 临时记忆:当前会话的对话历史
  • 长期记忆:持久化存储,跨会话保留

Memory 存储方案

1. 内存存储(InMemoryChatMessageHistory)

最简单的方案,数据存在内存中,程序退出就丢失。

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

const history = new InMemoryChatMessageHistory();
const systemMessage = new SystemMessage("你是一个友好的助手");

// 第一轮对话
const userMessage1 = new HumanMessage("我叫张三");
await history.addMessage(userMessage1);

// 组装上下文:系统消息 + 历史消息
const messages1 = [systemMessage, ...(await history.getMessages())];
const response1 = await model.invoke(messages1);
await history.addMessage(response1); // 记录AI回复

// 第二轮对话(基于历史)
const userMessage2 = new HumanMessage("我叫什么?");
await history.addMessage(userMessage2);
const messages2 = [systemMessage, ...(await history.getMessages())];
const response2 = await model.invoke(messages2);
// 模型回答:你叫张三!

优点 :简单、快速 缺点:程序退出数据丢失

2. 文件存储(FileSystemChatMessageHistory)

数据持久化到 JSON 文件,程序重启后可恢复。

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
});

// 添加消息
await history.addMessage(new HumanMessage("红烧肉怎么做"));
await history.addMessage(new AIMessage("红烧肉做法:..."));

// 保存到文件(自动)
// 下次启动时可直接读取
const messages = await history.getMessages(); // 恢复历史

JSON 文件格式

json 复制代码
{
  "user_session_001": {
    "messages": [
      { "type": "human", "data": { "content": "红烧肉怎么做" } },
      { "type": "ai", "data": { "content": "红烧肉做法:..." } }
    ]
  }
}

优点 :持久化、支持多用户 缺点:文件大时读写慢

3. 向量数据库(Milvus/Pinecone)

适合大规模、需要语义检索的场景。

javascript 复制代码
// 伪代码示例
const vectorStore = new Milvus(collectionName);
await vectorStore.addDocuments([message]);

// 语义检索
const relevantMessages = await vectorStore.similaritySearch("红烧肉", 5);

优点 :支持语义检索、可扩展 缺点:复杂、需要额外基础设施


Memory 管理策略

随着对话增长,Memory 会越来越大,需要管理策略。

1. 消息数量截断(slice)

最简单的截断方式,保留最近 N 条消息。

javascript 复制代码
const maxMessages = 4;
const allMessages = await history.getMessages();
const trimmedMessages = allMessages.slice(-maxMessages); // 只保留最后4条

示例

  • 原始:8条消息(4轮对话)
  • 截断后:4条消息(最近2轮)
  • 丢弃:前4条(最早2轮)

优点 :简单、快速 缺点:不考虑消息长度,可能超 token 限制

2. Token 数量截断(trimMessages)

精确控制 token 开销,适合生产环境。

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

const enc = getEncoding("cl100k_base"); // OpenAI tokenizer

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

// 截断消息
const trimmedMessages = await trimMessages(allMessages, {
  maxTokens: 100, // token 上限
  tokenCounter: async (msgs) => countTokens(msgs, enc),
  strategy: 'last', // 保留最新消息
});

trimMessages 配置

参数 说明
maxTokens token 上限
tokenCounter 自定义 token 计算函数
strategy 保留策略:'last'(最新)或 'first'(最早)

内部原理:二分查找,找到最大满足条件的消息数量。

优点 :精确控制 API 开销 缺点:计算开销较大

3. 总结压缩

将旧消息总结成摘要,保留关键信息。

javascript 复制代码
// 伪代码
const oldMessages = allMessages.slice(0, -4); // 旧消息
const summary = await model.invoke([
  new HumanMessage(`请总结以下对话:${JSON.stringify(oldMessages)}`)
]);

// 用总结替换旧消息
const newMessages = [
  new SystemMessage(`历史总结:${summary.content}`),
  ...allMessages.slice(-4) // 保留最近4条
];

优点 :保留关键信息、减少 token 缺点:需要额外 API 调用


截断策略对比

策略 复杂度 精确度 适用场景
消息数量截断 开发/测试
Token 数量截断 ⭐⭐⭐ 生产环境
总结压缩 ⭐⭐⭐⭐ 长对话场景

完整示例:带截断的 Memory 管理

javascript 复制代码
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { InMemoryChatMessageHistory } from '@langchain/core/chat_history';
import { HumanMessage, AIMessage, SystemMessage, trimMessages } from '@langchain/core/messages';
import { getEncoding } from 'js-tiktoken';

const model = new ChatOpenAI({
  modelName: process.env.MODEL_NAME,
  apiKey: process.env.OPENAI_API_KEY,
  configuration: { baseURL: process.env.OPENAI_BASE_URL }
});

const enc = getEncoding("cl100k_base");

function countTokens(messages) {
  let total = 0;
  for (const msg of messages) {
    total += enc.encode(msg.content).length;
  }
  return total;
}

async function chatWithMemory() {
  const history = new InMemoryChatMessageHistory();
  const systemMessage = new SystemMessage("你是一个友好的助手");
  const maxTokens = 100;

  // 模拟多轮对话
  const conversations = [
    "我叫张三",
    "我是一名设计师",
    "我喜欢艺术和音乐",
    "我擅长 UI/UX 设计",
    "我最近在做什么项目?"
  ];

  for (const userMsg of conversations) {
    // 添加用户消息
    await history.addMessage(new HumanMessage(userMsg));

    // 获取所有消息并截断
    let allMessages = await history.getMessages();
    const trimmedMessages = await trimMessages(allMessages, {
      maxTokens,
      tokenCounter: async (msgs) => countTokens(msgs),
      strategy: 'last',
    });

    // 组装上下文
    const messages = [systemMessage, ...trimmedMessages];

    // 调用模型
    const response = await model.invoke(messages);
    await history.addMessage(response);

    console.log(`用户:${userMsg}`);
    console.log(`助手:${response.content}\n`);
  }
}

chatWithMemory().catch(console.error);

最佳实践

1. 根据场景选择存储方案

场景 推荐方案
开发/测试 InMemory
小型应用 FileSystem
生产环境 向量数据库

2. 截断策略选择

  • 开发阶段:消息数量截断,简单快速
  • 上线前:Token 数量截断,精确控制
  • 长对话:总结压缩,保留关键信息

3. 多用户支持

javascript 复制代码
// 使用 sessionId 区分用户
const history = new FileSystemChatMessageHistory({
  filePath: "chat_history.json",
  sessionId: `user_${userId}` // 动态用户ID
});

总结

Memory 管理是 Agent 开发的核心环节:

  1. 存储:内存 → 文件 → 向量数据库,按需选择
  2. 截断:消息数量 → Token 数量 → 总结压缩,逐步精确
  3. 持久化:文件存储支持跨会话,向量数据库支持语义检索

掌握 Memory 管理,让你的 Agent 从"金鱼记忆"升级为"长期记忆"。


💡 思考:你的 Agent 项目用的是哪种 Memory 方案?遇到了什么问题?欢迎评论区交流!


相关资源

相关推荐
汉堡大王95272 小时前
面试必考:手写代码 new 做了什么?从原理到实现全解析
前端·javascript·面试
数智启示录3 小时前
Apache Doris 4.0.8 混合检索实战(第 5 篇):三套系统只返回两条结果,一条 SQL 如何避开交集丢失
大数据·数据库·经验分享·sql·面试
YonyouHRSaaS5 小时前
AI面试工具怎么选型?2026年企业AI面试系统选型标准是什么?
人工智能·面试·职场和发展·ai面试·ai招聘
Tenifs6 小时前
AI 智能体与大模型应用开发面试题库
人工智能·面试
RisunJan7 小时前
产品经理面试知识点梳理
面试·职场和发展·产品经理
CoderYanger8 小时前
A.每日一题:3345. 最小可整除数位乘积 I
java·数据结构·程序人生·算法·leetcode·面试·学习方法
测试19989 小时前
软件测试面试八股文【2026最新版】
自动化测试·软件测试·功能测试·测试工具·面试·职场和发展·测试用例
数智启示录9 小时前
Apache Doris 4.0.8 一致性实战(第 3 篇):Checkpoint 一直成功,Exactly-Once 为什么仍可能是假的
大数据·经验分享·面试·flink
boheweidexiatian9 小时前
如何系统性地准备面试,把通过率提到最高?
面试·职场和发展·求职招聘