0

AI Agent长期记忆系统搭建实战:让智能体真正记住一切

2026.06.04 | youres | 23次围观

为什么你的AI Agent总是"失忆"

用大模型做过Agent的朋友应该都有这个痛点:每次新开一个会话,AI就像失忆了一样,之前聊过的需求、做过的决策、踩过的坑,统统忘光。你不得不反复重复同样的上下文,效率大打折扣。

我最早感受到这个问题的严重性是在做一个持续迭代的项目——一个自动化的内容发布Agent。每次它执行完任务,下次启动时完全不记得上次发过什么、哪些关键词效果不好、发布时间间隔是多少。结果就是不断重复发布类似内容,完全没有"经验积累"。

这背后的问题很清楚:大模型的上下文窗口是有限的,会话一结束,所有信息就清空了。解决方案也很明确——你需要一套外挂记忆系统,让Agent能够跨会话地存储、检索和利用历史信息。

记忆系统的三层架构

在动手写代码之前,先理清架构。参考人类记忆系统,AI Agent的记忆通常分为三层:

层次类比存储时长实现方式
即时感知你正在看的这篇文章毫秒~秒当前输入解析
工作记忆你在开会时记在脑子里的要点会话期间对话上下文/变量字典
长期记忆你记得去年学过的东西永久外部数据库/向量检索

本文要搭建的就是长期记忆层——这是让Agent从"一次性工具"变成"可持续进化的助手"的关键。

技术选型:轻量级方案

网上有很多Agent记忆系统的教程,但大多数要么过于学术(上来就上知识图谱、RAG、多层向量数据库),要么依赖特定的框架(LangGraph、MemGPT),对独立开发者不太友好。

我选择的方案是JSON文件 + SQLite + 全文检索,零外部依赖,本地运行,核心代码不到200行。为什么这么选:

  • JSON文件存储记忆条目——简单直观,方便调试和人工检查
  • SQLite做索引和元数据查询——按时间、类型、关键词快速筛选
  • 全文检索(FTS5)——SQLite自带的全文搜索引擎,语义匹配够用
  • 不需要向量数据库——对于大多数Agent场景,精确匹配+模糊搜索已经足够

这个方案的核心哲学是:先跑起来,再优化。很多人一上来就追求"语义检索"、"RAG增强",结果项目还没启动就陷在了依赖安装和环境配置里。

核心数据结构设计

每条记忆条目的结构设计很重要,它直接决定了后续检索的效率和准确性。

// 记忆条目结构
const memoryEntry = {
  id: "mem_1717456800_a1b2c3",  // 唯一标识(时间戳+随机字符)
  type: "experience",             // 类型:experience/decision/preference/fact
  content: "用户偏好深色主题,不要推荐浅色UI方案",
  source: "conversation",          // 来源:conversation/task/observation
  timestamp: 1717456800000,        // 创建时间
  lastAccessed: 1717456800000,     // 最后访问时间
  accessCount: 1,                 // 访问次数(用于判断记忆"温度")
  tags: ["用户偏好", "UI", "主题"],
  importance: 0.8,                // 重要性评分 0-1
  context: {                      // 产生这条记忆的上下文
    task: "设计个人主页",
    sessionId: "sess_abc123"
  }
};

这里有几个设计决策值得说明:

  • type字段细分记忆类型——不同类型的记忆有不同的衰减策略。"fact"类记忆(如"API端口是8080")几乎不过期;"experience"类记忆(如"上次的方案效果不好")随时间衰减
  • accessCount + lastAccessed——实现类似人脑的"越常用的记忆越不容易遗忘"的机制
  • importance评分——Agent自己对记忆重要性的判断,关键决策记高分,闲聊记低分

实现记忆存储模块

下面是存储模块的核心代码,使用SQLite:

const Database = require('better-sqlite3');
const crypto = require('crypto');
const path = require('path');

class MemoryStore {
  constructor(dbPath) {
    this.db = new Database(dbPath);
    this.db.pragma('journal_mode = WAL');
    this._init();
  }

  _init() {
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS memories (
        id TEXT PRIMARY KEY,
        type TEXT NOT NULL,
        content TEXT NOT NULL,
        source TEXT DEFAULT 'conversation',
        timestamp INTEGER NOT NULL,
        last_accessed INTEGER NOT NULL,
        access_count INTEGER DEFAULT 1,
        tags TEXT DEFAULT '[]',
        importance REAL DEFAULT 0.5,
        context TEXT DEFAULT '{}'
      );
      CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts 
      USING fts5(content, tags);
    `);
  }

  store(entry) {
    const id = `mem_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
    const now = Date.now();
    this.db.prepare(`
      INSERT INTO memories (id, type, content, source, timestamp, 
        last_accessed, access_count, tags, importance, context)
      VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)
    `).run(id, entry.type, entry.content, entry.source, now, now,
      JSON.stringify(entry.tags || []),
      entry.importance || 0.5,
      JSON.stringify(entry.context || {}));
    // 同步到全文检索索引
    this.db.prepare(
      'INSERT INTO memories_fts VALUES (?, ?)'
    ).run(entry.content, (entry.tags || []).join(' '));
    return id;
  }
}

这里用 better-sqlite3 而不是原生 sqlite3,因为前者是同步API,在Agent场景下更简洁——你不需要异步回调增加复杂度。

实现记忆检索模块

检索是记忆系统最关键的部分。好的检索要同时满足相关性时效性

class MemoryStore {
  // ... 接上面的代码

  search(query, options = {}) {
    const { limit = 10, type, minImportance = 0, maxAge } = options;
    
    // 构建基础查询
    let sql = `
      SELECT m.*, rank as search_score
      FROM memories m
      LEFT JOIN memories_fts fts ON m.content = fts.content
      WHERE m.importance >= ?
    `;
    const params = [minImportance];

    // 按类型过滤
    if (type) {
      sql += ' AND m.type = ?';
      params.push(type);
    }

    // 按时间过滤
    if (maxAge) {
      const cutoff = Date.now() - maxAge;
      sql += ' AND m.timestamp > ?';
      params.push(cutoff);
    }

    // 全文检索匹配
    if (query) {
      sql += ' AND memories_fts MATCH ?';
      params.push(query);
    }

    // 综合排序:搜索相关度 * 重要性 * 时间新鲜度 * 访问频率
    sql += ` ORDER BY (
      (search_score + 1) * importance * 
      (1.0 / (1 + (strftime('%s','now') - timestamp/1000) / 86400.0)) *
      (1 + access_count * 0.1)
    ) DESC LIMIT ?`;
    params.push(limit);

    return this.db.prepare(sql).all(...params).map(row => ({
      ...row,
      tags: JSON.parse(row.tags),
      context: JSON.parse(row.context)
    }));
  }
}

排序公式中的四个因子各有含义:

  • search_score——全文检索的相关度,确保结果和查询匹配
  • importance——重要记忆排在前面
  • 时间衰减——越久远的记忆权重越低(用天数为单位计算衰减)
  • access_count——经常被检索到的记忆说明更有价值

这个公式不是完美的,但在实际使用中效果不错。如果你需要更精细的控制,可以考虑引入向量检索做语义匹配,但对于大多数中小规模Agent场景,这套已经够用。

实现记忆遗忘机制

一个没有遗忘机制的记忆系统最终会被无用信息淹没。人类大脑天然会遗忘不重要的信息,Agent的记忆系统也需要这个能力。

// 遗忘策略:自动清理低价值记忆
forgetUnused(maxAgeDays = 90, minAccessCount = 1, minImportance = 0.2) {
  const cutoff = Date.now() - maxAgeDays * 86400000;
  const result = this.db.prepare(`
    DELETE FROM memories 
    WHERE timestamp < ? 
      AND access_count <= ? 
      AND importance < ?
      AND type != 'fact'  // fact类记忆不过期
  `).run(cutoff, minAccessCount, minImportance);
  
  if (result.changes > 0) {
    console.log(`遗忘清理: 删除了 ${result.changes} 条过期记忆`);
  }
  return result.changes;
}

遗忘策略的核心逻辑:

  • 超过90天没被访问过的记忆,自动清理
  • fact类型豁免——像"数据库地址是xxx"这种事实性记忆不应该过期
  • importance高于0.2的保留——即使很久没访问,重要的记忆也要保留
  • 被访问过的记忆保留——说明还有价值

我的建议是把遗忘机制放在Agent的心跳(Heartbeat)或者Cron定时任务中,每隔一段时间自动执行一次清理。

在Agent中集成记忆系统

有了存储、检索、遗忘三个模块,接下来就是集成到Agent的工作流中。核心原则是:让记忆的写入和读取尽可能自动化,不要依赖Agent"主动"去记忆

// Agent任务执行前:自动加载相关记忆
async function loadRelevantMemories(task, memoryStore) {
  // 从任务描述中提取关键词
  const keywords = extractKeywords(task);
  const query = keywords.join(' OR ');
  
  // 检索相关记忆
  const memories = memoryStore.search(query, {
    limit: 10,
    minImportance: 0.3,
    maxAge: 30 * 86400000  // 最近30天
  });
  
  if (memories.length > 0) {
    // 构建记忆上下文注入到提示词中
    const memoryContext = memories
      .map(m => `[记忆 ${m.type}]: ${m.content} (重要性:${m.importance})`)
      .join('\n');
    
    return `\n以下是与当前任务相关的历史记忆:\n${memoryContext}\n请参考这些记忆来完成任务。`;
  }
  return '';
}

// Agent任务执行后:自动提取并存储新记忆
async function extractAndStoreMemories(task, result, memoryStore) {
  const extractionPrompt = `
    任务:${task}
    结果:${result}
    
    请从以上任务执行过程中提取值得长期记住的信息,
    包括:
    1. 踩过的坑或失败的尝试(type: experience)
    2. 做出的重要决策及原因(type: decision)
    3. 发现的用户偏好(type: preference)
    4. 确认的事实信息(type: fact)
    
    返回JSON数组格式。
  `;
  
  // 调用大模型提取记忆(伪代码)
  const extractedMemories = await callLLM(extractionPrompt);
  
  for (const mem of extractedMemories) {
    memoryStore.store({
      type: mem.type,
      content: mem.content,
      source: 'task',
      importance: mem.importance || 0.5,
      tags: mem.tags || [],
      context: { task }
    });
  }
}

关键设计思想:

  • 任务前自动检索——Agent执行任务前,根据任务关键词自动检索相关历史记忆,注入到上下文中
  • 任务后自动提取——Agent执行完任务后,用大模型自动从执行日志中提取值得记住的信息
  • 大模型做提取——让大模型判断哪些信息值得记忆、分类是什么、重要性多高,这比写固定规则灵活得多

一个真实的踩坑记录

分享我在实际项目中遇到的三个坑,这些都是教程里不会告诉你的:

坑一:记忆爆炸

一开始我没有设遗忘机制,Agent每执行一次任务就存储3-5条记忆。运行了一周后,记忆条目超过2000条,检索时间从几毫秒飙升到2秒——Agent的响应速度明显变慢。更糟糕的是,检索到的记忆质量也在下降,因为大量无关记忆淹没了真正有用的信息。

解决:加入遗忘机制 + 限制每次检索返回的记忆条数(最多10条)+ 对记忆内容做压缩(超过200字的记忆自动摘要到100字以内)。

坑二:循环记忆

Agent从记忆中检索到一条信息,执行任务后又把同样的信息存回记忆。反复几次后,同一条记忆被存储了几十次,浪费存储空间且干扰检索结果。

解决:在存储前做去重检查——计算新记忆内容与最近30天记忆的相似度(用简单的字符串包含关系即可),相似度超过70%就跳过存储。

坑三:记忆噪声

让大模型自动提取记忆时,它会倾向于"什么都记"。聊天的内容、无关的细节、临时变量都会被存成记忆,导致检索结果充满噪声。

解决:在提取提示词中加入明确的过滤标准——只提取"对未来执行有实质帮助"的信息,并要求给每条记忆的重要性打分。存储时过滤掉importance低于0.3的记忆。

从工具到"伙伴"的进化

接入记忆系统后的变化是显而易见的。我的内容发布Agent在运行两周后,明显能感觉到它的"成长":

  • 它不再重复发布相同关键词的文章——因为记住了已发布过的关键词
  • 它能根据历史发布效果调整发布时间——因为记住了哪些时间段互动率高
  • 它在遇到曾经踩过的坑时会自动避开——因为"经验"类记忆在检索中被优先返回

这就是长期记忆系统的价值所在:它把Agent从一个每次都从零开始的工具,变成了一个越用越聪明的伙伴。

最后,如果你对更高级的记忆方案感兴趣,可以了解一下最近腾讯混元发布的 Hy-Memory 技术和开源项目 Mem0。它们分别代表了"企业级"和"轻量级"两个方向的高级实现。但如果你像我一样追求零依赖、快速落地,本文的SQLite方案已经是一个可靠的起点。

延伸阅读:Claude Code MCP配置实战 | AI工作流自动编排实战 | OpenClaw自定义Skills开发入门指南

版权声明

本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论