ai-memory 这份 7.9K Stars 的开源项目,解决的是 Agent 开发里最扎手的一件事:记忆层。做 Agent 的人都知道,模型本身不记事儿,上下文窗口又不能无限增长,于是大家都在往框架里塞记忆模块,但大部分只是单个 session 的缓存,换个 Agent 就全断片儿了。ai-memory 把记忆抽成一个跨 Agent 的统一层,可以在多个 Agent 之间共享状态、隔离私有上下文、按重要性检索历史信息。对用 LangChain、Dify 或自研 Agent 框架的人来说,它是可以直接接进项目的记忆基础设施。不管做知识库问答、多智能体协作,还是长期任务规划,都能少踩很多记忆相关的坑。这篇文章会从“为什么需要记忆层”开始,拆解它的核心设计,再给你一套能落地的接入实践和排障记录。
1. 先想清楚:Agent 的失忆问题到底出在哪
1.1 上下文窗口不是无限存档
很多人第一次做 Agent 时会觉得,反正模型有上下文窗口,多轮对话里直接把历史消息全塞进去不就行了?短期跑 demo 确实可以,一旦任务变复杂,很快就会发现三个问题:token 成本直线上升、超过窗口后被强制截断、早期信息被无关内容淹没。我做过一个竞品调研的 Agent,让它连续采集目标产品的公开信息,跑了二十几轮之后,模型把最开始指定的筛选条件全给忘了,开始自己编结论。这不是模型变“笨”,而是有效上下文被稀释了。
后来我意识到,Agent 真正需要的不是“记忆”,而是“记忆管理”。该记什么、不记什么、旧信息怎么衰减、跨会话怎么恢复,这些如果都靠每次拼 Prompt 去解决,只会越搅越乱。那个调研 Agent 最后让我改成先提取关键约束存到记忆层,每轮只把约束和最近 3 条结论带进 Prompt,问题立刻缓解。所谓上下文管理,本质就是把负责“记得”这件事从模型手里拿回来,交给一个专门的存储层。
1.2 单 Agent 缓存和跨 Agent 记忆的区别
大多数 Agent 框架自带的记忆,本质上是 session memory:一个会话内保存历史消息,Session 结束就归零。单 Agent 场景里这个够用,但只要有协作,问题就出来了。比如你有一个编排者 Agent(Orchestrator),下面挂着规划、搜索、写代码三个子 Agent。理想状态下,规划 Agent 需要知道“用户最终目标是迁移服务且不允许长时间中断”,搜索 Agent 需要知道“已经找到哪些方案”,写代码 Agent 需要知道“上一版代码为什么被驳回”。
如果每个 Agent 只维护自己的 session,这种状态完全无法共享。那加长 context 行不行?可以,但对每个子 Agent 都传完整对话,token 爆炸不说,子 Agent 之间还会互相干扰,搜索 Agent 看到写代码 Agent 的内部推理,很容易被带偏。ai-memory 这类跨 Agent 记忆层做的事情,是把记忆从某个具体 Agent 进程里抽出来,变成一个公共基础设施。所有 Agent 都可以写,也可以按权限读。你可以把它理解成一个团队共用的白板,而不是每个人手里一本私人笔记本。白板上写什么、谁能擦,是项目管理的问题,但前提是白板存在。
1.3 什么时候你才真的需要它
我现在的判断标准很简单:如果你的 Agent 只是单轮问答、或者简单的多轮闲聊,引入记忆层就是自找麻烦,多一层服务多一层故障。但出现下面三种情况,就需要认真考虑:
- 任务跨多个 Session 执行,比如用户上午交代需求,下午回来继续,你不想让他重新描述一遍。
- 多个 Agent 必须共享中间状态,比如编排者要做任务进度汇总,子 Agent 要复用搜索结果。
- 你希望 Agent 长期记住用户偏好、项目约定这些“语义记忆”,而不是每次从零学习并说一堆正确的废话。
从我对社区里同类项目的观察来看,很多人做多智能体最后败给的不是模型能力,而是状态管理。模型输出不稳定可以靠 Prompt 约束,状态乱了几乎没法修。所以记忆层不是锦上添花,它在多 Agent 项目里是刚需。
2. 拆开 ai-memory:核心设计思路与关键模块
2.1 一个抽象层,而不是又一个向量数据库
第一次看这个项目,我下意识以为它就是个封装好的向量检索库。后来细看它的设计,才发现核心价值是“抽象层”:对外暴露一套统一记忆接口,底层存储可以切换。也就是说,你不必先决定用 Chroma、Redis 还是 pgvector。你可以先接 SQLite 跑通逻辑,等数据量大了再切到 Postgres 加 pgvector,接口不变,上层 Agent 代码不用改。
这个设计对生产项目来说很关键,因为选型不是一次性决定的,你在开发期也很难预测未来记忆量会涨到多大。你今天选了个轻量级 JSON 存储,明天数据涨到几十万条,迁移成本会高到让你原地放弃。有了一层抽象后,底层存储是后端的事情,你关心的是记忆怎么写、怎么查。
它还顺带解决了一个很实际的问题:不同 Agent 可能由不同团队维护,有的在 Python 服务里,有的在 Node 服务里。如果每个服务各自接一套存储,数据就分片了。有了统一层后,大家看到的都是同一个记忆空间,接口语义一致,只是语言 Client 不同。这一点在团队协作项目里价值非常大,因为你不用规定大家必须用同一个语言技术栈,只需要约定一条记忆协议。
2.2 记忆条目:不是简单文本,而是带属性的结构化对象
我后来仔细看了它的数据模型,发现它把每条记忆处理成带元数据的结构体。大致包含以下字段:
| 字段 | 作用 |
|---|---|
| content | 记忆正文,可以是文本或结构化描述 |
| memory_type | 记忆类型:情景记忆、语义记忆、程序记忆 |
| importance | 重要度,0 到 1,影响长期保留优先级 |
| agent_id | 写入这条记忆的 Agent |
| session_id | 所属会话 |
| namespace | 命名空间,用于多团队、多环境隔离 |
| created_at / last_accessed_at | 创建时间和最近读取时间 |
| metadata | 自定义标签、来源、任务 ID 等扩展字段 |
为什么要分 memory_type?说到底,记忆的性质不一样。情景记忆是“某次具体事件”,比如上一次部署发生超时;语义记忆是“用户偏好”,比如回复要简洁;程序记忆是“怎么做事”,比如迁移前必须先备份。如果不区分,检索时容易把临时状态当长期规则用,Agent 就会做出错误推理。
这个设计有点像整理抽屉:你肯定不会把备用钥匙和购物小票混在一起。Agent 记忆也一样,分类越清晰,拿出来用的时候越可靠。实际经验是,当我把用户的长期偏好和单次任务的临时约束分开存放后,Agent 的行为稳定性提高了很多。否则临时约束会频繁污染长期偏好,模型经常把上一次任务的细节当成通用规则。
2.3 检索不只看向量相似度,时间和重要度也算分
ai-memory 的检索逻辑如果只看表象,就是一个标准的“向量检索 + 元数据过滤”。但实际用下来,它会更重视混合评分。大概可以理解成:
排序分 = 向量相似度 × 时间衰减系数 × 重要度系数
向量相似度解决“内容相关”,时间衰减解决“旧信息降权”,重要度解决“关键约束不能被淹没”。这三个维度缺一个都会出问题。只按相关度找,你会反复捞到三个月前的聊天记录;只按时间找,又会把关键知识点丢掉。我在另一个项目里就犯过这种错,纯向量检索跑出来的结果经常是“相关的废话”。
举个场景:用户三个星期前提过一句“部署时尽量避开中午高峰”,这周又问你部署方案。从语义相关性看,这句旧话仍然相关,但如果时间权重太重,它可能被压到十名开外。反过来,如果只看相关度不看时间,系统会把上个月已经过时的“禁止使用新镜像仓库”的建议反复推给 Agent,导致它一直按照旧规则干活。只有把时间衰减调得温和一点、重要度权重配高一点,才能让这类记忆既不被遗忘,也不会霸屏。
2.4 跨 Agent 的共享、隔离和可见性
跨 Agent 记忆层最核心的设计点是命名空间。我个人的理解是:命名空间负责环境隔离,agent_id 负责身份识别,可见性负责粒度的共享控制。常见玩法有三种:
- 完全共享:所有 Agent 读同一个 namespace,适合团队知识库、共享任务状态。
- 按 Agent 隔离:每个 Agent 有独立命名空间,但底层还是统一存储,方便后续迁移。
- 目录式共享:比如写代码 Agent 能读运维 Agent 的部署记录,但反过来不行。
这种模型在工程上很容易实现,就是一个访问过滤层的问题。但我要强调一句:记忆层的隔离只是应用层隔离,如果你的 Agent 在处理用户敏感信息,不要靠它当安全边界,该加的权限校验、数据脱敏仍然要做。我的习惯是把记忆层定位成“协作工具”而不是“安全系统”,敏感数据要么不写入,要么写入前先经过脱敏规则。
3. 实操落地:把 ai-memory 接入项目的完整流程
3.1 安装与后端选型
我一般按这个顺序来:先装基础库,开发环境用 SQLite 后端跑通;如果项目提供 Docker 镜像,可以直接把服务跑起来,省得自己管理底层存储依赖。然后准备嵌入模型。两个选择:接远端 Embedding 接口,或者本地跑一个小模型。个人开发时远端 API 方便,但每条记忆都要网络调用,延迟和成本都有限制。生产环境我更推荐本地 embedding 模型,私密性好,批量导入时也能把吞吐打上去。
后端选型上,我的建议是:
| 使用阶段 | 推荐后端 | 原因 |
|---|---|---|
| Demo | SQLite | 零配置,文件即持久化 |
| 单机生产 | Redis / SQLite 高配 | 简单、可靠、运维成本低 |
| 多实例生产 | pgvector / Elasticsearch | 支持并发写入和复杂索引 |
需要注意的是,如果你用向量检索,后端必须支持向量索引。嵌入式列表使用不当会导致检索退化成暴力扫描,等记忆量过万就会明显变慢。另外,选后端之前先确认你用的 embedding 模型输出维度是多少,不同后端支持的向量长度限制不一样,别等数据都写进去了才换库。
3.2 最小可用接入:记录与检索
下面是一个最简单的接入例子,我用伪代码写出来,方便你对照自己的项目改:
from ai_memory import MemoryClient client = MemoryClient( backend="sqlite", path="./memory.db", collection="demo" ) # 写入一条语义记忆 client.remember( agent_id="assistant", content="用户偏好:回复尽量简短,多用列表,不要默认给大段背景分析。", memory_type="semantic", importance=0.9, ) # 检索与问题最相关的历史记忆 results = client.search( "用户希望我以什么风格回复?", top_k=5, filters={"memory_type": "semantic"}, ) for r in results: print(r.content, r.score)接入的位置通常在 Agent 主循环里。流程大概是:用户提问 → 先查记忆 → 把记忆组装进 Prompt → 让模型回答 → 把本轮问答提炼后写回记忆。写回之前最好做一次内容压缩,不要把原始对话整段塞进去。比如模型回答完,你可以先让它生成一句总结:“用户本次要求 xx,最终结果 xx”,再把总结写入。
我在项目里还会给 Agent 装一个“记忆钩子”:每次向 LLM 发请求前,先根据用户当前问题查询 top_k 条记忆,再塞进 System Prompt 的固定区块。这样既不会污染主对话,又能让模型始终看到关键背景。这里有个小技巧:记忆检索不要在用户刚发完消息就做,先让模型判断“这个问题是否需要历史信息”,需要再查。很多简单问题根本用不上记忆,强行检索反而可能引入无关干扰。
3.3 多 Agent 共享与隔离配置
多 Agent 场景下,我会在配置里明确 namespace 和可见性。下面是一个典型的团队协作配置:
namespace: "ops-team" default_visibility: "shared-read" agents: - id: "scheduler" memory_type: ["episodic", "semantic"] write: true - id: "executor" memory_type: ["procedural", "semantic"] write: true read_from: ["scheduler"]这个配置表达的意思是:ops-team 命名空间下,所有 Agent 默认只能读共享记忆;scheduler 可以写调度相关的临时记录;executor 可以读 scheduler 写入的内容,同时写入自己的操作经验。这样一来,编排者发现“目标服务器磁盘余量不足”这类状态,executor 就能在下一次执行时直接命中,而不需要把整条历史对话传给它。
实际运行中,我发现这种设计最大的价值不是技术上的,而是行为上的:每个 Agent 不用在每次请求里重复解释背景,写代码的 Agent 也不需要关心上一个 Agent 的完整聊天记录,它只需要看共享记忆里被标记为“当前任务状态”的那部分。这极大减少了 token 浪费和上下文互相干扰。当然,配置要克制,别把可见性规则设计得太复杂。我见过一个项目搞了七八种角色和十几条读取规则,最后没人能说清哪条记忆会被谁读到,调试起来非常痛苦。
多 Agent 记忆的配置建议:
| 场景 | 配置方式 | 效果 |
|---|---|---|
| 共享任务状态 | 同一 namespace,全部 shared-read | 所有 Agent 都能看到进度 |
| 用户私有偏好 | 独立 namespace,单 Agent 可见 | 隔离用户数据 |
| 团队经验库 | 统一 namespace,只允许写,不允许改 | 沉淀可靠经验,防止互相覆盖 |
3.4 生产环境里的优化手段
把 ai-memory 放到生产环境,有四个优化点值得提前做。
第一,批量 Embedding。别每条记忆单独调用一次模型接口,累积到一定数量后批量处理,吞吐能差出好几倍。我试过用本地 embedding 模型批量处理历史导入,十万条数据从原先预计的几小时压缩到了二十分钟左右。
第二,异步写入。记忆写入不该阻塞 Agent 主流程。我通常会把“写记忆”丢进消息队列,先让 Agent 回复用户,后台再慢慢保存。否则每次生成完答案都等一次 embedding,整体响应时间会肉眼可见地变长。
第三,定期重建向量索引。数据持续写入后,向量索引可能因为删除、更新操作变得碎片化。定一个定时任务,比如说每周重建一次,检索质量会稳定很多。这个操作和数据库重建索引类似,做与不做,响应延时和准确率差别很明显。
第四,记忆预聚合。有些记忆会被高频读取,比如用户偏好、项目约束。可以额外维护一个常驻内存的“热记忆”缓存,查询时先看热缓存,再看向量库。这个优化在小数据量时看不出来,数据量过了十万条之后会非常明显。
4. 接入后必看:常见问题与排查技巧实录
4.1 检索出来的记忆总是不相关
这是我被问得最多的问题。你改了 Prompt、调整了 Top K,结果还是搜出一堆不相关内容。这时候先不要怪项目,按顺序排查:
- 检查所有记忆是否是用同一个 embedding 模型写入的。中途换过模型,新旧向量很可能不在同一语义空间里,余弦相似度就失真。
- 打印一下候选结果的时间分布。如果全是旧记忆,说明排序权重里时间衰减可能设置得太弱或太强。
- 确认 query 本身没有经过额外改写。有些 Agent 框架会先让模型把用户问题改写成检索词,改写出错,检索自然跟着错。
我自己排查时会开一个调试开关,把 search 时的原始 query、候选 id、向量相似度、最终得分全部打印出来。看一眼分数组成,问题基本就能定位。有一次我发现检索结果不相关,最后查出来是写入时把文本自动加了前缀“用户说:”,查询时没加,语义空间偏移了。这种隐藏的很小的不一致,不打印原始数据很难发现。
4.2 记忆越存越多,查询越来越慢
这个问题的根源是缺少遗忘机制。真实人类记性有限,Agent 记忆如果只增不减,到最后库里全是低价值数据。我的做法是三层清理策略。
第一层是短时效清理:临时中间结果超过 24 小时直接删除。第二层是重要度淘汰:低重要度且长期未被读取的记忆,先标记为 archive;第三层是预算控制:给每个 namespace 设置记忆条数上限,达到上限后按综合分数淘汰老条目。保持存储“精”比“大”更重要。你如果试过在一个几十万的向量库里做实时检索,就知道哪怕有索引,响应时间也会逐渐失控。
清理策略要设计成可观测的。我会定期统计记忆总量、命中率、平均检索耗时。如果发现命中率下降但总量还在涨,说明库里垃圾变多了,该加大清理力度。千万别在线上直接删,先标记为 archive,观察一段时间没问题再物理清理。
4.3 多实例并发写入导致数据串号
跨 Agent 记忆层跑起来后,多个 Agent 实例同时写入同一个 namespace,容易出现两类问题:同一条业务信息被反复写入多份,或者读取时读到了其他 Agent 的中间状态。解决思路是给记忆写入加“业务幂等键”。比如用任务 ID 加消息序列号作为去重键,写入前先检查是否已存在。如果底层存储支持唯一索引,可以直接在数据库层卡住重复项。
另外要注意后端的选择。早期我用 SQLite 做联调,单机没问题,多实例部署后 SQLite 的锁竞争就开始显现。后来切到 pgvector,写入冲突基本消失了。所以如果你在设计阶段就知道会有多个 Agent 实例,就别在 SQLite 上耗时间。
还有一个容易忽略的点:session_id 的生成规则。如果多个实例共用同一个 session_id 前缀,很容易把不同任务的数据混在一起。我的习惯是 session_id 里带上任务 UUID,而不是用 Agent 类型名。这样即使并发执行多个任务,记忆也不会交叉污染。
4.4 记忆污染:Agent 把垃圾内容当重要信息记下来了
这是最难处理的一个问题,因为它不是技术故障,而是数据质量问题。Agent 在运行过程中会产生大量噪音,比如模型自行脑补的假设、某次失败的中间输出、根本没执行完的步骤。如果不加过滤,这些垃圾会变成“记忆”,下次查询还可能被命中,然后带着 Agent 走向错误方向。
我现在会做两件事。第一,写入门槛。重要度低于 0.3、或者来源标记为“推理中间结果”的内容,一律只存短期缓存,不进长期记忆。第二,来源审计。每条记忆都写清楚来源,是用户消息、工具返回,还是 LLM 自己的总结。这种字段平时检索用不上,但排查“Agent 为什么这么干”时价值巨大。我甚至会在开发环境把每个 Agent 读到的记忆、写入的记忆全部打日志,像看监控一样回放 Agent 的思考链条。
最后的最后,分享一个我自己的小习惯:把 ai-memory 的写入接口再加一道“人审钩子”。在开发测试阶段,凡是标记为重要度大于 0.8 的记忆,自动抄送一份到我的调试群。刚开始有点烦,但用了一周后,你会很清楚地看到 Agent 到底在记什么。很多看似莫名其妙的行为,过滤掉几条脏记忆就好了。这个习惯我从第一个 Agent 项目用到现在,对于维护跨 Agent 记忆系统来说,收益远大于成本。