尧图精选

Agent Memory 架构实战:基于 MCP 与 Docker 实现 LLM 长期记忆与回看能力

🕒 发布时间:2026/10/1 21:47:38 📁 来源:尧图网络
1. 从 hindsight 说起为什么 Agent Memory 是 LLM 落地的下一个关键战场第一次看到 hindsight 这个词我脑子里蹦出来的不是词典里的事后诸葛亮而是过去大半年在 Agent 项目里反复被同一个问题折磨的场景一个跑得好好的 LLM Agent聊到第 30 轮突然把用户三小时前说过的偏好忘得一干二净或者把已经确认过的订单号重新问了一遍。用户骂它智障我们做工程的心里清楚这不是模型笨是记忆架构没搭对。hindsight 这个项目标题放在 agent memory 这个语境下我理解它想解决的核心命题是让 Agent 具备回头看的能力——不是简单地把历史对话塞进 context window而是有选择地、有结构地、有策略地回看过去发生过什么并从中提取对当前决策有用的信息。这跟人类做决策的机制其实很像你开车变道之前不会把过去十年所有驾驶经历在脑子里过一遍而是瞬间调取上次这个路口有盲区这类高价值记忆。围绕这个标题热词里出现了 agent memory、LLM、MCP、Docker 这几个关键词还有一条很有意思的搜索词agent 存储 working memory。这几个词拼在一起基本勾勒出了当前 Agent 记忆系统的技术栈轮廓LLM 是推理内核MCP 是工具与上下文接入协议Docker 是部署与隔离手段working memory 是运行时状态管理的核心概念。而 hindsight 要做的就是在这套栈上补上长期记忆的检索与回看这一层。这篇文章我打算按实际做项目的思路来拆先讲清楚 Agent Memory 到底难在哪、hindsight 这类方案的设计取舍是什么再落到 MCP 协议怎么接、Docker 怎么部署、working memory 怎么设计最后把我踩过的坑和排查经验整理出来。适合正在做 LLM Agent 落地、被记忆问题卡住的工程师也适合想理解 Agent 记忆架构全貌的技术负责人。看完你应该能自己搭一套带回看能力的记忆系统而不是只会调 API。2. Agent Memory 的核心难点与 hindsight 的设计取舍2.1 为什么把历史全塞进 context是最蠢也最常见的做法我见过太多项目Agent 记忆的实现就是messages.append(...)然后把整个 list 丢给模型。前几轮没问题到第 20 轮开始 token 爆炸到第 50 轮要么超限报错要么成本高到老板找你谈话。更致命的是信噪比崩塌历史里 90% 是寒暄、确认、重复真正有价值的决策依据被淹没模型反而更容易抓错重点。这里有个常被忽略的量化问题。假设每轮对话平均 200 token50 轮就是 10000 token 的纯历史。如果模型 context 是 128k看起来还能撑但你要留出系统提示、工具定义、当前推理的空间实际可用历史预算可能只有 30k 左右。而且 token 越多推理延迟和成本是线性甚至超线性增长的。我实测过一个客服 Agent把历史从 10 轮扩到 40 轮首 token 延迟从 1.2s 涨到 3.8s成本翻了近 4 倍但任务成功率只提升了 6 个百分点——投入产出比极差。所以 hindsight 这类方案的第一层取舍就很清楚了记忆不能靠堆要靠检索。把历史存到外部存储需要的时候按相关性召回这才是正路。2.2 Working Memory 与 Long-term Memory 的分层设计热词里那条 agent 存储 working memory 其实点到了关键。我习惯把 Agent 记忆分成三层这个分层在 hindsight 这类项目里基本是标配层级存储内容生命周期典型实现Working Memory当前任务状态、临时变量、最近几轮对话单次会话内存 / RedisEpisodic Memory历史会话摘要、关键事件跨会话可衰减向量库 关系库Semantic Memory提炼后的知识、用户画像、偏好长期需更新结构化存储 向量索引Working memory 是手边的工作台必须快、必须小、必须结构化。我一般会把它设计成一个显式的状态对象而不是一堆散落的对话文本。比如working_memory { task_id: order_20240513_001, current_goal: 帮用户完成退款申请, confirmed_facts: { order_id: A12345, reason: 商品破损, user_preference: 希望原路退回 }, pending_questions: [退款金额确认], recent_turns: [...] # 只保留最近 3-5 轮 }这样设计的好处是每次调 LLM 时working memory 以结构化 JSON 注入token 占用可控模型也更容易抓住重点。而 episodic 和 semantic 层则通过检索按需注入这就是 hindsight 回看能力的落点。2.3 检索策略向量、关键词还是混合说到回看绕不开检索。纯向量检索embedding 相似度是主流但它有个坑语义相似不等于决策相关。用户说我上次那个订单向量检索可能召回一堆订单相关的历史但真正相关的是那个指代的具体订单。这时候关键词和元数据过滤就很重要。我现在的做法是混合检索先用元数据时间范围、会话 ID、实体类型做粗筛再用向量做精排最后用 LLM 做一次相关性重排rerank。这套流程在 hindsight 这类项目里通常叫 retrieve-then-rerank。实测下来相比纯向量混合检索在指代消解类查询上的命中率能提升 30% 以上。提示rerank 这一步别省。我试过用一个小模型比如 1B 级别的专门做相关性打分成本很低但能把明显不相关的召回结果过滤掉对最终回答质量提升很明显。2.4 记忆的写入与遗忘比检索更容易被忽视大家都在聊怎么读记忆很少有人认真设计怎么写和怎么忘。我的经验是写入策略决定了记忆系统的上限。如果什么都往里塞检索质量必然下降如果塞得太少又会出现该记的没记住。我一般会设几个写入触发条件任务完成时写入 episodic 摘要用户明确表达偏好时写入 semantic检测到重复模式时比如用户第三次问同类问题触发知识提炼。遗忘机制则用时间衰减 访问频率长期不被召回的记忆降低权重最终归档或删除。这套逻辑听起来复杂但用 hindsight 的思路实现起来其实就是几个定时任务加权重计算。3. MCP 协议接入让记忆系统成为 Agent 的标准外设3.1 MCP 到底是什么为什么它和记忆系统天然契合热词里有人问 mcp 是软件协议 硬件协议那个概念叫什么来着这个问题其实挺典型。MCPModel Context Protocol是一个软件层的通信协议你可以把它类比成 USB-CUSB-C 定义了设备怎么插、怎么传数据MCP 定义了 LLM 应用怎么和外部工具、数据源通信。它不是硬件协议硬件协议那是 PCIe、I2C 那一类。MCP 和记忆系统为什么契合因为记忆本质上就是 Agent 的一个外设——它需要被查询、被写入、被更新。把记忆系统封装成一个 MCP ServerAgent 就能通过标准接口调用它不用把记忆逻辑硬编码进 Agent 主流程。这个解耦带来的好处是记忆系统可以独立部署、独立升级、独立扩展Agent 换个框架也不用重写记忆层。3.2 把 hindsight 记忆层封装成 MCP Server 的实操我以 Python 为例用官方 SDK 搭一个最小可用的记忆 MCP Server。核心是暴露三个工具store_memory、retrieve_memory、update_working_memory。from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent import json app Server(hindsight-memory) app.list_tools() async def list_tools(): return [ Tool( namestore_memory, description存储一条记忆到长期记忆库, inputSchema{ type: object, properties: { content: {type: string}, memory_type: {type: string, enum: [episodic, semantic]}, metadata: {type: object} }, required: [content, memory_type] } ), Tool( nameretrieve_memory, description根据查询检索相关记忆, inputSchema{ type: object, properties: { query: {type: string}, top_k: {type: integer, default: 5} }, required: [query] } ) ] app.call_tool() async def call_tool(name: str, arguments: dict): if name store_memory: # 实际写入向量库 关系库 result await memory_store.write(arguments) return [TextContent(typetext, textjson.dumps(result))] elif name retrieve_memory: result await memory_store.retrieve( arguments[query], arguments.get(top_k, 5) ) return [TextContent(typetext, textjson.dumps(result))] async def main(): async with stdio_server() as (read, write): await app.run(read, write, app.create_initialization_options())这段代码的关键点在于工具描述要写得让 LLM 能理解什么时候该调。我见过很多 MCP Server 功能没问题但 description 写得太技术化模型根本不知道该在什么场景调用。store_memory的描述里最好加上当用户表达偏好、确认重要事实、或任务完成时调用这样模型才会在正确时机触发。3.3 MCP 工具设计中的 token 经济学热词里有一条 llm的token三个点key我是谁、query我在找什么、value我能提供什么这个总结很精辟。放到 MCP 工具设计上就是每个工具的定义都要回答这三个问题这个工具是谁能力边界、什么时候用触发条件、能提供什么返回什么。工具定义本身是占 token 的。一个 MCP Server 暴露 10 个工具每个工具 schema 平均 150 token就是 1500 token 的固定开销每轮对话都要带上。所以我的原则是工具数量能少则少参数能简则简。记忆系统其实三个工具就够写、读、更新。别搞什么store_episodic_with_metadata_and_tags这种又长又细的接口模型反而容易选错。注意MCP 工具的返回内容也要控制大小。我踩过一次坑retrieve_memory一次返回了 20 条完整记忆每条 500 字直接把 context 撑爆。后来改成返回摘要 ID需要详情再单独取token 占用降了 80%。3.4 与主流 Agent 框架的对接方式MCP 的好处是框架无关。不管你是用 LangChain、LlamaIndex 还是自己写的 Agent loop只要支持 MCP client就能接上这个记忆 Server。我实测过几种接法Claude Desktop / 支持 MCP 的 IDE直接配置 server 命令即可最省事。自研 Agent用 MCP Python SDK 的 client 端通过 stdio 或 SSE 连接。多 Agent 共享记忆把记忆 Server 部署成独立服务多个 Agent 通过 SSE 连同一个 Server实现记忆共享。这里有个细节stdio 模式适合本地单机SSE 模式适合分布式。如果你要做多 Agent 协作一定要用 SSE否则每个 Agent 一个独立记忆库共享就无从谈起。4. Docker 部署实战把记忆系统跑成稳定服务4.1 为什么记忆系统值得单独容器化有人会问记忆逻辑就几百行代码为什么要上 Docker我的理由很直接记忆系统是有状态的而且状态很贵。向量库、关系库、缓存这些东西一旦跑起来迁移和重建成本很高。容器化能保证环境一致、依赖隔离、升级可控。而且记忆系统往往需要独立扩缩容——Agent 可以多开但记忆库不能随便多开否则数据一致性就崩了。热词里 docker安装、docker desktop安装教程、windows安装docker 出现频率很高说明很多读者卡在环境这一步。我下面按 Linux 和 Windows 两条线讲重点讲记忆系统部署时容易踩的坑。4.2 用 docker-compose 编排记忆系统全栈一个典型的 hindsight 记忆系统栈包括向量库Qdrant 或 Milvus、关系库PostgreSQL、缓存Redis、记忆服务本身。用 docker-compose 编排最省心version: 3.8 services: qdrant: image: qdrant/qdrant:latest ports: - 6333:6333 volumes: - ./data/qdrant:/qdrant/storage restart: unless-stopped postgres: image: postgres:16 environment: POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: hindsight ports: - 5432:5432 volumes: - ./data/postgres:/var/lib/postgresql/data restart: unless-stopped redis: image: redis:7-alpine ports: - 6379:6379 volumes: - ./data/redis:/data command: redis-server --appendonly yes restart: unless-stopped memory-service: build: ./memory-service ports: - 8080:8080 environment: QDRANT_URL: http://qdrant:6333 DATABASE_URL: postgresql://postgres:${DB_PASSWORD}postgres:5432/hindsight REDIS_URL: redis://redis:6379 depends_on: - qdrant - postgres - redis restart: unless-stopped几个关键点数据卷一定要挂出来否则容器一删记忆全没restart 策略用 unless-stopped保证服务自愈依赖顺序用 depends_on但注意 depends_on 只保证启动顺序不保证服务就绪记忆服务里要做重试逻辑。4.3 Windows 下 Docker Desktop 的常见启动失败排查热词里 virtualization support not detected docker desktop failed to start because v 这条我太熟了这是 Windows 用户最高频的报错。根因通常是 BIOS 里虚拟化没开或者 Hyper-V / WSL2 没配好。排查顺序进 BIOS 确认 Intel VT-x 或 AMD-V 已启用。任务管理器 → 性能 → CPU看虚拟化是否为已启用。确认 WSL2 已安装wsl --install然后wsl --set-default-version 2。Docker Desktop 设置里确认使用 WSL2 后端而不是 Hyper-V。如果还不行检查是否装了其他虚拟化软件如某些安卓模拟器抢占了资源。提示Windows 家庭版没有 Hyper-V必须走 WSL2 路线。我见过有人折腾半天 Hyper-V结果发现系统版本根本不支持白忙活。4.4 容器网络不通的经典问题热词里 docker网络不通 也是高频痛点。记忆系统涉及多个容器互相通信网络问题特别常见。我的排查清单现象可能原因解决容器间 ping 不通不在同一 network用 compose 默认网络或自定义 network宿主机访问不了容器端口端口没映射检查 ports 配置容器访问外网失败DNS 配置问题指定 dns 或检查 daemon.json时通时不通服务未就绪加 healthcheck 重试我一般会在 compose 里给关键服务加 healthcheck记忆服务启动时轮询依赖服务直到就绪这样能避免 90% 的启动顺序类问题。5. 记忆系统的实操流程与关键环节实现5.1 从零搭建一套带回看能力的记忆流程我把整个流程拆成五个环节每个环节都有明确的输入输出方便你对照实现。环节一会话初始化。Agent 启动时从 semantic memory 加载用户画像和偏好注入 system prompt。这一步决定了 Agent 的初始认知。环节二working memory 维护。每轮对话后更新 working memory 的状态对象。这里的关键是增量更新不要每轮重建。我一般用一个update_working_memory函数只改变化的部分。环节三记忆检索触发。不是每轮都检索而是设触发条件用户提到上次之前那个等指代词时或当前任务需要历史信息时。触发后走混合检索流程。环节四记忆写入。任务完成、用户表达偏好、检测到重要事实时写入。写入前做一次去重和摘要避免冗余。环节五记忆衰减与整理。定时任务扫描长期未访问的记忆降权或归档对高频访问的记忆做摘要提炼升级为 semantic memory。5.2 检索参数的计算与选择检索这块有几个参数需要拍脑袋定我给出我的经验值top_k初筛取 20rerank 后取 5。太少漏召回太多噪声大。相似度阈值向量相似度低于 0.7 的直接丢弃别舍不得。时间衰减系数我一般用半衰期 30 天即 30 天前的记忆权重减半。rerank 模型小模型即可1B 级别足够别用大模型成本不划算。这些参数没有绝对最优要根据你的业务场景调。客服场景记忆更新快半衰期可以短到 7 天个人助理场景记忆更持久可以到 90 天。5.3 一次完整的回看调用实录假设用户说帮我把上次那个破损的订单退款。 完整流程Agent 检测到上次那个指代词触发检索。检索 query 用当前对话 指代词上下文构造。混合检索元数据筛订单类型 时间范围近 30 天向量精排rerank 取 top 5。召回结果注入 working memory模型据此确认订单号。用户确认后执行退款写入 episodic memory。更新 semantic memory该用户有商品破损退款偏好记录。这套流程跑通后Agent 的记忆力会有质的提升。我实测过一个售后 Agent接入这套记忆系统后重复询问率从 35% 降到 8%用户满意度提升明显。5.4 记忆质量评估怎么知道你的记忆系统好不好很多人搭完记忆系统就完事了从不评估。我建议至少跟踪三个指标召回命中率检索到的记忆里真正被用上的比例。重复询问率Agent 重复问已知信息的频率越低越好。记忆利用率写入的记忆被召回的比例太低说明写入策略有问题。这三个指标用日志就能统计不需要复杂工具。我一般每周看一次发现异常就调参数。6. 常见问题与排查技巧实录6.1 记忆检索召回不相关怎么办这是最高频的问题。排查顺序先看 embedding 模型是否适合你的语言和领域中文场景别用纯英文模型再看 chunk 切分是否合理切太碎丢上下文切太大噪声多最后看是否需要加 rerank。我遇到过最离谱的 case 是 embedding 模型版本和索引时不一致导致检索结果完全随机换回一致版本就好了。6.2 记忆写入过多导致检索质量下降写入策略太宽松的典型症状。解决方法是加写入前的 LLM 判断这条信息值不值得长期记住我一般用一个便宜的模型做这个判断prompt 大意是这条信息在未来对话中是否可能被再次需要。实测能过滤掉 60% 以上的冗余写入。6.3 MCP Server 连接超时或断连stdio 模式一般不会断SSE 模式容易。排查检查网络稳定性、Server 是否有心跳机制、client 是否有重连逻辑。我建议 SSE 模式一定要加心跳和自动重连否则长会话跑到一半断连用户体验极差。6.4 Docker 容器内存溢出向量库和 LLM 推理都吃内存。我一般给 Qdrant 限 4GPostgreSQL 限 2G记忆服务限 2G留足余量。用deploy.resources.limits配置避免单个容器把宿主机吃干。6.5 常见问题速查表问题根因快速解决检索结果不相关embedding 不匹配 / 无 rerank换模型 加 rerank记忆越用越慢索引未优化 / 数据量过大加索引 定期归档容器启动失败虚拟化未开 / 端口冲突查 BIOS 换端口MCP 工具不被调用description 不清重写工具描述记忆丢失数据卷未挂载检查 volumes 配置6.6 几条血泪经验第一别在 working memory 里存大对象。我见过有人把整个文档塞进 working memory每轮都注入token 直接爆炸。working memory 只放状态和指针大内容放外部存储按需取。第二记忆系统要能降级。检索服务挂了Agent 至少还能用最近几轮对话跑不能整个崩掉。我一般会做 fallback检索失败就用最近 N 轮历史兜底。第三版本化你的记忆 schema。记忆结构会随业务演进加字段是常事。提前设计好版本字段迁移时能省大量事。第四测试要用真实长会话。短会话测不出记忆问题一定要构造 50 轮以上的真实场景压测才能暴露召回、衰减、一致性这些深层问题。这套东西我从零搭过三遍每遍都在前一遍的坑上改进。hindsight 这个方向的价值在于它把回看从一个模糊的愿望变成了可工程化的架构。记忆不是把历史存下来就完事而是要设计好怎么存、怎么取、怎么忘、怎么用。把这四件事想清楚你的 Agent 才算真正有了记性。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →