首页 / 资讯中心 / 文章详情

Agent Memory架构设计与实战:分层存储、检索策略与MCP集成

Agent Memory架构设计与实战:分层存储、检索策略与MCP集成 ★ FEATURED ARTICLE
1. 从“hindsight”说起为什么我们需要给Agent装上“后视镜”第一次看到“hindsight”这个词我脑子里蹦出来的不是技术概念而是开车时看后视镜的那个动作。后视镜这东西很有意思它不帮你踩油门也不帮你打方向但没有它你变道、超车、倒车心里都没底。Agent Memory这件事本质上就是在给LLM驱动的智能体装后视镜——让它在做决策的时候能看到自己之前做过什么、什么做对了、什么做砸了。我接触Agent Memory这个概念是从做自动化工作流开始的。当时用LLM搭了一个简单的任务执行Agent跑单次任务没问题但只要涉及多轮交互它就“失忆”了。上一轮告诉它“用户偏好用表格输出”下一轮它又给你整段文字。你纠正它一次它记住了换个会话又忘了。这种体验就像跟一个每天失忆的人合作每次都要从头交代背景效率极低。后来我开始研究Agent的存储机制发现问题的核心在于LLM本身是无状态的。每次调用都是一次独立的推理它不记得上一次说了什么、做了什么。所谓的“记忆”其实是外部系统通过上下文注入的方式“喂”给它的。这就引出了一个关键问题——怎么喂、喂什么、喂多少。“hindsight”这个项目标题我理解它要解决的就是这个“喂”的问题。它不是简单地做对话历史存储而是要让Agent具备一种“回顾性推理”的能力。换句话说不只是记住发生了什么还要能从发生过的事情里提取出对当前决策有用的信息。这跟RAG有本质区别RAG是从外部知识库检索静态知识而Agent Memory是从自身的交互历史中检索动态经验。这个方向为什么现在特别热因为Agent正在从“单次任务执行”走向“持续自主运行”。你让一个Agent帮你管理日程、处理邮件、监控数据它必须记住上周你拒绝了一个会议邀请、上个月你把某个发件人拉黑了、昨天你调整了某个指标的阈值。这些信息不是静态知识而是动态的、跟时间强相关的经验。没有这些Agent就是个每次都要重新培训的新员工。我踩过的第一个坑就是以为把对话历史全塞进上下文就行了。结果token消耗爆炸不说模型还会被无关的历史信息干扰做出莫名其妙的决策。后来才明白Agent Memory的核心不是“存”而是“取”——在正确的时机取出正确的记忆以正确的方式注入上下文。这跟数据库索引的设计思路很像存什么不重要能快速找到什么才重要。这篇文章我会从架构设计、存储选型、检索策略、MCP集成、Docker部署几个维度把Agent Memory这件事拆开讲透。不管你是刚接触LLM应用开发的新手还是已经在做Agent系统的老手应该都能从中找到可以直接抄作业的方案和踩坑经验。2. Agent Memory的架构设计分层存储与检索策略2.1 为什么不能把所有记忆都塞进上下文先算一笔账。假设你的Agent每天处理100轮对话每轮平均200个token一个月就是60万token。如果全部保留在上下文里按当前主流模型的上下文窗口你连一周的历史都放不下。就算放得下每次推理都要处理这么多token成本和延迟都不可接受。更关键的是信息密度问题。60万token里真正对当前决策有用的可能就几百个token。剩下的全是噪音。LLM在处理长上下文时注意力机制会被稀释关键信息反而容易被忽略。这就像你在一个嘈杂的菜市场里找人不如在一个安静的房间里对话来得高效。所以Agent Memory的第一个设计原则就是分层存储按需检索。我通常会把记忆分成三层工作记忆Working Memory当前会话的上下文保留最近N轮对话直接注入prompt。N的大小取决于任务复杂度和模型上下文窗口一般5到10轮比较合适。短期记忆Short-term Memory最近几天或几周的交互摘要按时间窗口检索。比如“上周用户提到了三个待办事项”这种信息不需要逐字保留但需要能快速召回。长期记忆Long-term Memory经过提炼和结构化的经验知识比如用户偏好、常见问题解决方案、决策模式等。这部分通常以向量化形式存储通过语义检索召回。这三层不是孤立的而是有数据流动的。工作记忆里的内容会定期摘要后写入短期记忆短期记忆里的模式会被提炼后写入长期记忆。这个流动过程我称之为“记忆固化”类比人脑从短期记忆到长期记忆的转化。2.2 存储选型向量数据库还是关系型数据库这是被问得最多的问题。我的答案是都要。向量数据库负责语义检索关系型数据库负责结构化查询和时间范围过滤。两者配合使用才能覆盖Agent Memory的全部需求。向量数据库我试过Pinecone、Weaviate、Qdrant和Chroma。如果是本地开发和小规模部署Qdrant和Chroma都不错轻量、Docker部署方便。如果是生产环境Pinecone的托管服务省心但成本要考虑。Weaviate的功能最全但学习曲线也最陡。关系型数据库我用PostgreSQL居多主要是因为它对JSON字段的支持好而且pgvector扩展可以直接做向量检索省得维护两套系统。对于中小规模的Agent应用PostgreSQL pgvector基本够用了。这里有个关键设计决策记忆的元数据怎么存。我通常会在关系型数据库里建一张memories表字段包括CREATE TABLE memories ( id UUID PRIMARY KEY, agent_id VARCHAR(64) NOT NULL, session_id VARCHAR(64), memory_type VARCHAR(32) NOT NULL, -- working, short_term, long_term content TEXT NOT NULL, embedding VECTOR(1536), metadata JSONB, created_at TIMESTAMP DEFAULT NOW(), accessed_at TIMESTAMP, access_count INTEGER DEFAULT 0, importance_score FLOAT DEFAULT 0.5 );这个设计里importance_score和access_count是两个容易被忽略但极其重要的字段。它们决定了记忆的“遗忘曲线”——长期不被访问且重要性低的记忆应该被降权甚至删除。这跟人脑的记忆机制类似不常用的信息会逐渐模糊。2.3 检索策略语义相似度不是唯一标准很多人做Agent Memory检索时只用向量相似度。这在实际使用中会出问题。我遇到过这样的情况用户问“上次那个报表怎么导出的”向量检索返回了一堆关于报表格式的讨论但真正有用的那条“导出按钮在右上角先选日期范围再点导出”因为表述差异相似度反而不高。所以检索策略需要多路召回。我的做法是语义检索用embedding做向量相似度搜索召回Top-K。关键词检索用BM25或全文索引做关键词匹配召回包含特定术语的记忆。时间衰减加权越近期的记忆权重越高但长期记忆中的高重要性记忆不受此限制。访问频率加权被频繁访问的记忆权重提升形成“常用记忆优先”的效应。最后用一个加权公式合并排序final_score 0.5 * semantic_similarity 0.2 * keyword_match 0.2 * time_decay 0.1 * access_frequency这个权重不是固定的需要根据你的具体场景调。比如客服Agent可能更看重时间衰减而知识型Agent可能更看重语义相似度。注意检索回来的记忆不要全部注入上下文。我一般限制在3到5条每条不超过200个token。注入前还要做一次去重和冲突检测避免相互矛盾的记忆同时出现。3. 核心细节解析记忆的写入、提炼与遗忘3.1 什么内容值得被记住这是Agent Memory设计中最难的部分。不是所有对话都值得存。我见过一些实现把每一轮对话都原封不动地存进去结果检索出来的全是“好的”“明白了”“收到”这种废话。我的经验是值得记住的内容通常具备以下特征之一包含用户偏好或约束比如“我不喜欢在周五安排会议”“报表要用PDF格式”。包含决策依据比如“因为预算限制选择了方案B”。包含错误和纠正比如“上次用这个API报错了需要加超时参数”。包含任务状态变更比如“项目X已从进行中改为已完成”。这些内容的共同点是它们会影响未来的决策。如果一条信息不影响未来任何决策那它就不值得被记住。实现上我会在每轮对话结束后用一个轻量级的LLM调用做“记忆提取”。Prompt大概是这样分析以下对话提取出值得长期记住的信息。只提取会影响未来决策的内容忽略寒暄、确认和无关信息。输出JSON格式包含content和importance_score两个字段。 对话内容 {conversation}这个提取步骤会增加一次LLM调用但非常值得。它把原始对话压缩成了高密度的记忆条目后续检索效率会高很多。3.2 记忆提炼从原始对话到结构化知识提取出来的记忆还是文本形式进一步提炼成结构化知识检索和利用效率会更高。我通常会把记忆分成几种类型记忆类型示例存储方式用户偏好用户喜欢简洁的回复键值对事实知识项目X的截止日期是6月30日三元组操作经验导出报表需要先选日期范围文本向量决策记录选择方案B因为预算限制文本元数据对于用户偏好和事实知识我会用结构化方式存储检索时直接精确匹配不走向量。对于操作经验和决策记录用向量存储语义检索。这里有个技巧记忆的content字段不要只存原始文本要加上上下文标签。比如不存“导出按钮在右上角”而是存“报表导出操作导出按钮在右上角需先选日期范围”。这样检索时命中率更高。3.3 遗忘机制主动删除比被动堆积更重要Agent Memory最容易犯的错误就是只增不删。跑几个月后数据库里堆了几万条记忆检索质量急剧下降。遗忘机制不是可选项是必选项。我的遗忘策略分三种时间衰减超过一定时间未被访问的记忆重要性分数按指数衰减。衰减公式new_score old_score * exp(-λ * days_since_access)λ一般取0.01到0.05。容量限制每个Agent的长期记忆条目数设上限比如1000条。超过后按重要性分数淘汰最低的。冲突消解当新记忆与旧记忆冲突时保留新的旧记忆标记为“已废弃”而非直接删除以便追溯。实操心得遗忘机制一定要有日志。我遇到过用户投诉“Agent忘了之前说过的偏好”排查发现是遗忘策略把那条记忆误删了。有了日志才能快速定位和恢复。4. 实操过程从零搭建一个带Memory的Agent4.1 环境准备与Docker部署我习惯用Docker Compose来管理Agent Memory的依赖服务。下面是我常用的docker-compose.yml配置version: 3.8 services: postgres: image: pgvector/pgvector:pg16 environment: POSTGRES_DB: agent_memory POSTGRES_USER: agent POSTGRES_PASSWORD: agent_pass ports: - 5432:5432 volumes: - pgdata:/var/lib/postgresql/data qdrant: image: qdrant/qdrant:latest ports: - 6333:6333 - 6334:6334 volumes: - qdrant_data:/qdrant/storage redis: image: redis:7-alpine ports: - 6379:6379 volumes: pgdata: qdrant_data:PostgreSQL存结构化记忆和元数据Qdrant存向量Redis做工作记忆的缓存。这三个服务通过Docker网络互通Agent应用通过环境变量连接。启动命令docker compose up -d验证服务状态docker compose ps如果Docker Desktop启动时报“virtualization support not detected”需要在BIOS里开启虚拟化支持。Windows上还要确认WSL2已安装并启用。这些是Docker安装的常见坑跟Agent Memory本身无关但不解决就跑不起来。4.2 记忆写入的完整流程记忆写入分同步和异步两条路径。同步路径处理工作记忆直接写Redis延迟低。异步路径处理短期和长期记忆走消息队列不阻塞主流程。我用的消息队列是Redis的Stream轻量够用。写入流程如下对话结束后Agent将完整对话推入Redis Stream。一个独立的Worker消费Stream调用LLM做记忆提取。提取出的记忆条目写入PostgreSQL同时生成embedding写入Qdrant。更新Redis中的工作记忆缓存保留最近N轮。Worker的核心代码逻辑import json import redis from openai import OpenAI r redis.Redis(hostlocalhost, port6379, decode_responsesTrue) client OpenAI() def extract_memories(conversation): prompt f分析以下对话提取值得长期记住的信息。 只提取会影响未来决策的内容。输出JSON数组。 对话{conversation} response client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], response_format{type: json_object} ) return json.loads(response.choices[0].message.content) def process_stream(): while True: messages r.xreadgroup( groupnamememory_workers, consumernameworker_1, streams{conversation_stream: }, count1, block5000 ) for stream, msgs in messages: for msg_id, data in msgs: conversation data[conversation] memories extract_memories(conversation) for mem in memories: save_memory(mem) r.xack(conversation_stream, memory_workers, msg_id)这个流程的关键点是异步。记忆提取要调用LLM有延迟不能阻塞用户的对话响应。用Stream做缓冲Worker慢慢消费用户体验不受影响。4.3 记忆检索的注入时机检索发生在每次LLM调用之前。但不是每次调用都需要检索。我的策略是用户发新消息时触发检索。Agent执行工具调用前触发检索。定时任务中按需检索。检索结果注入prompt的方式也有讲究。我通常放在system prompt之后、用户消息之前用明确的标记包裹[相关记忆] - 用户偏好喜欢简洁回复不要客套话 - 操作经验导出报表需先选日期范围 - 事实知识项目X截止日期6月30日 [记忆结束] 用户消息帮我导出项目X的报表这样模型能清楚区分哪些是记忆、哪些是当前输入。实测下来这种标记方式比直接拼接效果好很多。4.4 MCP集成让Agent Memory成为可复用服务MCP协议的出现让Agent Memory的集成方式发生了质变。以前每个Agent都要自己实现一套记忆读写逻辑现在可以把Memory做成一个MCP Server任何支持MCP的Agent都能直接调用。我实现了一个简单的Memory MCP Server暴露三个工具memory_write写入一条记忆memory_search检索相关记忆memory_forget删除或降权一条记忆MCP Server的核心是用标准输入输出做JSON-RPC通信。下面是一个简化版的实现import json import sys def handle_request(request): method request.get(method) params request.get(params, {}) if method tools/list: return { tools: [ { name: memory_search, description: 检索相关记忆, inputSchema: { type: object, properties: { query: {type: string}, limit: {type: integer, default: 5} } } } ] } elif method tools/call: tool_name params.get(name) if tool_name memory_search: results search_memories(params[arguments][query]) return {content: [{type: text, text: json.dumps(results)}]} def main(): for line in sys.stdin: request json.loads(line) response handle_request(request) print(json.dumps(response), flushTrue) if __name__ __main__: main()这个Server可以通过Docker部署Agent通过MCP协议连接。好处是记忆服务独立于Agent可以单独升级、扩容、监控。注意MCP Server的token管理要小心。如果暴露在公网必须加认证。我一般用环境变量注入tokenServer启动时校验。5. 常见问题与排查技巧实录5.1 记忆检索不准确怎么办这是最高频的问题。表现是明明存了相关记忆但检索时就是不出来。排查思路分三步第一步检查embedding质量。用同一个query分别检索看返回的相似度分数。如果所有分数都低于0.7说明embedding模型不适合你的领域。换一个多语言模型或者领域微调过的模型试试。第二步检查记忆的content格式。如果content太短或太泛embedding质量会差。比如“用户偏好表格”这种不如“用户偏好输出报表时使用表格格式不要用纯文本”来得具体。第三步检查检索策略。纯向量检索对精确术语不敏感。加上关键词检索做多路召回效果会明显提升。我整理了一个速查表现象可能原因解决方法检索结果不相关embedding模型不匹配换模型或微调相关记忆排在后权重配置不合理调整语义/关键词权重检索不到新记忆写入延迟检查Worker消费速度记忆内容矛盾冲突消解未生效加冲突检测逻辑检索结果重复去重未做加content hash去重5.2 记忆写入延迟高怎么优化记忆提取要调LLM延迟通常在1到3秒。如果对话频繁Worker会积压。优化手段用更小的模型做提取比如gpt-4o-mini或本地部署的小模型。批量处理一次提取多条对话的记忆。对简单对话跳过提取比如只有“好的”“谢谢”这种。我实测下来用gpt-4o-mini做提取单条对话延迟在800毫秒左右。如果并发高可以起多个Worker并行消费。5.3 Docker环境下的网络问题Agent Memory涉及多个服务Docker网络配置容易出问题。常见的是容器间无法互相访问。排查步骤确认所有服务在同一个Docker网络中。用服务名而非localhost做主机名。检查端口映射是否正确。# 进入容器测试连通性 docker exec -it agent_app ping postgres docker exec -it agent_app curl http://qdrant:6333/health如果Docker Desktop在Windows上启动失败报“virtualization support not detected”需要进BIOS开启VT-x或AMD-V。这个坑我踩过两次第一次以为是Docker问题重装了好几遍最后才发现是BIOS设置。5.4 记忆膨胀导致性能下降跑了一段时间后数据库越来越大检索变慢。除了前面说的遗忘机制还可以做分区。按时间分区比如每月一个分区查询时只扫最近几个分区。PostgreSQL的分区表创建CREATE TABLE memories ( id UUID, agent_id VARCHAR(64), content TEXT, created_at TIMESTAMP ) PARTITION BY RANGE (created_at); CREATE TABLE memories_2025_01 PARTITION OF memories FOR VALUES FROM (2025-01-01) TO (2025-02-01);配合定时任务自动创建下个月的分区删除过期的分区。这样检索性能不会随数据量增长而线性下降。5.5 多Agent共享记忆的隔离问题如果你有多个Agent记忆需要隔离。我的做法是在memories表里加agent_id字段所有查询都带agent_id过滤。但有些记忆是跨Agent共享的比如用户偏好。这种我会单独建一张shared_memories表或者用agent_id*标记。隔离级别分三种完全隔离每个Agent独立记忆库互不可见。部分共享公共记忆共享私有记忆隔离。完全共享所有Agent共用一个记忆库。选择哪种取决于你的业务场景。客服Agent和销售Agent可能需要共享用户偏好但各自的对话历史应该隔离。6. 记忆系统的评估与持续优化6.1 怎么判断记忆系统好不好用没有评估指标的记忆系统就是盲人摸象。我通常用三个指标召回率相关记忆被检索出来的比例。构造一批测试query和对应的期望记忆跑一遍看命中多少。精确率检索结果中真正相关的比例。召回率高但精确率低说明噪音太多。响应延迟从query到返回结果的时间。超过500毫秒用户就能感知到卡顿。这三个指标要平衡。召回率优先因为漏掉关键记忆的代价比多返回几条噪音大。但精确率也不能太低否则模型会被噪音干扰。6.2 持续优化的几个方向记忆系统不是一次搭好就完事的。我通常会做这几件事定期review检索日志看哪些query检索效果差针对性优化。A/B测试检索策略不同权重配置跑对比用数据说话。用户反馈闭环让用户标记“这个记忆有用/没用”作为强化信号。我自己的经验是记忆系统上线第一个月检索准确率大概在60%左右。经过两三轮调优能到85%以上。剩下的15%主要是边界情况投入产出比不高可以接受。6.3 一个容易被忽略的点记忆的可解释性当Agent做出一个决策时用户可能会问“你为什么这么做”。如果决策依据来自记忆你需要能追溯是哪条记忆影响了决策。所以每次检索注入时要记录检索到的记忆ID和注入位置。这样排查问题时能还原当时的上下文。我在memories表里加了一个retrieval_log表记录每次检索的query、返回的记忆ID列表、注入的prompt片段。这个日志在调试时非常有用强烈建议加上。7. 关于Agent Memory的一些个人体会做Agent Memory这段时间最大的感受是这不是一个纯技术问题更多是一个产品设计问题。存什么、怎么存、什么时候取这些决策取决于你的Agent要解决什么问题、服务什么用户。没有通用的最优解只有适合特定场景的解。我见过一些团队一上来就追求大而全的记忆系统结果复杂度失控维护成本极高。我的建议是从最简单的方案开始先用Redis存最近N轮对话跑起来看效果。遇到瓶颈了再加向量检索再加遗忘机制再加MCP集成。渐进式演进比一步到位更靠谱。另外记忆的“质量”比“数量”重要得多。与其存一万条低质量记忆不如存一百条高密度、高相关性的记忆。提取和提炼环节的投入会在检索和使用环节加倍回报。最后分享一个我常用的调试技巧把Agent的每次决策和对应的记忆注入打印出来人工看几十条你就能发现很多自动化指标发现不了的问题。比如某类记忆从来没被检索到、某些记忆总是被错误召回、注入位置导致模型忽略记忆等等。这些细节只有肉眼过一遍才能发现。
阅读完成 · 觉得有帮助?
咨询建站