我这半年和 Agent 打交道最多的不是 Prompt,也不是工具调用,而是记忆。做了一个 7×24 小时的售后客服 Agent,用户三天前刚报过退货单号,三天后再问,它一脸茫然。后来在 GitHub 上刷到 ai-memory 这个 7.9K Stars 的开源项目,标题很直白:跨 Agent 记忆层。它把 Agent 的对话历史从上下文窗口里搬出来,放到一个独立的记忆服务中,谁需要谁去读,不同 Agent 之间还能共享。这篇就聊聊我调研、部署和改造它的全过程,也把这些年在 Agent 记忆上踩过的坑一并交代清楚。
1. 先搞懂 ai-memory 到底在解决什么问题
1.1 Agent 的“金鱼记忆”:上下文窗口并不等于记忆
LLM 本身是无状态的。你用同一个模型账号连续发十句话,模型不会记得第一句话;看起来有记忆,是因为客户端把聊天记录每一轮都塞进 API 请求里。Agent 也一样——市面上大多数 Agent 框架所谓的“记忆”,就是把历史消息原封不动地拼到 prompt 里。
问题很快就暴露了。
第一是成本:十万字的对话历史,每次请求都要按 token 重新计费。第二是窗口:上下文一长,超过模型 context length 就必须截断,一截断就把关键信息丢了。第三是跨会话:聊天窗口一关,历史全没了。第四是跨 Agent:A 机器人聊过的内容,B 机器人完全不知道,同一个用户要在两个机器人面前分别自我介绍一遍。
所以 ai-memory 这类项目的核心主张是:记忆不应该长在会话上下文里,而应该长在一个 Agent 之外的“层”上——也就是记忆服务。它保存的不是聊天记录缓存,而是结构化、可检索、可共享的记忆单元。
1.2 记忆层方案对比:RAG、MemGPT 与独立记忆服务
现在 Agent 圈做记忆的方案大致分三个流派,各有利弊,先理清楚才不会被绕晕。
RAG 是把知识文档切片、向量化,再让 Agent 检索。它解决的是“让 Agent 知道训练数据以外的事实”,更像知识库,而非用户级或者行为级的记忆。用来做“产品手册问答”很合适,但用来回答“用户上次说了什么”就很别扭。
MemGPT 思路(后来的 Letta)是把上下文窗口当成操作系统内存,把历史消息压缩、摘要、换页,再塞回 prompt。这个方案在单 Agent 场景非常好用,但记忆本质长在 Agent 内部,换个 Agent 就搬不走,更谈不上跨 Agent 共享。
ai-memory 走的是第三个方向:把记忆做成一个独立服务,Agent 只是客户端。所有写入、读取、共享、过期都通过 API 完成。好处是跨 Agent——你的客服 Agent、营销 Agent、数据分析 Agent 可以读同一份用户记忆;坏处是你要多维护一个服务,多一套读写逻辑。但如果你的项目里已经有多个 Agent 在跑,这笔账怎么算都划算。
1.3 项目概览:7.9K Stars 的 ai-memory 是什么
ai-memory 在 GitHub 上作者写的是 aaronzzx/ai-memory,一个轻量级外部记忆层项目,当前 Stars 大约 7.9K。我印象中它主要基于 Python 和 Redis 实现,Redis Stack 负责 JSON 存储和向量检索,对外暴露 REST API。项目的核心定位非常清晰:
| 维度 | 说明 |
|---|---|
| 核心定位 | 跨 Agent 记忆层 / 外部记忆服务 |
| 主要依赖 | Python + Redis Stack(向量检索) |
| 交互方式 | REST API,语言无关 |
| 核心能力 | 记忆写入、语义召回、元数据过滤、命名空间隔离 |
| 适用范围 | 多 Agent 共享记忆、会话记忆持久化、Agent 个性化 |
这个定位很讨巧:不碰模型、不碰编排,只做 Agent 的记忆这一件小事。它 7.9K stars 说明市场上对这个切口的认可是真实的——大家受够了“每换一个 Agent 就重新失忆一次”。而且因为它通过 HTTP API 提供服务,你用 LangChain、AutoGen、CrewAI 还是自研框架,都不影响接入。
2. 核心概念拆解:跨 Agent 记忆层是怎么设计的
2.1 记忆的原子化:把对话拆成一粒一粒可检索的记忆
先想一个问题:如果直接把一整天的聊天记录原样存下来,你希望检索时返回一个长段落,还是拆成“用户上次退货单号是 X”“用户偏好圆通快递”这样的小条目?
肯定是后者。这就是记忆原子化的核心思路。
ai-memory 给我的最大启发,是示范了一种通用的“记忆单元”结构。一个明确的模型化记忆单元通常包含以下字段:
| 字段 | 作用 | 说明 |
|---|---|---|
| id | 唯一标识 | 用于删除、更新、引用 |
| user_id / agent_id | 归属与隔离 | 跨 Agent 共享靠这一层控制 |
| content | 记忆正文 | 建议写入前先做提炼 |
| tags | 标签 | 精确过滤,大幅降噪 |
| importance | 重要性 | 0~1 分数,检索时加权 |
| created_at / updated_at | 时间 | 排序与统计 |
| expire_at | 过期时间 | TTL,主动遗忘 |
这样设计的好处是:记忆可以按用户/Agent 隔离,可以按标签精确过滤,可以按重要性排序,可以自动过期。它不是一坨日志文件,而是一个结构化记忆库,天然适合多 Agent 场景。
2.2 语义检索:怎么从记忆库里捞出“相关”的过去
记录只是第一步,关键在读取。
你想让 Agent 回答“上次那个退货单号是多少”,如果只做关键词匹配,用户换个说法,比如“之前退了的东西单号是什么”,关键词对不上,记忆就报废了。所以检索必须走向量语义匹配。
具体流程并不复杂:
- 对查询文本用 embedding 模型转成向量;
- 在 Redis 的向量索引里做近邻搜索,找到语义最接近的记忆向量;
- 在相似度分数基础上,叠加 importance 加权、时间衰减、标签过滤;
- 返回 Top-K 记忆,格式化成 prompt 片段塞进上下文。
这里有个容易忽略的细节:重要性分数不是写入后就不变了。如果一条记忆长期没被访问,它的权重应该随时间衰减;如果一条记忆反复被检索到,说明它很活跃,权重可以适当上调。这种“热度”机制让记忆库长时间运行后仍然给出高相关召回,而不是永远偏向最早写入的旧记忆。
2.3 读写路径:记忆存取的完整生命周期
拆开看一次完整的记忆使用过程,能帮你更好理解“记忆层”这套架构。
写路径:Agent 完成一轮对话后,决定哪些内容值得沉淀。这一步通常在 Agent 端做,但 ai-memory 这类项目在写入前会让 LLM 自己提炼内容,比如“请从刚才对话中提取用户偏好、订单信息、情绪状态,输出为结构化记忆”。提炼完调写入接口,服务端负责 embedding、辅助打标签、存 Redis、建索引。
读路径:新会话开始,Agent 框架先调用查询接口,传入当前用户 ID 和当前对话主题(或者用户最近一句话),服务端做向量检索,返回与该用户最相关的若干条历史记忆。Agent 把这些记忆拼进 system prompt 或 context。这个动作应该在每次 Agent 开口前都发生一次。
删除和更新路径:用户明确要求“忘掉我说过的话”,或者记忆过了有效期,必须支持按 ID 删除、更新 content 后重新 embedding。这块不是可选项,是做 Agent 记忆必须守住的底线。
理解这三条路径,你就明白为什么这类项目会叫“记忆层”而不是“聊天记录数据库”了:它要保证 Agent 每次开口之前,先把该想起来的事想起来。
3. 实操:20 分钟跑通一个跨 Agent 记忆服务
这部分我的建议是一切从简:先把服务跑起来,用 curl 写一条、查一条,再接入你正在用的 Agent 框架。不要一上来就复刻完整架构。
3.1 环境准备与分钟级启动
ai-memory 的部署很轻,只需要三样东西:
- Docker(跑 Redis Stack,自带向量检索能力)
- Python 3.10 以上
- 一个 embedding 服务(本地 sentence-transformers 或云端 API)
本地最稳的跑法:
docker run -d --name redis-memory -p 6379:6379 redis/redis-stack-server:latest git clone https://github.com/aaronzzx/ai-memory.git cd ai-memory pip install -r requirements.txt python run.py服务默认会起在 8000 端口,启动日志里会打印当前 embedding 模型和 Redis 连接状态。第一次启动如果用的是本地模型,会花一点时间下载模型文件,这是正常现象,不是卡住了。
这里插一句:本地模型的好处是零成本、无隐私外泄,缺点是中文语义效果可能一般。如果做中文 Agent,建议换一个中文语料训练的 embedding 模型再跑,效果差距非常大,后面第 4 节我会细说。
3.2 用 REST API 完成一次记忆写入与召回
跑起来之后先别急着接 Agent,手工验证两条链路。
写入记忆:
curl -X POST http://localhost:8000/write \ -H "Content-Type: application/json" \ -d '{ "user_id": "user_123", "agent_id": "after_sales", "content": "用户王先生反馈充电口接触不良,已生成工单 TS20240315,约定周三回访", "tags": ["维修", "工单"], "importance": 0.8 }'查询记忆:
curl -X GET "http://localhost:8000/query?user_id=user_123&query=上次充电口的问题处理到哪一步了&top_k=5"理想返回里应该带上“约定周三回访”“工单 TS20240315”这类信息。能召回,说明 embedding、向量索引、过滤逻辑都通了。召回不到,先确认模型是不是英文语料训练的,换中文模型再试,大概率是语义问题,不是部署问题。另外不同版本 API 路径可能有出入,以你拉下来的 README 为准。
3.3 接入 LangChain:让两个 Agent 共享一套记忆
接入框架的核心思想:把你原来的“聊天历史列表”换成“记忆服务查询结果”。以 LangChain 为例,最简单的方式是写一个 memory 工具,但我不建议硬套 LangChain 的 Memory 类——那个类本质还是在本地管历史消息。
更推荐的做法是:直接在 Agent 的 system prompt 里注入查询结果。
伪代码:
def build_context(user_id, current_query): memories = memory_api.query(user_id, current_query, top_k=8) return "\n".join([f"- {m['content']}" for m in memories]) def run_agent(user_id, current_query): context = build_context(user_id, current_query) prompt = f"""你是售后客服 Agent。以下是该用户的已知历史信息: {context} 现在用户说:{current_query} 请结合历史信息,给出不超过 200 字的回复。""" return call_llm(prompt)跨 Agent 怎么体现?再起一个营销 Agent,它也调同一个查询接口,且传同一个 user_id,它就能读到“用户王先生最近有维修工单”,于是推荐话术自动避开“再买一个充电器”这种不合时宜的营销。多 Agent 共享记忆,本质不是让一个 Agent 学会另一个 Agent 的技能,而是让它们共用同一个记忆服务。
4. 我踩过的坑与排查方法
4.1 记忆“串线”:跨用户隔离与跨 Agent 共享的边界
第一次上线时我犯过一个低级错误:查询时只传了 query,没传 user_id,结果 A 用户问“我的订单”,Agent 把 B 用户的历史记忆也召回了。这种串线比“记不住”还致命,因为用户会直接质疑你的系统泄露了别人的信息。
排查方法很固定:先看查询请求有没有把 user_id / agent_id 作为必填过滤条件;再看写入时有没有传完整的命名空间。我的建议是“共享尽量刻意、隔离尽量默认”——默认情况下每个 agent_id 加 user_id 的组合都是独立记忆域,只有明确要做跨 Agent 共享时,才用一个共享的 agent_id 或标记。宁愿多写一次隔离逻辑,也不要图省事把所有记忆放在一个池子里。
4.2 检索召回一堆噪声:embedding 模型和加权策略
项目默认的 embedding 模型经常是英文语料训练的,中文场景召回质量会明显下降。症状是:查询“退货流程”,召回结果是“用户说谢谢”这种毫无关系的内容。
解法有三步,按优先级来:
- 换中文 embedding 模型。这类项目一般支持配置模型名称,换成 BGE 系列(比如 BAAI/bge-small-zh-v1.5)效果会好很多;
- 提高 importance 和标签过滤的权重,避免纯向量相似度一锤定音;
- 写入前做记忆提炼,不要把每一句口水话都存进去,只存有信息增量的短句,比如“用户已发起退货,快递单号 SF123456”。
如果你发现召回的“相关”永远是表面相关,那八成不是检索参数问题,而是模型语言风格不匹配。把模型和文本都切到同一个语言赛道,问题通常立刻消失。
4.3 记忆膨胀:库越来越大、召回越来越差
记忆永久不删,最后一定会拖垮检索精度和速度。重点在于“遗忘”机制要落地,而不是嘴上说说。
我线上跑了一段时间后总结了三招:
- 为临时性记忆设置 TTL。比如“用户今天心情不好”这种两天后就过期,留着只会污染检索结果;
- 定期合并同类记忆。同一主题的多条短期记忆,由 LLM 汇总成一条长期记忆,然后删掉原条目。比如五条售后沟通记录合并成一条“该用户充电口问题已解决,偏好电话回访”;
- 冷热分离。长期未被访问的记忆标记为冷数据,查询时默认不召回;只保留活跃记忆进入默认召回候选池。
这些策略不一定需要 ai-memory 原生支持,你可以自己写一个定时任务,调用它的删除和更新接口来做。开源项目提供的基础设施只是存储,“聪明地遗忘”是你自己要做的事。
5. 什么时候用 ai-memory,什么时候自己写存储
5.1 我对这个项目的整体评价
ai-memory 的定位很讨巧:不碰模型、不碰编排,只做 Agent 的记忆这一件小事。它 7.9K stars 说明市场上对这个切口的认可度很高——大家受够了“每换一个 Agent 就重新失忆一次”。
优点是轻量、语言无关、接入成本低。缺点也明显:毕竟是一个轻量项目,面对超大规模、超高并发、复杂记忆关系场景,Redis 单机的容量和检索精度会成为瓶颈。它更像一个极好的起点和教学级参考,而不是终极的企业级记忆中台。但它的架构设计——记忆单元、向量检索、重要性衰减、命名空间隔离——这套模型本身就值得抄。
5.2 选型建议:哪种项目适合用它
我的判断标准很简单,按规模来:
- 1 到 5 个 Agent、每天千级以内的记忆写入量:直接用 ai-memory,或者干脆抄它的架构自己改;
- 做真正企业级记忆中台、复杂记忆图谱、多租户隔离、分布式部署:不一定用它,但它总结出的“记忆单元 + 向量检索 + 重要性衰减 + 命名空间隔离”这套模型,仍然值得直接照搬进你自己的架构设计。
另外补充一点:如果团队一点 Python 都不想碰,也可以通过 HTTP API 调用 ai-memory,后端用什么语言其实无所谓。它把记忆服务边界划得很清楚。
5.3 一点个人体会
说完技术再聊一句感受。
Agent 开发聊到记忆,很多人第一反应是“加大上下文窗口”“换更大的模型”,但 ai-memory 这类项目提醒了我一个常识:记忆不该是模型的属性,而应该是 Agent 架构的组件。
我后来把项目里的记忆部分彻底外包出去了,客服 Agent 和营销 Agent 共用一个记忆服务,效果比之前任何“把历史全塞进 prompt”的方案都稳定。而且线上跑了一段时间后我发现:真正难的从来不是“记得住”,而是“该记什么、该忘什么”。把这两点想清楚,任何记忆层项目都能发挥出价值。