ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Agent记忆系统落地实战:三层架构、MCP协议与Docker部署

Agent记忆系统落地实战:三层架构、MCP协议与Docker部署 1. 为什么“记忆”才是Agent落地的真正瓶颈做过LLM应用的人都有一个共同体会模型本身的能力在快速拉平真正拉开产品差距的是模型之外的那一圈工程设施。而在这圈设施里**记忆Memory**是最容易被低估、也最容易踩坑的一环。你让一个Agent连续处理三天任务它第二天就忘了第一天定下的约束你让它记住用户的偏好它转头把偏好和事实混在一起开始一本正经地胡说。这不是模型不行是记忆架构没设计好。“hindsight”这个词本身很有意思字面意思是“事后之明”——回头看时才明白当时该怎么做。把它作为项目标题指向的正是Agent记忆系统里最核心的一个命题如何让Agent在事后能够正确回溯、检索、利用过去发生过的交互而不是把上下文当成一锅粥全塞进prompt里。结合热搜词里的agent memory、working memory、MCP、Docker、tencentdb agent memory这些线索可以判断这是一个围绕Agent记忆层展开的工程实践项目涉及记忆的存储、检索、生命周期管理以及如何通过MCP协议把记忆能力标准化地暴露给上层Agent框架。这篇文章适合谁看如果你正在做LLM应用尤其是多轮对话、任务型Agent、个人助理类产品并且已经被“上下文窗口不够用”“记忆检索不准”“多Agent之间状态不同步”这些问题折磨过那这篇内容就是写给你的。我会从架构设计讲到Docker部署从MCP协议接入讲到记忆检索的调优尽量把每个决策背后的“为什么”讲清楚让你能直接抄作业也能理解为什么这么抄。先说清楚一个基本认知Agent的记忆不是简单的向量数据库。很多人一上来就搭个向量库把对话历史embedding进去检索top-k塞回prompt然后就宣称“我的Agent有记忆了”。这套方案在demo阶段能跑通一上生产就崩。原因在于人类的记忆是有层次的——工作记忆working memory负责当前任务的短期状态长期记忆负责跨会话的知识沉淀而两者之间的转换、遗忘、强化机制才是记忆系统的灵魂。hindsight这个项目要解决的正是这套层次化记忆的工程落地问题。2. 记忆系统的整体架构与选型逻辑2.1 三层记忆模型working memory、episodic memory、semantic memory在动手写代码之前先把记忆的层次分清楚这决定了你后面所有的存储和检索设计。我采用的是三层模型这套模型在认知科学里有对应理论落到工程上也非常好操作。第一层是working memory工作记忆。它对应的是当前任务正在进行的短期状态比如用户刚才说的那句话、当前对话轮次的目标、临时计算出来的中间结果。这一层的生命周期很短通常就是当前会话或当前任务周期任务结束就可以丢弃或归档。存储上我直接用Redis或者进程内内存读写要快不要求持久化。第二层是episodic memory情景记忆。它记录的是“什么时候发生了什么”比如“用户在周三下午让我帮他订了一张去上海的机票”。这一层是带时间戳的事件流检索时往往按时间范围或事件类型来查。存储上用关系型数据库或者带时间索引的文档库都行我实测下来PostgreSQL配合时间分区表很稳。第三层是semantic memory语义记忆。它沉淀的是从多次交互中抽象出来的稳定知识比如“用户偏好靠窗座位”“用户所在团队使用Python技术栈”。这一层是去时间化的检索靠语义相似度向量数据库是主力。注意很多人把episodic和semantic混在一起存结果检索时要么召回一堆过时的事件要么把临时状态当成长期知识。分层存储、分层检索是hindsight这类项目能不能用的分水岭。2.2 为什么选MCP作为记忆能力的暴露协议热搜词里反复出现MCP这里必须展开讲。MCPModel Context Protocol本质上是一套标准化的上下文交互协议它让Agent框架和外部能力工具、数据源、记忆服务之间有了统一的接口。你可以把它类比成USB-C——以前每个设备一个接口现在统一了插上就能用。把记忆系统做成一个MCP Server好处非常直接。第一解耦。记忆的存储和检索逻辑完全独立于Agent框架你换框架、换模型记忆服务不用动。第二复用。同一个记忆服务可以同时被多个Agent、多个会话调用天然支持多Agent共享记忆。第三可测试。MCP有明确的请求响应格式你可以单独对记忆服务做单元测试和压测不用把整个Agent跑起来。我试过不用MCP、直接在Agent代码里硬编码记忆逻辑的方案前期开发快但一旦要接第二个Agent或者换存储后端重构成本高得吓人。用MCP虽然前期多写一层协议适配但后期扩展性完全不是一个量级。2.3 Docker化部署为什么不用裸机热搜词里docker、docker compose、docker desktop出现频率极高说明部署方式是大家关心的重点。hindsight这类记忆服务我强烈建议Docker化理由有三。一是依赖隔离。记忆服务通常要同时跑向量库、关系库、缓存裸机装这些依赖版本冲突能让你怀疑人生。Docker把每个组件关进自己的容器互不干扰。二是环境一致性。开发机是Windows服务器是Linux裸机部署时“在我电脑上能跑”是常态。Docker镜像一旦构建好到哪都是同一套环境。三是编排方便。用docker compose一个文件描述所有服务启动、停止、扩容都是一条命令。下面这张表是我实际用的服务编排规划服务名镜像端口作用数据卷memory-api自构建8080MCP Server主服务无状态postgrespostgres:165432情景记忆存储pgdataredisredis:7-alpine6379工作记忆缓存无qdrantqdrant/qdrant6333语义记忆向量库qdrant_data这套组合我跑了大半年稳定性没问题。qdrant选它是因为单机部署简单、API友好如果你团队已经在用Milvus或Weaviate替换掉就行MCP层不用改。3. 核心细节解析记忆的写入、检索与遗忘3.1 记忆写入不是所有对话都值得记新手最容易犯的错是把每一轮对话都往记忆库里塞。结果就是记忆库迅速膨胀检索时噪声比信号还多。hindsight的核心设计之一是写入前的价值判断。我的做法是在写入链路上加一个轻量的“记忆提取器”它可以是规则也可以是一个小模型调用。提取器要回答三个问题这段内容里有没有值得长期保留的事实有没有用户明确表达的偏好有没有需要跨会话追踪的任务状态三个都没有就直接丢弃只留在working memory里。具体实现上我用一个prompt模板让LLM做结构化抽取输出JSON格式的记忆条目EXTRACT_PROMPT 从以下对话中抽取值得长期记忆的条目。每条记忆包含 - type: fact / preference / task_state - content: 记忆内容一句话 - confidence: 0-1之间的置信度 - ttl: 建议的存活时间秒事实类可长任务状态类要短 对话内容 {dialogue} 只输出JSON数组没有值得记忆的内容就输出空数组。 这个抽取步骤会增加一次LLM调用成本上要权衡。我的经验是对于高频对话场景可以用规则先过滤掉明显无价值的轮次比如纯寒暄、纯确认只对包含实体、数字、偏好词的轮次做LLM抽取能省下六七成的调用。实操心得抽取出来的记忆条目一定要带confidence和ttl。confidence低的记忆在检索时降权ttl到期的记忆自动归档。这两个字段是记忆库保持“干净”的关键别省。3.2 记忆检索token的三个点——key、query、value热搜词里有一句很精辟的话“llm的token三个点key我是谁、query我在找什么、value我能提供什么”。这其实是在讲检索的本质。记忆检索不是简单的向量相似度top-k而是要同时考虑查询意图query、**记忆主体key和记忆内容value**三者的匹配。我的检索链路是这样的先根据当前对话生成检索query然后做混合检索——向量相似度负责语义匹配关键词匹配负责精确命中时间衰减因子负责给近期记忆加权。最后用一个重排序模型或者简单的加权公式把结果排序取top-n注入prompt。加权公式我调了很久最终稳定在这套参数上final_score 0.5 * vector_similarity 0.3 * keyword_match_score 0.2 * time_decay_factortime_decay_factor用指数衰减半衰期设成7天。也就是说一条记忆如果7天内没被再次命中它的时间权重就减半。这个参数不是拍脑袋定的是根据用户行为数据调的——大部分偏好类记忆的有效期就在一周左右超过一周要么已经内化成习惯要么已经过时。3.3 记忆遗忘主动删除比被动堆积更重要记忆系统最难的不是“记住”是“忘记”。一个不会遗忘的系统最终会被自己的历史压垮。hindsight里我设计了三层遗忘机制。第一层是TTL过期。写入时带的ttl到期记忆自动从活跃区移到归档区检索时默认不召回。第二层是冲突消解。当新记忆和旧记忆矛盾时比如用户先说喜欢咖啡后说戒了咖啡不是简单覆盖而是把旧记忆标记为superseded保留历史但降低权重。这样Agent在被问到时能说“你之前喜欢咖啡后来戒了”而不是生硬地只认最新一条。第三层是低频淘汰。定期扫描记忆库把长期未被检索命中、confidence又低的记忆清理掉。这个定期任务我用一个cron容器跑每周一次。注意遗忘机制一定要可配置、可回滚。我踩过的坑是一次性删了太多记忆结果Agent突然“失忆”用户体感极差。后来改成先归档、观察一段时间再物理删除稳多了。4. 实操过程从零把hindsight跑起来4.1 环境准备与Docker安装要点先说环境。Windows用户装Docker Desktop最容易卡在“virtualization support not detected”这个报错上。这不是Docker的问题是BIOS里虚拟化没开。进BIOS把Intel VT-x或AMD-V打开重启就好。如果开了还报错检查是不是Hyper-V和WSL2冲突Windows11下建议直接用WSL2后端性能比Hyper-V好。Linux用户装Docker Engine别装Desktop。装完记得把当前用户加进docker组否则每条命令都要sudosudo usermod -aG docker $USER newgrp docker验证安装docker --version docker compose version两个命令都有输出环境就算齐了。这里提醒一句docker compose和docker-compose是两个东西前者是v2插件后者是v1独立二进制。现在统一用docker compose中间空格别再用带横杠的老命令。4.2 用docker compose一键拉起记忆服务把下面这个compose文件存成docker-compose.yml放在项目根目录version: 3.9 services: memory-api: build: ./memory-api ports: - 8080:8080 environment: - POSTGRES_DSNpostgresql://mem:mem123postgres:5432/memory - REDIS_URLredis://redis:6379/0 - QDRANT_URLhttp://qdrant:6333 depends_on: - postgres - redis - qdrant restart: unless-stopped postgres: image: postgres:16 environment: - POSTGRES_USERmem - POSTGRES_PASSWORDmem123 - POSTGRES_DBmemory volumes: - pgdata:/var/lib/postgresql/data restart: unless-stopped redis: image: redis:7-alpine restart: unless-stopped qdrant: image: qdrant/qdrant volumes: - qdrant_data:/qdrant/storage restart: unless-stopped volumes: pgdata: qdrant_data:启动命令就一句docker compose up -d第一次跑会拉镜像、构建memory-api大概三五分钟。起来之后用docker compose ps看状态四个服务都是running就对了。如果memory-api起不来八成是依赖的服务还没ready看日志docker compose logs -f memory-api4.3 MCP Server的接口设计与实现memory-api这个服务对外暴露的就是MCP协议定义的几个方法。核心是三个memory.write、memory.search、memory.forget。我用Python的FastAPI做HTTP层MCP的JSON-RPC格式在中间做一层转换。写入接口的关键是幂等。同一条记忆重复写入不能产生重复条目我用content的hash做去重键。检索接口的关键是超时控制向量检索偶尔会慢我设了500ms的超时超时就降级到只走关键词检索保证Agent不会因为记忆服务卡住而整体卡住。app.post(/mcp) async def mcp_handler(request: dict): method request.get(method) params request.get(params, {}) if method memory.write: return await write_memory(params) elif method memory.search: return await search_memory(params) elif method memory.forget: return await forget_memory(params) else: return {error: unknown method}这套接口设计我用了很久最大的好处是Agent框架完全不用关心记忆存在哪、怎么检索它只管调MCP方法。哪天你把qdrant换成别的向量库Agent侧一行代码都不用改。4.4 接入Agent框架与联调MCP Server跑起来后在Agent框架里配置MCP连接。以常见的配置方式为例在Agent的配置文件里加上{ mcpServers: { hindsight: { url: http://localhost:8080/mcp, timeout: 3000 } } }联调时先做单点测试手动调一次write再调一次search看能不能召回。这一步通了再接到Agent的对话循环里。我建议在Agent的每轮对话前后各加一个hook对话前search注入记忆对话后write沉淀记忆。联调阶段最容易出的问题是记忆注入过多导致prompt超长。我的做法是给注入的记忆设一个token预算比如最多500 token超了就按score截断。这个预算要根据你模型的实际上下文窗口来定别贪多。5. 常见问题与排查技巧实录5.1 记忆检索召回不准的排查思路召回不准是最常见的问题排查要按链路一步步来。先确认写入是否成功——直接查qdrant和postgres看数据在不在。数据在再确认检索query生成得对不对把query打印出来看。query没问题再看相似度分数分布如果所有分数都差不多低说明embedding模型不适合你的语料考虑换模型。我整理了一张速查表现象可能原因排查方法解决完全召不回写入失败查向量库条目数检查write链路日志召回但不相干embedding不匹配打印相似度分布换embedding模型召回旧记忆时间衰减失效检查time_decay计算修正衰减参数召回重复条目去重键失效查content hash修复幂等逻辑检索超时向量库负载高看qdrant监控加索引或扩容5.2 Docker网络不通的经典坑docker compose里服务之间用服务名互相访问这是最容易踩的坑。memory-api里连postgreshost要写postgres而不是localhost。写localhost的话容器会去连自己当然连不上。另一个坑是端口映射。compose里ports: 8080:8080前面是宿主机端口后面是容器端口。如果你宿主机8080被占了改成18080:8080然后从宿主机访问用18080容器之间互访还是用8080。还有Windows下Docker Desktop的WSL2网络偶尔会出现宿主机访问不了容器端口的情况。重启Docker Desktop一般能解决实在不行在WSL里用curl localhost:8080先确认容器本身是通的。5.3 记忆膨胀导致性能下降的处理跑了一段时间后如果发现检索越来越慢八成是记忆库膨胀了。先看条目数超过十万条就要考虑分片或归档。我的做法是按时间分区超过90天的记忆移到冷存储检索时默认只查热区。另外向量库的索引参数也要调。qdrant默认的HNSW参数对大规模数据不是最优m和ef_construct要根据数据量调大。我十万条数据下用m32, ef_construct256检索延迟能控制在50ms以内。实操心得定期跑一次记忆库的“体检”统计条目数、平均检索延迟、命中率。这三个指标一旦异常提前处理别等用户投诉了才动手。5.4 MCP接入时的授权与schema报错热搜词里提到“llm request failed: provider rejected the request schema or tool payload”和“codex无法找到mcp”这两个问题我都遇到过。前者通常是MCP方法的参数schema和Agent框架期望的不一致检查你的JSON-RPC参数名和类型别用框架不认识的字段。后者多半是MCP Server没启动或者URL配错先用curl手动调一次确认服务活着。授权方面如果MCP Server暴露在非本地环境一定要加token鉴权。我在memory-api里加了一个简单的Bearer token校验配置在环境变量里Agent侧请求时带上。别裸奔记忆数据里可能有用户隐私。6. 记忆系统的扩展方向与个人体会hindsight这套架构跑通之后能扩展的方向其实很多。比如多Agent共享记忆——几个Agent连同一个MCP Server天然就能看到彼此沉淀的记忆协作时不用反复同步状态。再比如记忆的可视化把记忆库里的条目按类型、时间、命中率画出来能直观看到Agent“学到了什么”调试和演示都好用。还有一个我觉得很有价值的方向是记忆的主动整理。现在的遗忘是被动的TTL、低频淘汰未来可以让LLM定期回顾记忆库把零散的情景记忆抽象成更高层的语义记忆就像人睡觉时大脑整理白天的经历一样。这个思路在学术界已经有相关研究工程上落地也不难无非是加一个定期任务。我个人在实际操作中的体会是记忆系统的价值不在于“记得多”而在于“记得准、忘得对”。一开始我总想把所有东西都记下来结果Agent反而变笨了因为噪声太多。后来狠下心做减法只记真正有价值的检索准确率反而上去了。这个道理说起来简单但真到写代码时克制住“多存点总没坏处”的冲动是需要点定力的。最后分享一个小技巧给记忆条目加上来源标记source标明这条记忆是从哪次对话、哪个Agent来的。排查问题时顺着来源能快速定位到原始上下文比在记忆库里瞎猜高效得多。这个字段我一开始没加后来补上时已经积累了几万条无来源记忆只能靠时间戳反推费了老大劲。
返回列表