ARTICLE DETAIL

资讯详情

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

Agent记忆系统实战:基于MCP与Docker构建可检索的长期记忆

Agent记忆系统实战:基于MCP与Docker构建可检索的长期记忆 1. 从“hindsight”说起为什么我们需要给Agent装上记忆“hindsight”这个词本身很有意思字面意思是“事后的洞察力”也就是我们常说的“后见之明”。放在AI Agent的语境里它指向一个非常具体且关键的问题Agent能不能记住之前发生过什么并在后续决策中真正用上这些信息我接触过不少做Agent项目的团队大家一开始都把精力放在工具调用、提示词优化、模型选型上但跑了一段时间之后几乎所有人都会撞上同一堵墙——Agent没有记忆或者说它的记忆是“假”的。每次对话重新开始它就像失忆一样用户之前说过的偏好、上一次任务执行的结果、中间踩过的坑统统不记得。你让它帮你订过一次咖啡下次它还是问你“请问您想喝什么”。这就是hindsight要解决的核心问题。它不是简单的“把聊天记录存下来”而是要让Agent具备一种可检索、可推理、可更新的长期记忆能力。结合热搜词里反复出现的agent memory、working memory、MCP、Docker这些关键词可以很清楚地看到这个项目大概率是一个围绕Agent记忆系统展开的工程实践涉及记忆的存储结构、检索机制、与LLM的交互方式以及如何通过MCP协议和Docker容器化来落地。这篇文章适合谁看如果你正在做Agent相关的开发或者你已经在用Dify、Coze这类平台搭建智能体但发现记忆功能总是不够用那这篇内容会对你有直接帮助。如果你只是对LLM应用感兴趣想了解“Agent记忆”到底是怎么回事我也会尽量用生活化的例子把它讲清楚。提示本文涉及的所有技术方案和参数选择均基于我在实际项目中的经验总结不同场景下需要根据具体需求调整。2. 核心思路拆解Agent记忆到底该怎么设计2.1 为什么“把聊天记录塞进上下文”不是真正的记忆很多人第一次做Agent记忆的时候思路很直接把历史对话全部拼接到prompt里一起发给LLM。这个方法在对话轮次少的时候能用但很快就会遇到三个问题。第一个问题是上下文窗口有限。就算现在很多模型支持128K甚至更长的上下文但你不可能把几个月的对话记录都塞进去。而且上下文越长推理成本越高延迟也越大。我实测过一个场景当上下文超过30K token之后模型对中间部分信息的注意力明显下降这就是所谓的“lost in the middle”现象。第二个问题是信息噪音。历史对话里大量内容是寒暄、确认、重复的指令真正有价值的记忆可能只占5%。把这些噪音一起喂给模型反而会干扰它的判断。第三个问题是无法跨会话。用户今天和你聊完明天再打开如果系统没有持久化存储一切归零。就算存了数据库没有好的检索机制也等于没存。所以hindsight这类项目的核心思路一定不是“存聊天记录”而是构建一套记忆的抽象层把原始对话经过提取、压缩、结构化之后存成可检索的记忆单元在需要的时候精准召回。2.2 记忆的分层working memory和long-term memory热搜词里出现了“agent 存储 working memory”这说明working memory是一个被明确讨论的概念。在认知科学里working memory指的是人在当前任务中临时保持和操作信息的系统容量有限但访问极快。对应到Agentworking memory就是当前会话中正在处理的上下文而long-term memory则是跨会话持久化的知识。我在实际项目中通常会把记忆分成三层即时上下文当前这一轮对话的原始消息直接放在prompt里不做压缩。工作记忆当前任务相关的关键信息比如用户的目标、已完成的步骤、待办事项。这部分会随着任务推进动态更新。长期记忆跨会话保留的用户偏好、历史决策、领域知识。这部分需要持久化存储并且要有检索机制。hindsight如果要做得好必须把这三层分开处理。混在一起做最后一定是一团乱麻。2.3 为什么选MCP和Docker热搜词里MCP出现了很多次还有“mcp协议”“mcp是软件协议 硬件协议那个概念叫什么来着”这样的搜索。MCP全称是Model Context Protocol简单说就是一套让LLM应用和外部工具、数据源之间标准化交互的协议。你可以把它理解成“AI世界的USB接口”——不管你是数据库、文件系统还是API只要实现了MCPLLM就能用统一的方式去调用。对于hindsight这样的记忆系统来说MCP的价值在于解耦。记忆的存储、检索、更新可以做成独立的MCP ServerAgent通过MCP协议来访问记忆而不需要把记忆逻辑硬编码在Agent内部。这样换存储后端、换检索算法都不影响Agent本身。Docker则是解决部署和环境一致性的问题。记忆系统通常需要依赖数据库比如PostgreSQL、Redis、向量库比如Milvus、Qdrant、缓存等组件用Docker Compose一键拉起整个环境比手动装省事太多。而且热搜词里有“docker安装mysql8.0并使用”“docker安装redis主从”“docker compose”这些说明很多人在实际部署时确实需要这些操作指引。3. 核心细节解析记忆系统的关键组件与实操要点3.1 记忆的存储结构从原始对话到结构化记忆原始对话是一串非结构化的文本直接存进去检索效率很低。我通常的做法是做一个记忆提取管道把原始对话转成结构化的记忆条目。一个典型的记忆条目包含这些字段字段说明示例memory_id唯一标识mem_20250101_001user_id用户标识user_123session_id会话标识sess_456content记忆内容用户偏好喝美式咖啡不加糖memory_type记忆类型preference / fact / eventembedding向量表示[0.12, -0.34, ...]created_at创建时间2025-01-01T10:00:00Zlast_accessed最后访问时间2025-01-02T15:30:00Zaccess_count访问次数5importance重要性评分0.8这个结构看起来简单但每个字段都有讲究。memory_type决定了检索时的过滤策略embedding决定了语义检索的效果importance和access_count则用于记忆的淘汰和优先级排序。提取管道的工作流程一般是原始对话 - LLM提取 - 结构化条目 - 向量化 - 存入数据库。这里的关键是提取的prompt设计。我试过很多版本最后发现最有效的方式是让LLM输出JSON格式并且明确告诉它“只提取值得长期记住的信息忽略寒暄和临时性内容”。3.2 检索策略向量检索加关键词检索的混合方案记忆存进去之后怎么在需要的时候精准召回是另一个核心问题。纯向量检索的问题是它对精确匹配不敏感。比如用户说“我上次说的那个项目”向量检索可能召回一堆不相关的项目讨论但关键词检索能精准定位到“上次”和“项目”这两个词。我的经验是混合检索效果最好先用向量检索召回一批语义相关的记忆再用关键词检索补充精确匹配的结果最后用重排序模型比如bge-reranker做统一排序。实测下来混合检索的召回准确率比单一方案能提升20%到30%。具体参数上向量检索的top_k一般设20到50关键词检索的top_k设10到20重排序后取前5到10条注入prompt。这个数量需要根据模型上下文窗口和任务复杂度调整。如果任务很简单3条就够了如果任务复杂可能需要10条以上。注意检索到的记忆不要直接全部塞进prompt最好做一个摘要或压缩。我见过有人把20条记忆原封不动放进去结果模型反而被干扰了。3.3 MCP Server的实现要点把记忆系统做成MCP Server需要实现几个核心工具toolmemory_store存入一条新记忆memory_search根据query检索相关记忆memory_update更新已有记忆的内容或重要性memory_delete删除不再需要的记忆memory_list列出某个用户或会话的所有记忆每个工具都需要定义清晰的输入输出schema。比如memory_search的输入是query、user_id、top_k、memory_type等参数输出是记忆条目列表。这里有个坑MCP协议对参数类型有严格要求如果schema定义不严谨Agent调用时很容易报“provider rejected the request schema or tool payload”这类错误。热搜词里正好有这条说明不少人踩过这个坑。我的建议是schema定义尽量用基础类型避免嵌套过深。如果确实需要复杂结构用JSON字符串传参然后在Server端解析。3.4 Docker Compose编排一键拉起记忆系统一个完整的记忆系统通常需要这些组件PostgreSQL存结构化记忆条目Redis做缓存和会话状态管理Qdrant或Milvus存向量embeddingMCP Server记忆服务的核心逻辑Agent应用调用MCP Server的客户端用Docker Compose编排关键是处理好网络和依赖关系。我一般会建一个自定义网络让所有服务在同一个网络里用服务名互相访问。数据库服务要加healthcheck确保MCP Server启动时数据库已经就绪。version: 3.8 services: postgres: image: postgres:16 environment: POSTGRES_DB: hindsight POSTGRES_USER: hindsight POSTGRES_PASSWORD: hindsight123 volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: [CMD-SHELL, pg_isready -U hindsight] interval: 5s timeout: 5s retries: 5 networks: - hindsight-net qdrant: image: qdrant/qdrant:latest volumes: - qdrant_data:/qdrant/storage networks: - hindsight-net mcp-server: build: ./mcp-server depends_on: postgres: condition: service_healthy qdrant: condition: service_started environment: DATABASE_URL: postgresql://hindsight:hindsight123postgres:5432/hindsight QDRANT_URL: http://qdrant:6333 networks: - hindsight-net volumes: pgdata: qdrant_data: networks: hindsight-net: driver: bridge这个compose文件可以直接用但有几个细节要注意。PostgreSQL的healthcheck我用了pg_isready这个命令在postgres镜像里自带比用curl更可靠。Qdrant的存储卷一定要挂出来不然容器重启数据就没了。MCP Server的build上下文指向本地目录你需要确保Dockerfile写对了。4. 实操过程从零搭建一个带记忆的Agent4.1 环境准备与Docker安装如果你在Windows上Docker Desktop是最省事的选择。但热搜词里出现了“virtualization support not detected docker desktop failed to start because v”这条说明很多人遇到了虚拟化支持的问题。这个问题的根源通常是BIOS里没开启虚拟化或者Hyper-V和WSL2冲突。我的建议是Windows 11用户直接用WSL2后端在BIOS里开启Intel VT-x或AMD-V然后在“启用或关闭Windows功能”里勾选“虚拟机平台”和“适用于Linux的Windows子系统”。装完Docker Desktop之后在设置里确认“Use WSL 2 based engine”是勾选的。Linux用户直接用apt或yum装docker-ce就行记得把当前用户加到docker组里不然每次都要sudo。sudo usermod -aG docker $USER newgrp docker装完之后用docker run hello-world验证一下能跑通就说明环境没问题。4.2 数据库初始化与记忆表设计PostgreSQL启动之后需要建表。我一般用SQL migration来管理表结构这里给一个核心表的建表语句CREATE TABLE memories ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id VARCHAR(255) NOT NULL, session_id VARCHAR(255), content TEXT NOT NULL, memory_type VARCHAR(50) NOT NULL DEFAULT fact, importance FLOAT DEFAULT 0.5, access_count INT DEFAULT 0, created_at TIMESTAMPTZ DEFAULT NOW(), last_accessed TIMESTAMPTZ DEFAULT NOW(), metadata JSONB DEFAULT {} ); CREATE INDEX idx_memories_user_id ON memories(user_id); CREATE INDEX idx_memories_type ON memories(memory_type); CREATE INDEX idx_memories_created ON memories(created_at DESC);向量部分存在Qdrant里每个记忆条目对应一个pointpoint的id用memory的UUIDpayload里存user_id和memory_type用于过滤。Qdrant的collection创建参数需要根据embedding模型的维度来定。如果你用的是OpenAI的text-embedding-3-small维度是1536如果用bge-m3维度是1024。距离度量用Cosine因为文本embedding通常做归一化之后Cosine和点积等价。4.3 记忆提取管道的实现记忆提取是整条链路里最需要调优的部分。我的做法是用一个专门的LLM调用来做提取prompt大概长这样你是一个记忆提取助手。请从以下对话中提取值得长期记住的信息。 提取规则 1. 只提取用户的偏好、事实性信息、重要事件、决策结论 2. 忽略寒暄、确认、重复内容 3. 每条记忆独立成条不要合并无关信息 4. 输出JSON数组每个元素包含content、memory_type、importance三个字段 5. memory_type只能是preference、fact、event、decision之一 6. importance是0到1之间的浮点数越重要值越高 对话内容 {conversation} 请输出JSON这个prompt我迭代了七八个版本关键改进点在于明确列出memory_type的枚举值避免模型自由发挥要求输出JSON数组而不是单个对象方便批量处理加入importance评分后续检索时可以按重要性加权。提取出来的记忆先存PostgreSQL然后异步做embedding存入Qdrant。这里用异步是为了不阻塞主流程因为embedding调用通常有几百毫秒的延迟。4.4 MCP Server的核心代码MCP Server我用Python实现基于官方的mcp库。核心是定义tool和对应的处理函数。以memory_search为例from mcp.server import Server from mcp.types import Tool, TextContent import json app Server(hindsight-memory) app.list_tools() async def list_tools(): return [ Tool( namememory_search, description根据query检索相关记忆, inputSchema{ type: object, properties: { query: {type: string, description: 检索关键词}, user_id: {type: string, description: 用户标识}, top_k: {type: integer, default: 5}, memory_type: {type: string, enum: [preference, fact, event, decision]} }, required: [query, user_id] } ) ] app.call_tool() async def call_tool(name: str, arguments: dict): if name memory_search: results await search_memories( queryarguments[query], user_idarguments[user_id], top_karguments.get(top_k, 5), memory_typearguments.get(memory_type) ) return [TextContent(typetext, textjson.dumps(results, ensure_asciiFalse))]这里有个细节inputSchema里的required字段一定要写对不然Agent调用时可能传空参数导致报错。另外description要写清楚因为Agent是根据description来决定什么时候调用这个工具的。4.5 Agent端的接入与测试Agent端接入MCP Server不同框架方式不一样。如果你用的是Claude Desktop直接在配置文件里加mcp server的地址就行。如果用的是自己写的Agent需要实现MCP client的逻辑。测试的时候我建议分三步走第一步单独测试MCP Server的每个tool用curl或Python脚本直接调用确认功能正常。第二步测试Agent调用MCP的链路看Agent能不能正确选择tool并传对参数。第三步做端到端的场景测试。比如让Agent记住“用户喜欢喝美式咖啡”然后在新会话里问“我想喝点什么”看Agent能不能检索到这条记忆并给出正确回答。我实测下来最容易出问题的环节是第二步。Agent有时候会传错参数类型比如把top_k传成字符串。解决办法是在MCP Server端做参数校验和类型转换不要完全信任Agent传来的数据。5. 常见问题与排查技巧实录5.1 记忆检索不准确怎么办这是最常见的问题。表现是Agent检索到的记忆和当前对话不相关或者明明存了某条记忆却检索不到。排查思路分三层。第一层看存储确认记忆确实存进去了用SQL查一下数据库用Qdrant的API查一下向量。第二层看检索单独调用memory_search看返回结果是否合理。第三层看注入确认检索到的记忆确实被放进了prompt。如果存储没问题但检索不准大概率是embedding模型的问题。不同模型对中文的支持差异很大我试过text-embedding-ada-002、bge-m3、m3e最后发现bge-m3在中文场景下效果最稳。如果检索到了但Agent没用上可能是prompt里记忆的呈现方式有问题试试把记忆放在prompt靠前的位置或者加一句“请优先参考以下历史记忆”。5.2 Docker网络不通的排查热搜词里有“docker网络不通”这个我踩过好几次。典型表现是MCP Server连不上PostgreSQL报connection refused。排查步骤先用docker compose ps确认所有容器都在运行。然后进到MCP Server容器里用ping postgres测试网络连通性。如果不通检查compose文件里是不是在同一个network下。如果ping通但连不上端口检查PostgreSQL是不是真的在监听5432端口用docker compose logs postgres看日志。还有一个坑是防火墙。有些云服务器默认开了防火墙容器之间的通信可能被拦截。我遇到过一回最后发现是iptables规则的问题加了一条允许docker网段的规则就好了。5.3 MCP工具调用报schema错误“llm request failed: provider rejected the request schema or tool payload”这个错误通常是因为tool的inputSchema定义和实际传参不匹配。比如schema里定义top_k是integer但Agent传了字符串5。解决办法有两个一是在schema里用oneOf或anyOf允许多种类型二是在Server端做类型转换。我倾向于后者因为schema太复杂会影响Agent的理解。另外如果tool的description写得太模糊Agent可能传一些schema里没定义的参数。所以description里最好明确列出支持的参数并且说明哪些是必填的。5.4 记忆膨胀与性能下降跑了一段时间之后记忆库会越来越大检索速度变慢而且噪音增多。这个问题必须提前考虑。我的做法是加一个记忆淘汰机制。定期比如每天凌晨跑一个任务做三件事第一删除importance低于阈值且超过30天没被访问的记忆第二合并重复或高度相似的记忆第三对长期未访问但importance高的记忆做降权处理。阈值怎么定我一般设importance 0.3且access_count 0且created_at超过30天。这个参数可以根据实际数据量调整。如果记忆量不大可以放宽如果记忆量很大要收紧。5.5 常见问题速查表问题现象可能原因排查方法解决方案检索不到记忆embedding未生成或存储失败查Qdrant collection是否有数据检查embedding管道日志检索结果不相关embedding模型不适合中文人工评估top_k结果换bge-m3或m3e模型Agent不调用memory工具tool description不清晰看Agent的决策日志优化description加示例Docker容器启动失败端口冲突或依赖未就绪docker compose logs加healthcheck和depends_on记忆重复存储提取管道未去重查数据库重复content加唯一索引或相似度去重响应延迟高检索top_k太大看检索耗时日志降低top_k加缓存提示这张表是我在实际运维中总结的大部分问题都能覆盖。如果遇到表里没有的优先看日志90%的问题日志里都有线索。6. 记忆系统的扩展方向与个人经验6.1 从被动检索到主动记忆管理现在大部分记忆系统都是被动检索Agent需要的时候去查。但更高级的做法是主动记忆管理也就是Agent自己决定什么时候该记、什么时候该忘、什么时候该更新。这需要给Agent加一个“记忆反思”的环节。比如每轮对话结束后让LLM评估一下这轮对话里有没有值得记住的新信息如果有就主动调用memory_store。同时如果发现新信息和已有记忆冲突主动调用memory_update。我试过这个方案效果确实比被动检索好但成本也更高因为每轮都要多一次LLM调用。折中方案是只在特定条件下触发反思比如对话轮次超过5轮或者用户明确说了“记住”之类的关键词。6.2 多Agent共享记忆的挑战如果你的系统里有多个Agent它们之间的记忆共享是个复杂问题。最简单的做法是共用一个记忆库用user_id区分。但这样会有隐私和冲突问题。更合理的做法是分层每个Agent有自己的私有记忆同时有一个共享记忆层。私有记忆只对当前Agent可见共享记忆对所有Agent可见。写入共享记忆需要经过审批或冲突检测。这个方案实现起来不复杂就是在memories表里加一个scope字段检索时根据scope过滤。但策略设计需要仔细考虑不然容易出现记忆污染。6.3 我踩过的最大的坑最后分享一个我踩过的最大的坑。早期做记忆系统的时候我把所有检索到的记忆都直接拼到prompt里没有做任何压缩。结果有一次用户问了一个简单问题Agent检索到了20条记忆prompt一下子膨胀到8000 token模型响应变得很慢而且回答质量反而下降了。后来我改成检索到记忆后先用一个小模型做摘要压缩把20条记忆压缩成3到5条核心要点再注入prompt。这样既保留了关键信息又控制了token消耗。实测下来响应速度提升了40%回答准确率也更高了。这个经验让我意识到记忆系统的核心不是“存了多少”而是“在正确的时候用正确的方式取出正确的信息”。存储和检索只是手段最终目标是为Agent的决策提供有效支撑。另外一个小技巧在记忆的metadata里加一个source字段记录这条记忆是从哪次对话、哪个场景提取的。排查问题的时候可以顺着source回溯到原始对话非常有用。这个字段平时用不上但出问题的时候能省很多时间。
返回列表