为什么你的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辅助作者原创,未经许可,转载请保留原文链接。

发表评论