资讯动态

Agent Memory 实战:基于 MCP 与 Docker 构建可持久化记忆系统

发布时间:2026/9/30 12:22:20 来源:尧图企业网站定制
1. 从 hindsight 说起为什么 Agent Memory 是 LLM 落地的下一个关键战场第一次看到 hindsight 这个词我脑子里蹦出来的不是词典释义而是过去两年做 LLM 应用时反复踩过的同一个坑模型聊到第三轮就开始失忆前面确认过的需求、约束、偏好转头就忘得一干二净。你让它帮你改一段代码第一轮说用 Python 3.11第二轮它给你写了个 3.8 的语法你告诉它这个项目不要引入新依赖它下一轮就热情地推荐你 pip install 一堆东西。这不是模型笨是它压根没有记忆这个概念——每次请求对它来说都是全新的世界。hindsight这个标题加上agent memory、LLM、MCP、Docker这几个热搜词指向的其实是一个非常具体的技术命题如何给基于 LLM 的 Agent 构建一套可持久化、可检索、可演进的记忆系统。hindsight 本意是事后之明、后见之明放在 Agent 语境里它精准地描述了记忆系统的核心价值——让 Agent 能够回头看把过去发生过的对话、决策、工具调用结果沉淀下来在需要的时候重新调取而不是每次都从零开始。这套东西解决的是什么问题简单说就是三件事跨会话的上下文延续今天聊的项目明天还能接着聊、长期知识的积累与复用踩过的坑不用再踩第二遍、多 Agent 之间的状态共享一个 Agent 学到的另一个也能用。适合谁来参考如果你正在做 LLM 应用、Agent 框架、RAG 系统或者单纯想让自己的 AI 助手记住点事那这套思路你迟早要用上。我下面会从设计思路、核心机制、实操落地到问题排查把hindsight这类 Agent Memory 系统拆开讲透尽量让你看完能直接动手搭一个。2. Agent Memory 的整体设计与思路拆解2.1 为什么记忆不能简单等同于把历史对话塞进 Prompt很多人第一反应是记忆嘛把之前的对话记录拼到 prompt 里不就行了我一开始也这么干过结果很快撞墙。原因有三个而且一个比一个致命。第一是Token 成本。LLM 的上下文窗口再大也是有限的而且是要花钱的。你把 50 轮对话全塞进去每轮请求都在为这 50 轮付费成本线性膨胀。更别说很多模型对超长上下文的注意力会衰减塞得越多模型反而越抓不住重点。第二是信噪比问题。历史对话里 90% 是寒暄、试错、废弃方案真正有价值的可能就那两三句关键约束。全量塞进去等于让模型在一堆噪音里捞针捞不捞得到全看运气。第三是一致性与冲突。用户第一轮说用 MySQL第五轮改口说还是用 PostgreSQL 吧。你把两句话都塞进去模型到底听谁的没有一套机制去处理记忆的更新与失效历史记录反而会变成干扰源。所以hindsight这类系统的核心设计哲学不是存更多而是存得对、取得准、更新得及时。这三点决定了整个架构的走向。2.2 三层记忆架构Working Memory、Episodic Memory、Semantic Memory参考认知科学里对人类记忆的分类Agent Memory 通常也拆成三层这个分层不是学术炫技而是每一层的读写频率、存储介质、检索方式都完全不同混在一起做必然出问题。Working Memory工作记忆对应的是当前会话的短期上下文生命周期就是一次会话读写极其频繁通常直接放在内存里或者 Redis 这类高速存储中。它的作用是维持当前任务的连贯性比如用户正在让我改一个 React 组件这个状态。Episodic Memory情景记忆记录的是发生过什么比如某次对话的完整摘要、某次工具调用的输入输出、某个决策的来龙去脉。它按时间线组织生命周期是中期到长期通常落库存储。检索时往往按时间或会话 ID 来查。Semantic Memory语义记忆是最抽象也最有价值的一层它存的是提炼后的知识比如这个用户偏好简洁的代码风格、这个项目的技术栈是 FastAPI PostgreSQL。它不绑定具体某次对话而是从多次交互中归纳出来的稳定事实。检索时靠向量相似度或结构化查询。我实测下来三层分开管理是这套系统能不能用的分水岭。早期我把所有东西都塞进一个向量库结果检索出来的东西要么太碎全是对话片段要么太泛提炼过度丢了细节根本没法用。分层之后工作记忆保证当下流畅情景记忆保证可追溯语义记忆保证越用越聪明。2.3 为什么选 MCP 作为记忆的接入协议热搜词里MCP出现频率极高这不是偶然。MCPModel Context Protocol本质上是一套标准化的模型与外部能力对接的协议它把工具、资源、提示模板统一成一套接口规范。把记忆系统做成一个 MCP Server好处非常直接解耦记忆逻辑独立成一个服务Agent 框架换不换、模型换不换记忆层都不用动。复用任何支持 MCP 的客户端都能接上这套记忆不用为每个框架重写一遍。标准化读写记忆、检索记忆、更新记忆都通过统一的工具调用暴露出去Agent 只需要知道我有个 memory 工具可以调。我个人的判断是MCP 正在成为 Agent 生态的USB 接口。以前每个框架都有自己的插件体系现在大家慢慢往 MCP 上收敛。把记忆做成 MCP Server等于一次性投资长期受益。这也是为什么hindsight这类项目大概率会走 MCP 路线——它天然适合被多个 Agent 共享。2.4 Docker 化部署为什么记忆服务必须容器化Docker、docker desktop、docker安装这些词高频出现说明大家最关心的还是怎么跑起来。记忆服务容器化不是跟风而是有实打实的理由记忆服务通常依赖向量数据库比如 Qdrant、Milvus、关系库PostgreSQL、缓存Redis本地裸装这一堆东西版本冲突、端口占用、环境差异能把你折磨到怀疑人生。Docker Compose 一把梭所有依赖声明在docker-compose.yml里换台机器docker compose up -d就能复现这对需要长期运行的记忆服务来说太重要了。而且记忆服务往往是常驻后台的容器化之后重启策略、健康检查、日志收集都有标准做法运维成本大幅下降。我踩过的坑是早期把记忆服务直接跑在宿主机上某次系统更新把 Python 环境搞崩了记忆库连接全断排查了半天。容器化之后这类问题基本绝迹。3. 核心细节解析与实操要点3.1 记忆的写入Token 三元组模型Key / Query / Value热搜里有一条特别有意思llm的token三个点key我是谁、query我在找什么、value我能提供什么。这其实点出了记忆系统里最核心的数据结构设计——每条记忆都应该能被三个维度描述。我把它理解成一张记忆卡片维度含义举例Key我是谁这条记忆的标识与归属用户ID、会话ID、项目名、标签Query我在找什么这条记忆会在什么场景下被检索用户的技术栈偏好、项目的部署方式Value我能提供什么记忆的实际内容用户偏好 FastAPI PostgreSQL这个三元组模型的价值在于它让写入和检索有了共同的坐标系。写入时你按这三个维度组织数据检索时你按 Query 维度去匹配命中后按 Key 维度做过滤最后拿到 Value。没有这个结构记忆就是一堆散乱的文本块检索全靠向量相似度碰运气。实操上我建议每条记忆写入时至少带上来源会话 ID、时间戳、记忆类型working/episodic/semantic、置信度。置信度这个字段很多人会忽略但它极其重要——用户随口一提的偏好和反复强调的约束权重应该不一样。我一般给反复出现的记忆打高置信度单次提及的打低置信度检索时按置信度加权排序。3.2 记忆的检索向量检索 结构化过滤的混合策略纯向量检索的问题在于语义相近但事实无关。你搜用户喜欢什么数据库它可能给你返回一段用户提到数据库连接超时的对话语义上确实相关但完全不是你要的。我的做法是混合检索先用结构化条件Key 维度缩小范围比如只查这个用户、这个项目的记忆再在这个子集里做向量相似度排序最后用置信度和时间衰减做重排。时间衰减的意思是越久远的记忆权重越低除非它被反复命中说明是稳定事实。具体参数上我一般这样设向量召回 Top 20结构化过滤后保留 Top 10重排后取 Top 3 注入上下文。为什么是 3因为实测下来注入超过 3 条记忆模型反而容易分心把不相关的记忆也硬往回答里塞。少而准永远比多而杂强。3.3 记忆的更新与失效处理用户改主意了这是最容易被忽略、但最影响体验的一环。用户改主意了旧记忆怎么办我的策略是不删除而是标记失效。具体做法是给每条记忆加一个status字段active / superseded / expired和一个superseded_by指针。当检测到新记忆与旧记忆冲突时比如用户明确说之前说的不算改成 XXX把旧记忆标记为 superseded指向新记忆。检索时默认只查 active 的但保留追溯能力。为什么不直接删因为记忆的价值往往在为什么变里。用户从 MySQL 改到 PostgreSQL可能因为某个具体的技术原因这个原因本身就是有价值的语义记忆。直接删掉下次遇到类似场景你又得重新问一遍。注意冲突检测不要做得太激进。我早期搞了个自动冲突检测结果用户只是随口提了句也可以用 Redis系统就把用 PostgreSQL标记失效了闹了大笑话。后来改成只有用户明确表达变更意图时才触发失效稳定多了。3.4 记忆的压缩与摘要别让情景记忆无限膨胀情景记忆如果每条对话都完整存几个月下来就是灾难。我的做法是分级压缩原始对话保留 7 天之后压缩成摘要。摘要保留 90 天之后提炼成语义记忆或丢弃。语义记忆长期保留但定期做去重和合并。压缩用 LLM 来做prompt 大概是把以下对话压缩成不超过 100 字的关键信息只保留决策、约束、结论丢弃寒暄和试错过程。这个 prompt 我调了很多版核心是明确告诉模型丢什么、留什么不然它会把废话也当关键信息留着。4. 实操过程与核心环节实现4.1 环境准备Docker 与依赖服务编排先把地基打好。我用的是一台 4C8G 的 Linux 机器Windows 用户用 Docker Desktop 也行但要注意开启虚拟化支持——热搜里virtualization support not detected docker desktop failed to start这个报错太常见了本质是 BIOS 里虚拟化没开或者和 Hyper-V/WSL2 冲突。Windows 上我建议直接用 WSL2 后端比 Hyper-V 省心。docker-compose.yml我精简到最核心的几个服务version: 3.9 services: postgres: image: postgres:16 environment: POSTGRES_PASSWORD: memory_pass POSTGRES_DB: agent_memory ports: - 5432:5432 volumes: - pg_data:/var/lib/postgresql/data healthcheck: test: [CMD-SHELL, pg_isready -U postgres] interval: 10s retries: 5 qdrant: image: qdrant/qdrant:latest ports: - 6333:6333 volumes: - qdrant_data:/qdrant/storage redis: image: redis:7-alpine ports: - 6379:6379 volumes: pg_data: qdrant_data:PostgreSQL 存结构化的记忆元数据Key、时间、状态、置信度Qdrant 存向量Redis 做工作记忆的缓存。三个服务各司其职别想着用一个数据库全搞定那是给自己找麻烦。启动就一句docker compose up -d然后docker compose ps确认三个服务都 healthy。如果 qdrant 起不来八成是端口冲突lsof -i:6333查一下。4.2 记忆服务的核心接口设计记忆服务对外暴露的接口我设计成四个核心操作对应 CRUD 的变体# 写入记忆 def write_memory(user_id, session_id, mem_type, key, query, value, confidence0.5): # 1. 生成 value 的向量 vector embed(value) # 2. 结构化元数据落 PostgreSQL # 3. 向量落 Qdrantpayload 带上 user_id/session_id/mem_type # 4. 工作记忆同步写 Redis设 TTL pass # 检索记忆 def retrieve_memory(user_id, query_text, top_k3, mem_typesNone): # 1. query_text 向量化 # 2. Qdrant 检索filter 按 user_id statusactive # 3. 按置信度和时间衰减重排 # 4. 返回 top_k pass # 更新记忆状态 def supersede_memory(old_mem_id, new_mem_id): # 标记旧记忆 superseded指向新记忆 pass # 压缩情景记忆 def compress_episodic(user_id, before_date): # 拉取旧对话LLM 摘要写入语义记忆 pass这里有个关键细节embedding 模型的选择。我试过好几个最后稳定用 BGE-M3中文英文都能打维度 1024检索效果和速度平衡得不错。别用 OpenAI 的 embedding一是贵二是数据出境有顾虑本地跑 BGE 完全够用。4.3 接入 MCP把记忆暴露给 Agent记忆服务本身跑起来了接下来是让 Agent 能用上。MCP Server 的实现我用 Python 的mcpSDK核心是把上面四个操作注册成 MCP 工具from mcp.server import Server from mcp.types import Tool, TextContent server Server(hindsight-memory) server.list_tools() async def list_tools(): return [ Tool( namewrite_memory, description写入一条记忆。key 标识归属query 描述检索场景value 是内容, inputSchema{ type: object, properties: { key: {type: string}, query: {type: string}, value: {type: string}, mem_type: {type: string, enum: [working, episodic, semantic]} }, required: [key, query, value] } ), Tool( nameretrieve_memory, description根据当前上下文检索相关记忆, inputSchema{ type: object, properties: { query_text: {type: string}, top_k: {type: integer, default: 3} }, required: [query_text] } ) ]Agent 侧只需要在每轮对话开始前调一次retrieve_memory把结果拼进 system prompt对话结束后调write_memory把关键信息存下来。这个读-用-写的循环就是hindsight的核心工作流。提示MCP 连接配置里token 这类凭证一定要走环境变量别硬编码在配置文件里。我见过有人把 token 直接写进mcp.json然后提交到 Git那基本等于把钥匙插在门上。4.4 一个完整的记忆生命周期演示假设用户第一次对话说我在做一个数据分析项目用 Python数据量大概百万级希望处理速度快一点。写入阶段Agent 调用write_memory写入一条语义记忆Key:user_123/project_data_analysisQuery: 用户的技术栈和性能偏好Value: 数据分析项目Python百万级数据重视处理速度Confidence: 0.6第二次对话用户问帮我写个读取数据的函数。 Agent 先调retrieve_memoryquery_text 是读取数据 函数 技术栈检索到上面那条记忆于是生成的代码直接用 pandas 向量化操作而不是慢吞吞的 for 循环。第三次对话用户说算了数据量涨到千万级了pandas 扛不住。 Agent 写入新记忆同时把旧记忆标记 superseded新 Value: 数据分析项目Python千万级数据pandas 性能不足考虑 Polars 或 DuckDB旧记忆 status 改为 superseded一个月后用户回来问我那个数据分析项目用啥来着 Agent 检索到最新的 active 记忆直接答出 Polars/DuckDB 方案还附带一句之前 pandas 在千万级数据上性能不够。这就是 hindsight 的价值——它记得的不只是结论还有结论背后的演进过程。5. 常见问题与排查技巧实录5.1 记忆检索答非所问的排查思路这是最高频的问题。检索出来的记忆和当前问题不相关导致模型被带偏。排查按这个顺序走现象可能原因排查方法解决检索结果语义相近但无关纯向量检索无过滤检查是否带 user_id/status 过滤加结构化过滤检索不到任何记忆向量维度不匹配对比写入和查询的 embedding 维度统一 embedding 模型检索到过期记忆status 过滤失效查 Qdrant payload 里的 status 字段修复 supersede 逻辑检索结果太泛记忆粒度过粗看写入时的 value 长度拆分细粒度记忆我踩过最深的一个坑是embedding 模型换了但没重新索引。写入时用的是模型 A查询时换成了模型 B向量空间完全对不上检索结果全是乱的。换 embedding 模型必须全量重建索引这个没有捷径。5.2 Docker 环境下的典型故障docker网络不通、docker安装mysql8.0这类问题在记忆服务部署里也很常见。我整理几个高频的容器间网络不通默认 bridge 网络下容器之间要用服务名互相访问不是 localhost。记忆服务连 PostgreSQLhost 要写postgres而不是127.0.0.1。这个坑我踩过不止一次。数据卷权限问题PostgreSQL 容器启动报权限错误多半是宿主机挂载目录的属主不对。chown -R 999:999 ./pg_data解决999 是 postgres 容器内的 uid。内存不足导致容器被杀向量库吃内存4G 机器跑 Qdrant PostgreSQL Redis 有点紧。docker stats看一下必要时给 Qdrant 加内存限制或者换更轻量的向量库。5.3 记忆膨胀与性能衰减跑了一两个月之后你会发现检索越来越慢记忆库越来越大。这是必然的关键是怎么控制。我的做法是定期跑一个记忆整理任务每周一次把低置信度、长期未被命中的记忆归档不是删除是移到冷存储把重复的语义记忆合并。整理任务本身也用 LLM 来做prompt 是以下记忆哪些是重复或过时的给出合并建议。实测下来整理之后检索延迟能从 800ms 降到 200ms 左右效果立竿见影。记忆系统不是建好就不管的它需要像花园一样定期修剪。5.4 几个我踩过的坑直接抄作业别在写入时做太多 LLM 调用。我早期每条记忆写入都让 LLM 判断这是不是值得记结果写入延迟高得离谱。后来改成规则优先关键词命中直接记LLM 只做兜底判断快多了。工作记忆的 TTL 别设太长。我一开始设 24 小时结果第二天用户回来Agent 还记着昨天的临时状态答非所问。改成 2 小时清爽多了。检索结果一定要做去重。向量检索很容易返回几条内容高度相似的记忆注入上下文纯属浪费 token。用简单的文本相似度去重就行。给记忆加来源字段。哪条记忆是从哪次对话来的出问题时能追溯。这个字段平时没用排查时救命。6. 记忆系统的演进方向与个人实践体会hindsight这类系统的想象空间其实比记住对话大得多。往深了做它可以变成 Agent 的长期人格——不只是记住事实还记住偏好、风格、甚至决策模式。热搜里提到的a-memguard这类主动防御框架思路就是把记忆系统当成一个需要保护、需要审计的资产防止记忆被污染或滥用。这个方向我觉得很对记忆一旦成为 Agent 的核心资产它的安全性、一致性、可解释性就都得认真对待。另一个我比较看好的方向是记忆的跨 Agent 共享。现在每个 Agent 各记各的其实很多记忆是通用的比如用户偏好、项目背景。通过 MCP 把记忆做成共享服务多个 Agent 接同一个记忆后端理论上能实现一个 Agent 学会所有 Agent 都会。当然这里有一致性和权限的坑要填但方向是对的。我自己跑这套系统小半年最大的体会是记忆系统的难点从来不在存而在取和舍。存谁都会存难的是在正确的时机取出正确的那几条以及果断地舍弃那些不再有价值的。这跟人脑其实一模一样——记性好的人不是记得多是记得准、忘得对。做 Agent Memory 做久了你会发现自己对什么信息值得记住这件事的判断力也在提升这算是意外收获。最后分享一个我一直在用的小技巧给记忆系统加一个手动干预入口。自动记忆再聪明也会有判断失误的时候留一个让用户能手动查看、编辑、删除记忆的界面既是对用户的尊重也是系统自我修正的重要途径。我那个界面做得很简陋就是一个列表加几个按钮但用户反馈出奇地好——能看见、能控制人才会对系统产生信任。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑