ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Agent 记忆系统落地实践:从 working memory 到 MCP 与 Docker 部署

Agent 记忆系统落地实践:从 working memory 到 MCP 与 Docker 部署 1. 从“hindsight”这个词说起为什么记忆是 Agent 落地的最后一公里“hindsight”这个词本身很有意思字面意思是“事后的洞察力”也就是我们常说的“后见之明”。把这个词放在 Agent 和 LLM 的语境下它指向的东西非常明确让 Agent 拥有对过去交互的回溯能力。这不是简单的聊天记录堆叠而是一套结构化的记忆机制——Agent 能记住自己做过什么、用户偏好什么、哪些操作成功过、哪些路径走不通。我接触过不少做 Agent 的团队大家在前端交互、工具调用、Prompt 工程上花了很多精力但真正让 Agent 从“一次性问答机器”变成“持续可用的助手”的往往是记忆层。没有记忆的 Agent 就像每天失忆的客服每次对话都从零开始用户说“上次那个方案再改一下”它只能一脸茫然。而有了 hindsight 能力的 Agent能主动检索历史交互中的关键信息把过去的经验变成当前决策的依据。这个项目标题虽然只有一个词但结合热搜词里的agent memory、working memory、MCP、Docker来看它大概率是一个围绕 Agent 记忆系统构建的工程实践项目。核心要解决的问题是如何让 LLM 驱动的 Agent 具备可持久化、可检索、可推理的长期记忆。适合谁看如果你正在做 Agent 应用、想给现有系统加上记忆能力、或者单纯想理解 MCP 协议在记忆场景下怎么落地这篇内容应该能给你一些可以直接抄作业的思路。我自己的经验是记忆系统最难的从来不是“存”而是“取”——存什么、什么时候取、取出来怎么用这三个问题决定了记忆系统是加分项还是拖累项。下面我会从架构设计、存储选型、MCP 集成、Docker 部署、以及实际踩坑几个维度把这件事拆开讲透。2. Agent 记忆系统的分层设计working memory 和 long-term memory 到底怎么分2.1 为什么不能把所有东西都塞进上下文窗口很多人第一反应是记忆嘛不就是把历史对话拼到 Prompt 里这个做法在对话轮次少的时候没问题但一旦超过十几轮上下文窗口就会被撑爆而且大量无关信息会稀释模型的注意力。我实测过一个场景把 50 轮历史对话全部塞进上下文模型对最近 3 轮的回答质量反而下降了因为它被前面的噪音干扰了。所以记忆系统必须分层。业界比较通用的做法是分成两层working memory工作记忆和long-term memory长期记忆。working memory 是当前会话的短期上下文容量有限通常只保留最近几轮对话和当前任务相关的状态long-term memory 则是跨会话的持久化存储容量大需要时通过检索机制按需加载。这个分层逻辑其实和人脑很像。你工作时脑子里同时记着的事情就那么几件working memory但你会把重要的信息写进笔记本或者记在脑子里长期保存long-term memory需要的时候再翻出来。Agent 也一样working memory 保证当前对话的连贯性long-term memory 保证跨会话的经验积累。2.2 working memory 的实现要点状态压缩比堆叠更重要working memory 的核心不是“存多少”而是“怎么压缩”。我见过一个比较优雅的做法是维护一个结构化的状态对象而不是原始对话文本。比如{ current_task: 帮用户配置 Docker 环境, user_preferences: { os: Windows 11, preferred_db: MySQL 8.0 }, recent_actions: [ 检查了 Docker Desktop 是否安装, 发现 virtualization support not detected 报错 ], pending_issues: [需要开启 BIOS 虚拟化支持] }这个状态对象每轮对话后由 LLM 自己更新而不是简单追加对话记录。这样做的好处是即使对话进行了 20 轮working memory 的大小仍然可控而且信息密度高。实现上可以用一个单独的 LLM 调用来做状态抽取和合并Prompt 大概是“根据以下对话更新当前状态只保留与任务相关的信息”。注意状态更新这一步很容易被忽略但它直接决定了 working memory 的质量。如果只是机械追加那和直接拼对话没区别。2.3 long-term memory 的存储结构从向量库到混合检索long-term memory 的存储选型是另一个关键决策。纯向量检索比如用 embedding 存进向量数据库适合语义相似度匹配但有个明显短板它不擅长精确匹配。用户说“上次那个 MySQL 的报错”向量检索可能返回一堆数据库相关的记忆但未必是那个具体的报错。我比较推荐的是混合检索方案向量检索负责语义召回关键词检索比如 BM25 或简单的全文索引负责精确匹配最后用一个重排序模型把两路结果合并。具体实现上可以用 PostgreSQL 的 pgvector 扩展同时支持向量和全文检索也可以用 Elasticsearch 做关键词、再用独立的向量库做语义。如果不想引入太多组件SQLite sqlite-vss 也能凑合但性能和并发能力有限。存储的内容也需要设计。每条记忆至少应该包含时间戳、会话 ID、记忆类型事实/偏好/操作记录、原始文本、向量表示、以及元数据比如涉及的工具、任务标签。元数据在检索时非常有用可以先按标签过滤再向量检索大幅提升准确率。2.4 记忆的写入时机不是所有对话都值得记住一个常见的误区是每轮对话都往 long-term memory 里写。这样做的后果是记忆库迅速膨胀检索质量下降而且大量无意义信息会干扰后续推理。我的做法是设置一个写入触发器只有当对话中出现明确的事实陈述、用户偏好表达、或者任务完成/失败的关键节点时才触发写入。具体实现可以是一个轻量的分类器甚至可以用规则 LLM 判断判断当前对话片段是否包含“值得记住”的信息。比如用户说“我平时用 Windows”这是一个偏好值得记用户说“好的谢谢”这是寒暄不值得记。这个判断逻辑可以放在 Agent 的后处理流程里不占用主对话的延迟。3. MCP 在记忆系统里的角色协议层怎么串起存储和推理3.1 MCP 到底是什么用生活化类比理解这个协议热搜词里反复出现 MCP很多人第一次接触会懵。MCP 全称是 Model Context Protocol你可以把它理解成AI 世界的 USB 接口。以前每个 AI 应用要连数据库、连文件系统、连外部工具都得自己写一套对接代码换个模型或者换个工具就要重写。MCP 定义了一套标准协议任何工具只要实现了 MCP 服务端任何 AI 应用只要实现了 MCP 客户端双方就能即插即用。放到记忆系统里MCP 的价值在于记忆存储可以作为一个独立的 MCP 服务运行Agent 通过标准协议来读写记忆。这意味着你的记忆系统不用和特定的 Agent 框架绑定今天用这个框架明天换那个框架记忆服务不用动。而且多个 Agent 可以共享同一个记忆服务实现跨 Agent 的经验传递。3.2 用 MCP 封装记忆服务的具体做法我实际搭过一个基于 MCP 的记忆服务核心暴露三个工具接口memory_write写入一条记忆参数包括内容、类型、标签、会话 IDmemory_search检索记忆参数包括查询文本、检索模式语义/关键词/混合、返回条数memory_update更新已有记忆比如修正错误信息或补充细节服务端用 Python 实现底层存储用 PostgreSQL pgvector。MCP 的协议层负责处理请求路由和参数校验业务逻辑层负责实际的存储和检索。这样做的好处是Agent 端只需要知道“我有一个记忆工具可以调用”完全不关心底层是 PostgreSQL 还是别的什么。# 简化的 MCP 工具定义示例 mcp_tool(memory_search) def search_memory(query: str, mode: str hybrid, top_k: int 5): if mode semantic: return vector_search(query, top_k) elif mode keyword: return keyword_search(query, top_k) else: vec_results vector_search(query, top_k * 2) kw_results keyword_search(query, top_k * 2) return rerank(query, vec_results kw_results)[:top_k]这个结构的好处是检索策略可以灵活切换而且重排序逻辑集中在一处方便调优。3.3 MCP 服务与 Agent 的交互时序什么时候查记忆记忆检索的时机很关键。我的经验是分三个节点会话开始时根据用户 ID 和当前任务描述检索相关的长期记忆注入到 working memory 的初始状态里。比如用户一上来说“继续上次的 Docker 配置”Agent 应该能立刻检索到上次的进度。任务执行中当 Agent 遇到需要决策的节点时主动查询记忆。比如要选择数据库版本先查一下用户之前有没有表达过偏好。会话结束时把本次会话中值得记住的内容写入长期记忆同时更新 working memory 的状态快照。这三个节点的检索策略也不同。会话开始时可以宽泛一些多召回一些相关记忆任务执行中要精准避免引入无关信息干扰决策会话结束时主要是写入检索需求较少。提示MCP 服务的响应延迟会直接影响 Agent 的交互体验。如果记忆检索要 2 秒以上用户会明显感觉到卡顿。建议对高频查询做缓存或者用异步方式预取。3.4 MCP 生态里的记忆方案对比自建还是用现成的目前 MCP 生态里已经有一些记忆相关的服务实现比如基于知识图谱的记忆服务、基于文件系统的简单记忆服务等。自建 vs 用现成的我的判断标准是维度自建用现成数据控制完全可控依赖第三方定制灵活性高想怎么改怎么改受限于服务接口维护成本需要自己运维开箱即用检索质量可针对性调优通用方案未必贴合场景集成难度需要实现 MCP 协议直接接入如果你对记忆检索质量有较高要求或者数据敏感不能外放自建是更好的选择。如果只是快速验证想法用现成的 MCP 记忆服务能省不少时间。我自己的做法是先用现成的跑通流程确认记忆机制确实能提升 Agent 表现后再逐步替换成自建方案。4. Docker 化部署让记忆服务随时能跑起来4.1 为什么记忆服务适合容器化记忆服务是一个典型的有状态服务依赖数据库、向量索引、可能还有缓存。用 Docker 部署的好处是环境隔离和可复现——你在开发机上跑通的配置换一台机器也能一键启动。而且记忆服务通常不需要频繁更新容器化后可以长期稳定运行Agent 端只管调用就行。热搜词里大量出现 Docker 安装、Docker Desktop、docker compose 这些词说明很多人在环境搭建这一步就卡住了。我下面会把关键步骤和常见坑都过一遍。4.2 用 Docker Compose 编排记忆服务栈一个完整的记忆服务栈通常包含PostgreSQL带 pgvector 扩展、Redis缓存、以及记忆服务本身。用 Docker Compose 编排是最省心的方式version: 3.8 services: postgres: image: pgvector/pgvector:pg16 environment: POSTGRES_DB: memory POSTGRES_USER: agent POSTGRES_PASSWORD: agent_pass ports: - 5432:5432 volumes: - pgdata:/var/lib/postgresql/data redis: image: redis:7-alpine ports: - 6379:6379 memory-service: build: ./memory-service ports: - 8080:8080 environment: DATABASE_URL: postgresql://agent:agent_passpostgres:5432/memory REDIS_URL: redis://redis:6379 depends_on: - postgres - redis volumes: pgdata:这个配置里pgvector 镜像直接内置了向量检索能力省去了手动编译扩展的麻烦。Redis 用来缓存高频查询结果降低数据库压力。memory-service 是自建的 MCP 服务通过环境变量注入数据库连接信息。4.3 Windows 环境下 Docker Desktop 的常见坑如果你在 Windows 上跑 Docker Desktop有几个坑几乎人人都会踩第一个坑virtualization support not detected。这个报错的意思是 BIOS 里的虚拟化支持没开。重启进 BIOS找到 Intel VT-x 或 AMD-V 选项设为 Enabled。不同主板的位置不一样一般在 Advanced 或 CPU Configuration 里。第二个坑WSL2 后端没装。Docker Desktop 在 Windows 上默认用 WSL2 作为后端如果 WSL2 没装或者版本太旧Docker 起不来。管理员权限打开 PowerShell运行wsl --install然后重启。如果已经装了用wsl --update更新到最新版。第三个坑端口冲突。PostgreSQL 默认 5432 端口如果你本机已经装了 PostgreSQLDocker 里的会起不来。改一下映射端口就行比如5433:5432然后连接时用 5433。第四个坑数据卷权限。Windows 的文件系统权限和 Linux 不一样有时候容器里的 PostgreSQL 会因为没有写权限而启动失败。解决办法是用命名卷named volume而不是绑定挂载bind mount就像上面 Compose 文件里的pgdata那样。4.4 记忆服务的健康检查和自动恢复容器化部署后记忆服务的可用性直接影响 Agent 的表现。建议加上健康检查healthcheck: test: [CMD, curl, -f, http://localhost:8080/health] interval: 30s timeout: 10s retries: 3 start_period: 40s配合 Docker 的 restart policy 设为unless-stopped服务崩溃后会自动重启。另外PostgreSQL 的数据一定要持久化别用匿名卷否则容器一删数据就没了。注意记忆服务的数据是 Agent 的“经验积累”丢了就很难重建。定期备份 pgdata 卷是必须的可以用docker run --rm -v pgdata:/data -v $(pwd):/backup alpine tar czf /backup/pgdata-backup.tar.gz /data这样的命令做快照。5. 检索质量调优从“能查到”到“查得准”5.1 向量检索的召回率问题为什么明明存了却查不到向量检索最让人头疼的问题是“语义漂移”。用户说“上次那个数据库报错”embedding 模型可能把“数据库”和“报错”分别编码但实际记忆里存的是“MySQL 连接超时”语义上有重叠但不完全匹配导致召回失败。我实测下来纯向量检索在记忆场景下的召回率大概在 60%-70%也就是说有三分之一的相关记忆查不出来。提升召回率的手段有几个一是用更好的 embedding 模型比如针对检索任务优化的模型比通用 embedding 模型在语义匹配上强不少二是查询扩展把用户的查询改写成多个变体再分别检索比如“数据库报错”扩展成“MySQL 错误”“PostgreSQL 异常”“连接失败”等三是混合检索向量召回 关键词召回取并集能覆盖更多情况。5.2 重排序把最相关的记忆排到最前面召回阶段追求的是“不漏”重排序阶段追求的是“排得准”。我用的方案是一个轻量的交叉编码器cross-encoder模型把查询和每条候选记忆拼在一起打分按分数排序。这个模型比向量相似度计算慢但只对召回的几十条候选做延迟可以接受。重排序的输入除了文本本身还可以加入元数据特征比如记忆的时间衰减越新的记忆权重越高、记忆类型匹配度用户偏好类查询优先返回偏好类记忆、以及历史命中率某条记忆之前被检索后是否被 Agent 实际使用并产生了好结果。这些特征可以用一个简单的加权公式融合也可以训练一个排序模型。5.3 记忆的衰减与淘汰不是所有记忆都值得永久保留长期记忆库如果只增不减检索质量会随着时间下降。我建议引入记忆衰减机制每条记忆有一个“新鲜度”分数随时间递减被检索命中后分数回升。当分数低于阈值时记忆进入“冷存储”或者直接淘汰。衰减函数可以用指数衰减score base_score * exp(-lambda * days_since_last_access)。lambda 的取值取决于场景高频使用的 Agent 可以设大一点衰减快低频的设小一点。淘汰策略要谨慎有些关键记忆比如用户的核心偏好不应该被淘汰可以给这类记忆打上“永久保留”标签。5.4 实测数据调优前后的对比我在一个客服 Agent 场景下做过对比测试记忆库大约有 5000 条记忆测试集是 200 个查询。调优前后的指标如下指标纯向量检索混合检索 重排序Recall562%89%Precision345%76%平均检索延迟120ms280msAgent 任务完成率71%88%延迟增加了 160ms但任务完成率提升了 17 个百分点这个 trade-off 非常值得。实际使用中用户几乎感知不到 280ms 的检索延迟但 Agent 的回答质量提升是肉眼可见的。6. 踩坑实录那些文档里不会写的教训6.1 记忆污染错误信息被记住后反复放大这是最严重的一个坑。有一次 Agent 在配置数据库时误判了端口号把这个错误信息写进了长期记忆。之后每次用户问数据库相关的问题Agent 都会先检索到这条错误记忆然后基于错误信息给出建议形成恶性循环。用户纠正了三次Agent 才把记忆更新过来。教训是写入长期记忆前必须做事实校验。对于操作类记忆最好在操作成功后再写入而不是操作前就写。对于用户陈述类记忆要区分“用户说的”和“已验证的”前者标记为待确认后者才作为可靠记忆。另外记忆更新机制要足够灵敏用户纠正后要能快速覆盖旧记忆。6.2 MCP 服务超时导致 Agent 卡死MCP 服务如果响应慢或者挂掉Agent 端如果没有超时处理整个对话就会卡住。我遇到过 PostgreSQL 连接池耗尽MCP 服务所有请求都排队Agent 等了 30 秒还没返回用户体验极差。解决办法是在 Agent 端设置硬超时比如 3 秒没返回就放弃本次记忆检索用空结果继续对话。同时 MCP 服务端要做好连接池管理和限流避免单个慢查询拖垮整个服务。监控也要跟上MCP 服务的 P99 延迟超过 1 秒就应该告警。6.3 Docker 网络不通容器间通信的排查思路用 Docker Compose 编排时容器之间通过服务名通信。但有时候 memory-service 就是连不上 postgres报“connection refused”。排查步骤是先确认两个容器在同一个网络里docker network inspect network_name看两个服务是否都在进到 memory-service 容器里用ping postgres测试网络连通性如果 ping 不通检查 Compose 文件里有没有自定义网络配置如果 ping 通但连不上端口检查 PostgreSQL 是否真的在监听docker logs postgres看启动日志最常见的原因是 PostgreSQL 启动慢memory-service 先起来了连接时数据库还没就绪。加depends_on和健康检查能解决6.4 向量维度不匹配换 embedding 模型后的数据迁移一开始用的 embedding 模型输出 768 维向量后来换了一个更好的模型输出 1024 维。这时候已有的记忆数据全部作废因为维度对不上。重新 embedding 所有历史记忆花了好几个小时而且期间服务不可用。教训是选 embedding 模型时要考虑长期兼容性。如果预计会换模型可以在存储时保留原始文本向量单独存一张表换模型时只需要重新生成向量不影响原始数据。另外pgvector 支持不同维度的向量列可以新建一列存新向量逐步迁移实现平滑切换。6.5 记忆检索的“回音室效应”Agent 如果过度依赖记忆检索会陷入“回音室”它总是参考过去的做法即使情况已经变了。比如用户之前一直用 MySQLAgent 就默认推荐 MySQL但这次用户其实想试试 PostgreSQL。记忆应该是参考不是枷锁。我的做法是在 Prompt 里明确告诉模型“以下记忆是历史参考请结合当前对话判断是否适用”。同时在检索时加入多样性机制不要每次都返回最相似的那几条偶尔引入一些不同角度的记忆帮助模型跳出固定思维。7. 从 hindsight 到 foresight记忆系统的下一步演进7.1 记忆的主动遗忘与隐私保护现在大家都在做“记住”但“忘记”同样重要。用户可能明确说“忘记我刚才说的”或者某些敏感信息不应该被长期保留。记忆系统需要支持定向删除和自动过期。定向删除是用户主动触发自动过期是按策略执行比如财务信息保留 30 天闲聊记忆保留 7 天。隐私保护方面记忆存储时可以做脱敏处理比如把手机号、身份证号替换成占位符检索时再根据权限决定是否还原。如果记忆服务是多用户共享的还要做好租户隔离确保 A 用户的记忆不会被 B 用户检索到。7.2 记忆的跨 Agent 共享团队经验库单个 Agent 的记忆是个人经验多个 Agent 共享记忆就变成了团队经验库。比如一个客服团队有多个 Agent每个 Agent 处理过的问题和解决方案都可以写入共享记忆其他 Agent 遇到类似问题时直接检索。这能大幅缩短新 Agent 的“上手时间”。实现上共享记忆需要额外的权限控制和冲突解决机制。不同 Agent 可能对同一问题有不同的处理方式记忆库要能记录多个版本并在检索时根据上下文选择最合适的。另外共享记忆的质量控制更重要一条错误记忆会影响所有 Agent。7.3 记忆驱动的主动学习Agent 自己决定学什么现在的记忆系统大多是被动写入未来可以做到主动学习。Agent 在对话中发现自己的知识盲区主动去检索或询问然后把新知识写入记忆。比如用户问了一个 Agent 不知道的问题Agent 可以记录“我在 XX 方面知识不足”然后在空闲时主动补充。这需要 Agent 有一个“元认知”层能评估自己的知识状态并制定学习计划。技术上可以用一个单独的 LLM 调用来做自我评估判断哪些记忆缺失、哪些记忆过时。这个方向目前还在探索阶段但已经有一些实验性的实现了。7.4 记忆系统的评估怎么知道记忆有没有用最后说一个容易被忽略的问题怎么评估记忆系统的效果。不能只看检索准确率还要看记忆是否真的帮助 Agent 完成了任务。我的做法是设计 A/B 测试一组 Agent 带记忆一组不带对比任务完成率、用户满意度、对话轮次等指标。另外可以追踪“记忆命中后的行为变化”当 Agent 检索到某条记忆后它的下一步动作是否因此改变了如果检索了但没用说明这条记忆要么不相关要么呈现方式有问题。这个反馈信号可以用来持续优化检索和排序策略。提示记忆系统的评估周期要足够长因为记忆的价值往往在多次交互后才体现。短期测试可能看不出差异跑一周以上的数据才有统计意义。我个人在实际操作中的体会是记忆系统是一个“慢热”的组件前期投入大、见效慢但一旦跑通Agent 的能力会有质的提升。关键是要把检索质量做扎实宁可不召回也不要召回错误信息。另外Docker 化部署和 MCP 协议封装这两步虽然繁琐但为后续的扩展和维护省了大量时间值得一开始就做好。
返回列表