ai-memory:跨Agent共享记忆层的设计与工程实践
如果你和我一样最近半年一直在折腾 Agent 项目一定会被同一个问题反复折磨模型本身是没有记忆的。上下文窗口再大对话一关它就跟喝了忘崽牛奶一样什么都得重新教一遍。单个 Agent 还能靠调整 prompt、拼接历史记录硬撑一旦上了多 Agent 协作问题就彻底暴露了——每个 Agent 各记各的聊天记录各存各的向量库互相之间完全不互通。你让一个 Agent 去收集用户偏好另一个 Agent 生成代码时完全不知道这些偏好那这套系统跟几个互不说话的插件有什么区别这个 ai-memory 项目7.9K Stars 的跨 Agent 记忆层解决的就是这个痛点。它不是给单个 Agent 套一个记忆组件而是把记忆从 Agent 里抽出来做成一个独立的服务层。任何 Agent、任何框架、任何语言都能通过统一的接口读写同一份记忆池。说白了就是给 Agent 们装一个共享的企业级 CRM而不是让每个人继续在自己的小本本上记笔记。这篇内容我会从项目理念、核心设计、部署接入、调优治理到排坑经验完整分享一遍。适合正在做多 Agent 系统、私人助理、客服机器人或者想给现有 Agent 框架加记忆能力的开发者参考。不管你之前有没有接触过记忆层跟着走一遍至少能少踩一半的坑。1. 项目概述与解决的痛点1.1 Agent 记忆到底是什么在聊 ai-memory 之前先把记忆这件事拆清楚。我在实际项目里习惯把 Agent 记忆分三层短期记忆当前对话上下文本质就是模型上下文窗口里的内容。对话一结束这些内容就没了。长期记忆从多轮对话或者任务执行中提取出来的事实、偏好、决策记录存到外部存储里下次需要时可以重新加载。跨 Agent 记忆多个 Agent 共享一套长期记忆。某个 Agent 学到的东西其他 Agent 也能读取和引用。短期记忆靠 prompt 拼接和滑动窗口就能搞定长期记忆也不算难本质上就是“提取关键信息 向量化 相似度检索”。真正麻烦的是第三层。举个例子你做一个项目管理系统里面有三个 Agent一个负责需求分析一个负责代码生成一个负责写测试用例。需求 Agent 在跟用户交流时发现了一个技术约束“用户要求必须兼容国产数据库”。如果这三个 Agent 各管各的记忆代码生成 Agent 完全不知道这个约束生成的 SQL 可能全是 MySQL 方言最后测试环节各种报错。ai-memory 解决的问题就是让这种约束信息只被写入一次后续所有相关 Agent 在执行任务时都能自动检索到。它像一个团队共享的知识库而每个成员都有一本可以随时查阅的工具书。1.2 ai-memory 的项目定位这个项目的核心定位不是“某个框架的配套插件”而是一个独立的记忆中间件。它对外提供一整套记忆读写接口内部帮你处理了存储、索引、检索、衰减、权限隔离这些脏活累活。我看到的几个关键设计理念跟 Agent 框架解耦不管是 LangChain、CrewAI、Dify 还是你自己手写的 Agent 循环都可以通过 HTTP 或 SDK 接入。记忆层不关心上层是 React 模式还是 Plan-and-Execute 模式。以记忆对象为中心每条记忆都被建模成一个独立单元带身份、来源、重要度、过期时间、访问策略这些属性。所以你可以精确控制一条记忆该不该被共享、该不该被淘汰。自带检索与遗忘策略不是把数据丢进向量库就完事而是内置了混合检索、时间衰减、重要度排序这些机制。你拿到的不是一个原始向量列表而是整合排序后的上下文。它的核心价值在于替你把多 Agent 记忆中最容易做错的部分——共享与隔离的边界、记忆污染与遗忘的平衡——提前做了封装。适合跑在多个 Agent 需要协同、记忆需要复用、又不想重复造轮子的场景。2. 核心功能拆解与设计思路2.1 记忆单元模型给每条记忆一个身份证第一次看 ai-memory 的记忆模型时我最大的感受是它把“记忆”当成了和“用户”“订单”一样的一等公民。一条记忆不是一个字符串而是一个具有完整属性的对象。常见的记忆字段包括字段作用我的理解memory_id记忆唯一标识没有 ID后续更新、删除、去重都是空谈agent_id来源/归属 Agent标明这条记忆是谁写入的也是隔离的基础session_id所属会话用于追踪记忆来自哪次对话便于回放content记忆正文通常是一句完整陈述比如“用户偏好使用 Python 3.12”metadata业务扩展字段可以塞项目代号、业务线、标签等任意信息embedding语义向量由 content 向量化得到用于相似度匹配importance重要度0 到 1 的浮点数决定记忆是否容易被淘汰access_policy访问策略控制哪些 Agent/租户可以读到这条记忆created_at创建时间配合时间衰减做近期加权expires_at过期时间临时性记忆可以自动过期避免长期污染为什么要给记忆加这么多属性因为“跨 Agent 共享”有一个很现实的问题不是所有记忆都应该共享。用户在某次闲聊中说“今天工作好累”这种状态性记忆没必要让三天后的代码 Agent 知道但“用户所在团队采用微服务架构”这种长期事实必须让所有 Agent 在规划方案时能看到。我实际使用时会按重要度把记忆分成三层临时事实0 到 0.3、常规知识0.3 到 0.7、核心约束0.7 到 1.0。核心约束类记忆强制设置很长的过期时间写入时还会额外走一遍 LLM 摘要确保没有歧义。2.2 记忆写入链路从原始对话到结构化记忆ai-memory 的写入不是一个简单的 insert 操作而是完整的数据加工管线。我梳理了一下它在写入时会经历的几个关键步骤事件捕获监听对话结束、任务完成等事件拿到原始文本。内容清洗与拆分把冗长对话拆成一个一个可独立检索的陈述句。这一步很关键如果直接把整段对话塞进去后面检索时的噪音会非常大。语义摘要与信息抽取调用 LLM 把口语化内容转成规范化陈述。比如“我平时写后端多一些啦”会变成“用户的主要开发方向是后端”。向量化用 embedding 模型把规范化文本转成向量。去重与冲突检测把新记忆和已有记忆做语义相似度比对如果撞车要么合并、要么更新旧记录。双写存储结构化字段写关系型数据库向量写向量索引。我第一次上手时踩了个坑没有做去重结果同一个用户在测试环境重复对话了几轮记忆库里积累了一百多条“用户喜欢喝美式咖啡”。后面检索时模型甚至会被这些重复记忆带偏以为用户对咖啡有某种执念。后来我在写入前强制加了一个search预检相似度超过 0.92 就直接更新原记忆的时间戳不再新增。写入链路里还有一个设计值得提异步化。记忆写入不要阻塞主对话流程。你其实不需要等记忆完全落库后再回复用户。通常的做法是把写入事件丢进队列api 服务立刻返回成功后台再慢慢做向量化、去重和存储。这样对话首字延迟不会因为记忆写入而变慢。2.3 记忆读取链路不只是 top-k 向量检索很多简单的记忆实现读取就是“算 query 的向量然后去向量库里取 top-k”。我实测下来这种方案在玩具项目里没问题在真实 Agent 场景里效果很差。原因很简单语义相似度只解决了“意思相近”的问题没有解决“更相关”的问题。ai-memory 的读取链路上做了三层融合语义匹配向量相似度负责找到意思相近的记忆。适合用户说“我后端项目该怎么规划”去匹配“用户主要做后端开发”这种语义相关的记录。关键词匹配BM25 或全文索引负责找到包含精确实体的记忆。比如用户问“我们之前讨论过 Redis 集群吗”向量检索往往会被同义词干扰关键词匹配直接锁定“Redis 集群”这个实体准确率高得多。时间衰减近期记忆加权。同一个事实用户三个月前说“我们打算用 MongoDB”昨天改口“我们最终选型用了 PostgreSQL”那三个月前的记忆就不应该跟昨天的记忆处于同等权重。一条记忆的最终相关度可以粗略理解成final_score w1 * vector_score w2 * keyword_score w3 * recency_factor这三个权重可以在配置里调整。我的经验是通用问答场景向量权重大一些项目文档和代码场景关键词权重需要拉高。你可以观察自己的检索场景如果经常出现“明明提到过某个产品名但向量检索就是搜不到”大概率是关键词权重不够。还有一个很实用的细节上下文组装。记忆层不仅是把相关记忆找出来还要负责把它们压缩成适合放进系统提示词的形态。ai-memory 会在返回结果时携带来源 Agent、时间、重要度等信息你可以把结果按重要度排序后再按照当前上下文的 token 预算做截断。我一般会给它设定一个硬上限比如 1500 token防止记忆把上下文塞爆。3. 部署与集成实操3.1 快速启动Docker Compose 把记忆服务跑起来ai-memory 的部署不复杂我本地环境通常用 Docker Compose 一下拉起三个组件Postgres存结构化数据和向量、Redis做缓存和异步队列、memory-api对外提供 REST 接口。以下是一份我实测可用的 Compose 配置基本可以照抄version: 3.8 services: postgres: image: pgvector/pgvector:pg16 environment: POSTGRES_USER: memory POSTGRES_PASSWORD: memory_pass POSTGRES_DB: ai_memory ports: - 5432:5432 volumes: - pg_data:/var/lib/postgresql/data redis: image: redis:7-alpine ports: - 6379:6379 memory-api: image: ai-memory/api:latest depends_on: - postgres - redis environment: POSTGRES_DSN: postgresql://memory:memory_passpostgres:5432/ai_memory REDIS_DSN: redis://redis:6379/0 EMBEDDING_MODEL: bge-m3 EMBEDDING_DIM: 1024 ACCESS_TOKEN: change_me ports: - 8000:8000 volumes: pg_data:启动后先验证服务状态curl http://localhost:8000/health # 期望返回类似 # {status: ok, version: 0.4.2}有几个配置点需要说明EMBEDDING_MODEL决定向量化模型。我本地用的是bge-m3中文效果不错模型会在服务启动时加载到内存。如果你不想本地跑模型也可以接外部 embedding 服务把维度配一致就行。ACCESS_TOKEN是服务级访问凭证所有 SDK 请求都要带这个 token。生产环境务必换掉默认值。首次启动时如果接口返回 503别慌多半是 embedding 模型还在加载。看日志等模型 ready 再试。如果你是 Python 项目还可以直接通过 SDK 连接pip install ai-memory-client3.2 最小可用代码示例让两个 Agent 共享记忆现在我用一个最小场景来演示跨 Agent 记忆到底怎么工作。假设系统里有两个 Agent一个叫researcher负责跟用户聊天收集偏好一个叫coder负责写代码。用户告诉 researcher 自己偏好 Python 3.12 和 FastAPIresearcher 把这条记忆写入共享池coder 在执行任务时能检索到它。from ai_memory import MemoryClient client MemoryClient( base_urlhttp://localhost:8000, access_tokenchange_me, ) # researcher 写入一条记忆 client.add( agent_idresearcher, session_idsession_001, content用户偏好使用 Python 3.12Web 框架选用 FastAPI, metadata{project: user-profile, type: preference}, importance0.8, ) # coder 检索时跨 Agent 读取 results client.search( query用户偏好什么 Python 技术栈, agent_idcoder, top_k5, threshold0.35, ) for hit in results: print(hit.content, hit.score, hit.agent_id)输出会包含 researcher 写入的那条记忆同时携带agent_id和score。这一步很关键你可以在拿到记忆后让 coder 在系统提示词里注明“该信息来自需求 Agent 收集的用户偏好”。这里的threshold是相似度阈值低于阈值的记忆不会返回。它是最容易出问题的参数——设高了容易漏设低了容易混入无关信息。我通常先调到 0.3 左右观察一段时间的检索质量再逐步微调。3.3 接入 LangChain / CrewAI / MCP封装成工具ai-memory 本身是独立服务怎么让现有 Agent 框架用上它最通用的思路是把它封装成一个工具ToolAgent 需要读写记忆时会自动调用。在 LangChain 里你可以这样封装一个记忆检索工具from langchain.tools import Tool from ai_memory import MemoryClient client MemoryClient(base_urlhttp://localhost:8000, access_tokenchange_me) def memory_search(query: str) - str: results client.search(queryquery, agent_idcoder, top_k5) return \n.join(f- {r.content} (来源: {r.agent_id}, 相关度: {r.score:.2f}) for r in results) memory_tool Tool( nameteam_memory_search, description检索团队共享记忆获取用户偏好、项目约束和历史决策, funcmemory_search, )在 CrewAI 里把工具传给 Agent 就行from crewai import Agent coder Agent( role代码生成工程师, goal根据用户需求生成高质量代码, tools[memory_tool], )如果你在用 MCP 生态ai-memory 也可以暴露成 MCP Server让支持 MCP 协议的客户端直接调用memory_write和memory_search这两个工具。这样不同语言、不同框架的 Agent 都能通过同一套协议读写记忆真正实现跨 Agent 记忆互通。我个人强烈建议不要把记忆工具设计成“搜索后自动注入”。让 Agent 自主决定什么时候查记忆、查哪些记忆比强行在每个请求里拼历史记忆更灵活也更节省 token。4. 实战调优与数据治理4.1 向量库选型与索引参数ai-memory 底层支持多种向量存储我也在不同项目里对比试过。选型前先把场景想清楚数据量多大、更新频率多高、是否需要事务一致性。存储方案特点适合场景pgvectorPostgres 插件事务一致能和结构化查询联合过滤中小项目量级在百万以内团队已有 Postgres 运维能力Qdrant高性能专用向量库支持丰富过滤条件API 友好数据量大、检索量大需要独立向量服务的场景Milvus分布式能力最强支持超大规模向量亿级以上向量集群化部署FAISS元库性能高但不带服务化能力研究原型、单机离线检索我自己的默认选择是 pgvector。原因很朴素记忆数据不是纯向量它还有agent_id、namespace、importance这些结构化字段。用 pgvector 可以把“向量相似度过滤”和“SQL 条件过滤”放在同一个查询里事务一致性也天然支持。如果你决定用 pgvectorHNSW 索引参数值得调一下m控制每个节点的最大连接数。默认 16数据量大或召回率要求高可以调到 32。代价是内存和构建时间上升。ef_construction构建索引时动态列表大小。常见配置 200 到 400值越大索引质量越高但构建越慢。ef_search查询时动态列表大小。常见配置 100 到 200值越大召回越好但延迟越高。经验值是先用默认参数跑起来等数据量上来后如果召回率不够再逐步增加ef_search。不要一上来就追求全召回延迟会很难看。4.2 记忆过载与遗忘策略记忆层最隐蔽的问题不是“记不住”而是“记得太多”。我把用户三个月的对话全部灌进记忆池之后检索接口返回的相关性肉眼可见地下降。原因很简单同一个用户偏好被不同时间、不同表述反复写入重要信息被淹没在重复和噪音里。ai-memory 提供了几个遗忘机制我的组合使用方式是TTL 自动过期会话级临时记忆设置 24 小时过期。比如“用户今天问过 Redis 性能问题”这种记忆过期就让它消失。重要度阈值淘汰定期清理重要度低于 0.2 且超过 30 天未被访问的记忆。低价值记忆不该长期占据索引。摘要合并每天晚上跑一个定时任务把同一主题的碎片记忆压缩成一条结构化摘要。例如把几十条“用户提到过 Docker”的散装记录合并成“用户有 Docker 使用经验熟悉 Compose但对 K8s 不熟”。版本覆盖当新记忆与旧记忆冲突时旧记忆不是删掉而是标记为 superseded避免直接删数据导致追溯不到历史。你可以通过配置来控制遗忘策略memory_ttl: session_level: 24h knowledge_level: 365d min_importance: 0.2 rollup_cron: 0 2 * * * rollup_threads: 4这个部分我最想提醒的是遗忘策略不是越激进越好。我之前设置过“低重要度 7 天就清”结果用户两周前随口提过的一个设计约束被删了后面 Agent 生成方案时完全不记得这个约束导致返工。宁可多留一些可检索但低优先级的记忆也别把可能有用的事实物理删除。4.3 多租户隔离与安全边界跨 Agent 记忆听起来很美好但一旦牵扯到多个项目、多个用户隔离问题马上变得尖锐。你以为自己在写共享记忆实际上可能是在泄漏数据。我一开始犯过的错只依靠agent_id字段做过滤认为查询时加上where agent_id coder就安全了。但客户端是可以伪造agent_id的。如果 Restful API 允许调用方自己传agent_id那任何拿到 token 的人都能读到别的 Agent 的记忆。要安全地做隔离至少三层租户级 token每个项目或团队分配独立access_tokentoken 本身绑定namespace写入和查询都强制约束在 namespace 内。策略属性绑定入口服务校验 token 后在请求上下文中注入真实的租户身份存储层查询必须带上这个身份而不是信任客户端传入的agent_id。存储层强制过滤在查询构造时把 namespace/tenant_id 作为固定条件而不是可选项。这样即使有人绕过封装直连数据库也无法直接读到其它租户的数据。跨 Agent 共享记忆还有一个边界设计问题哪些记忆可以共享我的原则是领域知识和项目约束默认共享用户个性化数据和隐私记忆默认隔离。比如“本项目采用微服务架构”可以共享“用户手机号是 138xxxx”绝不能给所有 Agent 共享。 ai-memory 里的access_policy字段就是干这个的写入时就要想清楚这条记忆的可见范围。5. 常见问题与排查技巧5.1 检索结果差明明存了却搜不到这是我在使用各种记忆层项目时遇到最多的一个问题ai-memory 也不例外。现象很典型用户问“我们之前商定的技术选型是什么”结果什么都查不到但明明记忆库里就有这条记录。排查顺序很重要看 embedding 模型是否一致。写入和查询用的模型不一致或者模型版本升级后向量维度没对齐检索大概率失效。看中文分词和命名实体。向量模型对低频人名、产品名的语义捕捉往往不敏感。你搜“小王的技术偏好”如果记忆里写的是“用户王某某”相似度可能很低。降阈值测试。把 threshold 调到 0.1 看看能不能搜出结果。如果能搜出来说明是阈值太高而不是没有写入。混合检索权重。如果精确实体搜索频繁失败把 keyword 权重拉高或者对命名实体额外建一个反向索引。我碰到过一个典型案例用户叫“林晚”记忆里写了“林晚说项目要在一个月内上线”我用“用户的时间要求”做语义查询结果排在第一的是别的内容。后来我把“林晚”和“一个月内上线”单独抽出来加了 keyword 标签检索准确率才上来。记忆层的真实场景里实体名往往比语义更重要。5.2 记忆重复同一事实写了几百条重复记忆不是数据问题是写入链路问题。常见原因有两个写入前没做相似度预检。每次对话都当成新记忆写入没有去判断“这条记忆是不是已经存在”。对话内容本身重复。用户多次表达同一个偏好提取出来的记忆语义一致但没有被合并。解决方式在add之前先做一次search用阈值判断是否已有类似记忆。如果有就更新原记忆的内容和时间戳而不是新增。我的做法是在业务代码里做一个upsert_memory函数def upsert_memory(client, content, agent_id, metadata, importance0.5): existing client.search(querycontent, agent_idagent_id, top_k1, threshold0.92) if existing: client.update( memory_idexisting[0].memory_id, contentcontent, metadatametadata, importanceimportance, ) else: client.add( contentcontent, agent_idagent_id, metadatametadata, importanceimportance, )阈值 0.92 比较高只有语义几乎完全一致的才被认为是重复。你可以按业务场景调低一点但要小心误删多条含义相近但细节不同的记忆。5.3 跨 Agent 共享后记忆串味记忆串味指的是A Agent 写入的隐私信息被 B Agent 在另一个会话里查到了然后 B Agent 还把它当背景知识写进了输出。这是多 Agent 系统里最危险的问题尤其在客服、医疗、金融场景。排查时先检查三件事写入时是否设置了 access_policy。如果没有记忆默认是全 Agent 可读的串味是必然的。查询身份是否真实传递。如果你在 SDK 里手动传 agent_id而没有经过 token 校验那任何 Agent 都能绕过策略。代码里是否有硬编码的检索条件。比如某个 Tool 里写死了agent_idcoder但实际运行的是另一个 Agent也会导致串味。我的建议是不要在检索工具里让 Agent 自由传 agent_id而是由服务端根据当前会话身份自动填充。只在明确需要共享的场景使用access_policypublic的专门入口。5.4 写入延迟高一个请求卡住整个流程如果你在对话主链路里同步调用client.add而且 embedding 模型在 CPU 上跑写入一条记忆可能要几百毫秒甚至一秒钟。用户那边就会明显感觉到“发送消息后卡了一下”。这个问题最直接的解法是异步化。把写入动作丢进 Redis 队列立即返回由后台 worker 去消费队列、完成向量化和写入。这样用户无感知记忆也能最终到达。from redis import Redis import json redis_client Redis.from_url(redis://localhost:6379/0) def add_memory_async(client, memory_data): redis_client.lpush(memory_write_queue, json.dumps(memory_data))如果写入仍然慢检查一次 embedding 请求是否串行。模型推理是批量型任务单条推理永远比批量推理慢。把多条记忆攒在一起一次性调用 embedding 模型批量生成向量吞吐能提升不少。最后说点实在的如果你只是单 Agent 原型没必要上 ai-memory自己用 SQLite 加个 embedding 列就够。但一旦你开始做多 Agent、多会话、多租户的系统记忆就不再是“存和搜”的问题而是“怎么让记忆在正确的时间、以正确的可见范围、出现在正确的 Agent 面前”。我实际用下来ai-memory 帮我省掉最多的不是写代码的时间而是调试记忆串味和检索质量的时间。它把记忆当成了基础设施而不是模型的一个插件。还有一个小建议新项目接入 ai-memory 后先别急着在 Agent 里塞一堆记忆跑两周正常业务再把记忆暴露给 Agent 自主检索。这样你能积累一份干净的记忆操作日志后续调阈值、调权重、调隔离策略都有据可查。记忆层这东西一开始做得越克制后面越省心。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →