
做 Agent 开发的朋友应该都有同感最难的往往不是模型能力本身而是怎么让 Agent “记住”说过的话、做过的事。今天聊的开源项目ai-memory一个 7.9K Stars 的跨 Agent 记忆层就是专门解决这个“记忆断层”问题的。如果你的 Agent 经常出现“上一轮说过的事下一轮就忘了”、或者多个 Agent 之间互相不知道对方干了什么那这个项目值得你花十分钟认真看看。先说清楚它是什么ai-memory 不是某个具体的聊天机器人也不是某个 Agent 框架而是一个独立的记忆服务。它给你提供一套标准化的“记忆读写 API”让任何 Agent 框架都能接入。换句话说它把“记忆”这件事从你的 Agent 业务逻辑里抽出来做成了一层独立的基础设施。适合谁看正在用 LangChain、自研框架做 Agent 开发的工程师以及所有被“长期记忆”“跨会话记忆”折磨过的同学。1. Agent 为什么需要独立的记忆层1.1 先从“大模型没有记忆”说起用过 ChatGPT 或者接过大模型 API 的都知道大模型本身是无状态的。你每次调用 API它接收的是你发给它的这一段话它不知道你昨天跟它聊过什么也不知道三分钟前它在另一个会话里说过什么。这就像你每次去找同一个医生都得从头到尾重新描述一遍病情——医生每次接诊都“失忆”。放在聊天场景里这顶多是体验问题用户多重复几次就行。但放在 Agent 场景里这就是硬伤。Agent 的核心工作是“多步任务拆解 工具调用 结果汇总”它天然需要在多轮里记住自己执行到哪一步了、用户提出了什么约束、前面工具返回了什么数据。没有记忆Agent 的每一步都是“睁眼瞎”根本不可能完成复杂任务。打个比方Agent 就像工地上的施工队模型是工人本身工具调用是手里的电钻和锤子而记忆就是施工图纸和工程日志。图纸丢了、日志不写工人再熟练也盖不成楼。1.2 现有的“硬拼上下文”方案为什么不够用早期大家怎么解决记忆问题最粗暴的方式把全部历史对话一股脑拼进 Prompt。这种做法有三个致命问题。第一个是 token 成本。哪怕是中规模的对话几十轮下来可能就是上万 token每次请求都要把这堆历史重新发给模型成本和延迟都翻着倍涨。第二个是上下文长度天花板。现在模型窗口大了动辄 128K、200K但窗口再大也架不住 Agent 长期运行。你不可能指望着三个月之后还在 Prompt 里从头塞这几个月所有的对话记录。第三个更隐蔽就算你塞得下模型也不一定“看得见”。有研究Lost in the Middle表明大模型对长上下文的中间部分注意力会明显衰减。你辛辛苦苦拼进去的 50 轮历史模型真正能“吸收”的可能只有开头和结尾那一点。还有一种是“总结式记忆”每轮对话结束后把当前状态压缩成一段摘要存起来。这个方案的缺点是总结过程本身会丢细节而且写总结的逻辑要你自己维护做不好容易把关键信息比如用户偏好、参数配置丢掉。1.3 ai-memory 的定位把记忆做成一层服务ai-memory 做的事情就是把这些“记忆管理”的脏活累活全部收拢到一个独立服务里。它的核心价值我认为可以拆成三个“跨”。第一个是跨会话。同一个用户在两次不同的会话里问同一个 Agent 相同的问题Agent 能通过 ai-memory 检索到之前会话的信息给出连贯的回答。第二个是跨 Agent。多个 Agent 共用一个记忆服务A Agent 写进去的信息B Agent 能读到。这在一个多 Agent 协作的系统里非常有用——比如一个负责调研的 Agent 和一个负责写报告的 Agent共享同一份“参考资料记忆”就不用重复调研、信息还不会对不上。第三个是跨框架。它暴露的是 REST API跟你用什么框架没有关系。LangChain 能用、AutoGPT 能用、自研的一堆胶水代码也能用将来换框架不用把记忆逻辑重写一遍。这就是“记忆层”这三个字的含金量。它既不是模型也不是工具而是夹在 Agent 大脑和外部世界之间的一层“持久化缓存检索服务”专门解决“记住什么、怎么想起”的问题。2. 拆解 ai-memory 的核心设计四层记忆模型与双通道检索2.1 记忆分层的思路不能什么东西都往一个桶里塞我在实际用的时候发现ai-memory 做得好的一点是它没有把记忆混为一谈。它的存储模型是分层的按我的理解大致可以分成四层工作记忆Working Memory当前会话正在进行中的上下文信息比如“用户刚才说项目下周截止”。特点是短期、易变会话结束或任务完成之后就不再重要。情景记忆Episodic Memory历史会话的完整记录比如“昨天用户让我查过上海的租房价格”。特点是按时间组织偏向事实追溯。语义记忆Semantic Memory从对话里提炼出来的、可长期复用的知识比如“用户偏好深色界面”“用户公司用的技术栈是 Java”。特点是稳定、跨会话有效。程序性记忆Procedural MemoryAgent 学会了怎么完成某类任务的“操作经验”比如“处理退款时需要先调 A 接口再调 B 接口”。如果你不做这个分层所有记忆混在一起会出一堆问题临时的上下文被当成长期知识存了下来后面的会话都被污染真正的用户偏好淹没在海量操作记录里检索的时候排不到前面。分层之后每一类记忆可以配置不同的保留策略和检索权重。比如工作记忆默认保留 24 小时语义记忆却可以放一年。每个记忆条目在 ai-memory 里通常会有这样一组元数据{ id: mem_8f2h9d, agent_id: agent-research-01, session_id: session_ab12, type: semantic, content: 用户偏好深色界面字体大小偏好中等偏大, metadata: { created_at: 2025-01-15T10:23:00Z, last_accessed_at: 2025-01-18T09:00:00Z, access_count: 3, source: chat_consolidation }, embedding: [0.012, -0.034, ...] }id 是唯一标识agent_id 和 session_id 分别表示这条记忆属于哪个 Agent、哪次会话。type 就是上面说的分层类型。embedding 是文本向量也是后面检索的核心。这些字段设计并不复杂但对于后续的检索、隔离、清理都至关重要。2.2 双通道检索为什么不能只靠向量搞过 RAG 的朋友对“向量检索”应该不陌生把文本转成向量然后算余弦相似度。但 ai-memory 这类项目单用向量会踩一个坑——专有名词检索不到。举个例子用户之前说过“我们的服务部署在 Kubernetes 上”。如果后面他问“k8s 相关的配置在哪”语义向量检索往往匹配不到那一条记忆因为 “Kubernetes” 和 “k8s” 语义关联在向量空间里并不像你想的那么紧密。再比如代码里的函数名、API 名这些专有名词对向量模型很不友好。ai-memory 的做法是双通道检索向量通道管“语义相似”关键词通道管“精确匹配”。关键词检索用 BM25 这类传统算法对专有名词、短文本的命中率比纯向量高得多。最后把两个通道的结果做一个融合排序常见做法是 RRFReciprocal Rank Fusion。RRF 的计算也很直白对每个通道的检索结果按排名给分排名越靠前分越高然后把两个通道的分数加总。只需要一个求解极简公式score(d) Σ 1 / (k rank_i(d))其中 k 通常取 60rank_i(d) 是文档 d 在第 i 个检索通道里的排名。这个公式不用调权重天然地平衡了向量通道和关键词通道的贡献实用性很强。这种混合检索策略基本已经成这类记忆服务的标配了。2.3 API 设计与多 Agent 隔离ai-memory 对外暴露的是标准的 REST API核心就三个写记忆、查记忆、删记忆。写记忆的请求大致长这样POST /v1/memories Content-Type: application/json Authorization: Bearer agent-token { agent_id: agent-research-01, session_id: session_ab12, type: semantic, content: 用户偏好深色界面, importance: 0.8 }查询记忆GET /v1/memories/search?queryk8s部署agent_idagent-research-01top_k5你可能会问为什么要传 agent_id这就是“跨 Agent”和“多 Agent 隔离”的关键设计。每个 Agent 有自己独立的内存空间一个 Agent 默认看不到另一个 Agent 的记忆避免数据和上下文串味。但在需要协作的场景可以主动把某条记忆的可见性放开让它变成团队共享记忆。API 层还做了超时重试和幂等写入。幂等这块比较关键同一句话被多次写入不会产生重复的记忆条目而是合并更新旧条目的权重和时间戳。这在高并发的 Agent 场景里很实用因为一个 Agent 可能在多个线程里同时执行记忆保存操作。3. 从零上手部署、写入、集成 Agent 框架3.1 一条命令把 ai-memory 跑起来ai-memory 提供了 Docker 镜像部署确实省心。找一个至少有 2 核 4G 内存的机器跑下面这行命令一个完整的记忆服务就起来了docker run -d \ --name ai-memory \ -p 8000:8000 \ -e MEMORY_DB_URLpostgresql://memory:memoryhost.docker.internal:5432/memory \ -e EMBEDDING_MODELtext-embedding-3-small \ -e EMBEDDING_API_KEYsk-xxx \ -e TOP_K_DEFAULT5 \ ai-memory/ai-memory:latest有三个环境变量要特别说一下MEMORY_DB_URL记忆的持久化存储位置。本地调试可以用 SQLite生产环境建议用 PostgreSQL需要开 pgvector 扩展。EMBEDDING_MODEL 和 EMBEDDING_API_KEY向量化用的模型。注意两条铁律第一建议用市面上主流的 Embedding 模型第二写入和查询必须用同一个模型。换了模型以前存的向量和新的向量语义空间不一致检索就是垃圾进垃圾出。TOP_K_DEFAULT默认每次检索返回的记忆条数。按我的经验单次检索返回 3~5 条最合适。给太多了上下文会杂。如果你本地没有 PostgreSQL也可以先跑个最简单的 SQLite 模式做功能验证docker run -d -p 8000:8000 -e MEMORY_DB_URLsqlite:///./data/memory.db ai-memory/ai-memory:latest起来之后curl http://localhost:8000/health能看到服务状态就算部署成功了。3.2 写入一段记忆再把它检索出来部署完我用 Python 做了一个最基础的“写入-检索”闭环验证。先安装依赖然后跑这段脚本import requests BASE_URL http://localhost:8000/api/v1 def save_memory(agent_id, content, memory_typesemantic, importance0.5): resp requests.post(f{BASE_URL}/memories, json{ agent_id: agent_id, type: memory_type, content: content, importance: importance, }, headers{Authorization: Bearer test-token}) resp.raise_for_status() return resp.json() def search_memory(agent_id, query, top_k5): resp requests.get(f{BASE_URL}/memories/search, params{ agent_id: agent_id, query: query, top_k: top_k, }, headers{Authorization: Bearer test-token}) resp.raise_for_status() return resp.json() # 写入两条记忆 save_memory(agent-research-01, 用户偏好深色界面字体大小偏好中等偏大, importance0.9) save_memory(agent-research-01, 客户项目 deadline 是下周五, importance0.8) # 检索“界面风格偏好”相关的记忆 results search_memory(agent-research-01, 界面风格 主题 偏好, top_k3) for item in results[memories]: print(f({item[score]:.3f}) {item[content]})正常情况下第一条带“深色界面”的记忆会排在首位。这里有个细节如果你使用重要性字段查询结果里除了相关性分数ai-memory 还会把“重要性”作为排序的加权因子一起考虑。给用户明确表达过的偏好打高分这种长期记忆就不会被无关消息顶掉。3.3 把它接到 LangChain 里替换默认内存很多朋友用的 Agent 框架是 LangChain。接入 ai-memory 的核心就是写一个自定义的 Memory 类把框架默认的ConversationBufferMemory替换掉。下面是一个实现了核心接口的最小示例from langchain.memory import BaseMemory from langchain.schema import BaseMessage, HumanMessage, AIMessage import requests class AiMemory(BaseMemory): def __init__(self, agent_id: str, base_url: str http://localhost:8000/api/v1): self.agent_id agent_id self.base_url base_url property def memory_variables(self) - list[str]: return [memory] def load_memory_variables(self, inputs: dict) - dict: query inputs.get(input, ) resp requests.get(f{self.base_url}/memories/search, params{ agent_id: self.agent_id, query: query, top_k: 5, }).json() return {memory: [m[content] for m in resp[memories]]} def save_context(self, inputs: dict, outputs: dict) - None: user_input inputs.get(input, ) ai_output outputs.get(output, ) # 这里可以做简单的策略用户输入存为语义记忆AI 回复存为情景记忆 for content, mem_type in [ (f用户说{user_input}, episodic), (f助手回复{ai_output}, episodic), ]: requests.post(f{self.base_url}/memories, json{ agent_id: self.agent_id, type: mem_type, content: content, importance: 0.5, })然后把这段 Memory 传给 Chainfrom langchain.chains import ConversationChain from langchain_openai import ChatOpenAI chain ConversationChain( llmChatOpenAI(modelgpt-4o-mini), memoryAiMemory(agent_idagent-demo-01), verboseTrue, ) print(chain.run(你好我叫小明我喜欢深色模式)) print(chain.run(我叫什么名字我的偏好是什么))第二次提问时Agent 会通过 ai-memory 检索到刚才写入的“用户叫小明喜欢深色模式”然后正确回答。就这么简单几十行代码把长期记忆接进来了。4. 生产环境落地多 Agent 协作、并发与记忆治理4.1 多 Agent 共享记忆的正确姿势我刚开始用 ai-memory 时犯过一个错误把所有 Agent 塞到同一个记忆空间里结果 A Agent 的临时状态变成了 B Agent 的“长期记忆”两个 Agent 互相污染。正确做法是设计好命名空间和共享规则。我现在的实践是分三类空间私有空间每个 Agent 专属。存会话过程中的临时上下文、工具调用中间结果。其他 Agent 不可见。团队空间一组协作 Agent 共享。适合放项目背景、用户核心偏好、公共调研结果。比如“用户公司是电商行业主技术栈是 Java”这种信息所有协作 Agent 都该知道。全局空间整个系统共享。放的是全局规范比如“输出格式必须遵守 Markdown”“禁止在回复中包含内部 API Key”。在 ai-memory 里这三类空间可以通过 agent_id 的命名规则实现agent-research-01对应私有空间team-core-01对应团队空间。隔离做得好的话几十个 Agent 共存也不会串味。4.2 并发场景怎么扛住压力很多朋友关注 ai-memory 能不能扛并发。我的经验是它的架构天生适合水平扩展但你要懂得怎么用。首先写入操作建议做成异步的。Agent 的主流程里调用一次 save_memory如果同步等待写入完成那就白白多了一次网络 RTT。正确姿势是独立线程或消息队列异步处理写入失败也不影响 Agent 主流程。我写过一个简单的异步包装from concurrent.futures import ThreadPoolExecutor memory_pool ThreadPoolExecutor(max_workers4) def async_save_memory(agent_id, content): memory_pool.submit(save_memory, agent_id, content, semantic, 0.5) # Agent 主流程里直接调用不阻塞 async_save_memory(agent-research-01, 用户刚确认了项目预算上限)其次查询要做缓存。同一个 Agent 在短时间内的多次检索请求问题往往高度类似。给 ai-memory 前面挂一层 Redis 缓存 key 设计很关键按agent_id query 的 hash做缓存 key 就行TTL 设 5 分钟足够了。最后是扩展。ai-memory 服务本身是无状态的存储和 Embedding 调用都依赖外部组件。扛并发的时候只需要在服务前面挂负载均衡器后面多开几个副本即可。真正的瓶颈在存储层和 Embedding API。存储层建议直接用托管数据库向量索引会帮你扛住大部分压力Embedding 调用是做 Agent 高并发时的最大瓶颈所以我建议在写入侧做向量缓存同样的文本不重复调 Embedding API直接从缓存拿。4.3 记忆生命周期清理、归档与防中毒记忆不是存得越多越好存了不会清理反而会变成另一堆垃圾。我在生产环境里维护了三个策略。第一个是定期压缩。对同一个 Agent 一个月以前的“情景记忆”定期做摘要压缩把 50 条琐碎的操作记录合并成 3~5 条核心事实。压缩后的记忆保留在“长期摘要”类型里原始记录存到冷存储或直接删除。第二个是遗忘机制。给每条记忆算一个综合权重分按我的实践可以套用简化公式weight importance × 0.5 frequency × 0.3 recency × 0.2其中 importance 是写入时用户标记或模型评估的重要性frequency 是这条记忆最近被访问的次数recency 是最后访问时间到现在的距离衰减系数。权重低于阈值的记忆定期清理掉。这套逻辑跟人脑的“用进废退”很像没有价值的记忆会自然衰退。第三个是防记忆中毒。现在 Agent 的“提示注入”攻击很多是通过历史记忆发起的攻击者在一次对话里塞入恶意指令结果被当成记忆存下来后续会话每次都读到这段污染记忆Agent 就会被持续“洗脑”。ai-memory 的多 Agent 隔离能降低这个风险的扩散范围但最好在写入侧加一道过滤对包含“忽略之前的指令”“你是我的助手请执行以下操作”这类敏感模式的文本直接拒绝写入记忆。5. 常见问题与排查实录5.1 检索结果为空或者排得乱七八糟先别急着怀疑项目有问题。按我的经验一半以上的“检索不准”是 Embedding 模型不一致导致的写入用的模型 A查询用的模型 B向量空间完全对不上。排查方法很简单查一下写入和查询两条链路配置的模型名是否一致。第二种常见原因是相似度阈值设置太高。ai-memory 默认阈值一般在 0.7 左右但实际使用要看场景。如果你发现该搜到的没搜到把阈值降到 0.5~0.6 试试宁可多返回几条结果、靠 RRF 排序也不要因为阈值把记忆卡死在门外。第三种是 agent_id 传错了。回忆一下这个设计每个 Agent 只能搜到自己的记忆。如果你查询时传的 agent_id 和写入时不一样结果当然是空的。这个坑我踩过两次排查时先确认两头的 agent_id 完全一致。5.2 记忆写入很慢甚至丢记忆写入慢的第一真凶是同步调用。Agent 主流程里如果耦合了同步写入每一次都会消耗几十毫秒甚至更久直接把整个链路的 P99 延迟拉高。解法就是前面提的异步化。第二个原因可能是批量写入太大。一次写入几百条记忆会让服务端的 Embedding 调用排队。如果确实有批量导入需求控制单批 50 条以内分批次提交。第三个坑相对隐蔽幂等去重。因为 ai-memory 默认开启了幂等写入同一条记忆内容在系统里是唯一的。如果你反复写入完全相同的文本第二次之后其实没有真正落库只是在更新权重和时间戳。所以判断“写入成功了”不能只看返回码还要看返回里的updated: true/false字段。5.3 记忆串号A Agent 读到了 B Agent 的私密信息出现这个问题几乎可以断定是访问控制和命名空间配置出了问题。最常见的原因是某些请求在写代码时忘了传 agent_id服务端把它归到了一个默认的全局空间里然后所有 Agent 都能看到这部分数据。解决思路有两个层面。第一代码层做强校验调用 ai-memory 的所有读写接口必须带上 agent_id不允许用单 Agent 模式糊弄过去。第二服务层做权限收敛为每个 Agent 分配独立的 Auth TokenToken 里绑定特定的命名空间一个 Token 只能读写对应的空间。这样即使代码里忘了传 agent_id服务端也能从 Token 推断归属。5.4 排查速查表最后把最常遇到的几个问题整理成一张表方便你对照排查症状可能原因排查思路解决方案检索结果为空Embedding 模型不一致对比写入和查询链路的模型配置统一使用同一个 Embedding 模型检索结果为空相似度阈值过高查看服务端配置里的阈值参数下调阈值到 0.5~0.6 再测试检索结果为空agent_id 不一致检查写入和查询的 agent_id确保两处使用同一个合法空间 ID写入慢 / 延迟高同步写入阻塞主流程看 Agent 日志里 save 耗时改成异步写入或消息队列写入返回成功但查不到幂等去重内容未变更检查返回是否带 updated: false确认是否真的需要重复写入记忆串号未传 agent_id检查代码是否漏参用 Token 绑定命名空间做强校验检索结果混杂、不准混合检索排序参数不合理查看 RRF 的 k 值设置调整 k 值默认 60 通常够用从我的实际体验来看ai-memory 这类“记忆层”项目解决的是 Agent 落地过程中最绕不开的痛点上下文隔离、长期存储、跨 Agent 共享。它并不神秘核心就是一套结构化存储加混合检索的服务。搭好这套东西之后你的 Agent 才有了“长性”不会每会话都从零开始。如果你正在做一个正经的 Agent 产品与其自己造一套记忆轮子不如先把这类开源方案用起来等业务复杂度真的压不住的时候再照着它的设计去改造成自己的内部基础设施。我自己就是从“全量塞 Prompt”到“自己写摘要记忆”再到“用 ai-memory 这类记忆层服务”一步一步趟过来的。最后提醒一句记忆层的核心秘密不在 API 而在数据治理把“写什么记忆、留什么记忆、给谁看记忆”这三件事想明白比部署多少副本都管用。