ARTICLE DETAIL

资讯详情

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

LLM Agent长期记忆系统实战:分层架构、MCP接入与Docker部署

LLM Agent长期记忆系统实战:分层架构、MCP接入与Docker部署 1. 项目缘起为什么“事后复盘”值得单独造一个轮子做过 LLM Agent 项目的人大概都有过这种体验一个会话跑了几十轮用户突然问“我上周跟你提过的那个配置参数是多少来着”模型一脸茫然地回你“抱歉我没有相关记忆”。这不是模型笨而是它的上下文窗口里根本没有那部分信息——要么被截断了要么压根没存下来。hindsight这个项目从名字就能看出它的定位事后之明。它要解决的核心问题就是给 Agent 装上一套可检索、可回溯、可推理的长期记忆系统。不是简单的“把历史对话塞进向量库”那种粗暴做法而是一套有结构、有层次、有取舍的 memory 架构。我最初接触这个方向是因为手上有个客服场景的 Agent用户经常跨天来追问同一个工单的进展。每次都要用户重新描述一遍背景体验极差。试过直接把历史对话全量拼进 prompttoken 成本直接爆炸而且模型在超长上下文里反而抓不住重点。后来试过只存摘要结果细节全丢了用户问“你上次说的那个退款金额是多少”摘要里根本没这个数。hindsight这类项目的价值就在这里它把 Agent 的记忆拆成了working memory工作记忆和long-term memory长期记忆两层前者负责当前会话的即时上下文后者负责跨会话的知识沉淀。再通过 MCP 协议把这些能力暴露给上层 Agent 框架让 Claude、GPT 这类模型能够主动调用“回忆”工具。适合谁来参考这篇文章如果你正在做以下任何一件事这篇内容应该能帮到你用 LLM 框架LangChain、LlamaIndex、自研搭 Agent被上下文长度和记忆丢失折磨过想了解 MCP 协议在记忆管理场景下怎么落地需要一套能跑在 Docker 里、开箱即用的 Agent 记忆服务对 RAG、GraphRAG、本体ontology在记忆系统里的应用感兴趣下面我会从架构设计、核心机制、Docker 部署实操、MCP 接入、常见坑排查几个维度把hindsight这类项目的完整落地路径讲清楚。内容会结合我自己的实操经验该给参数给参数该说坑说坑。2. 架构拆解Agent Memory 到底该怎么分层2.1 为什么不能只用一个向量库很多人做 Agent 记忆的第一反应是搞个向量数据库把对话 embedding 进去需要的时候相似度检索一下不就行了我一开始也是这么想的直到踩了几个坑才发现问题。第一个坑是时间维度丢失。向量检索只看语义相似度不看时间。用户三个月前说“我住在北京”上个月说“我搬到上海了”你检索“用户住哪”两条都会命中模型可能给你返回北京。这在需要时效性的场景里是致命的。第二个坑是关系断裂。对话里的信息不是孤立的点而是有关系的。用户说“我有个订单 A123”后来说“A123 要退款”再后来说“退款原因选错了”。这三条信息如果只是三个独立的向量检索出来模型还得自己拼关系。但如果记忆系统里本身就存了实体和关系检索效率完全不一样。第三个坑是写入成本。每轮对话都做 embedding 写入高频场景下向量库压力很大而且很多对话内容是“嗯”“好的”“谢谢”这种无信息量的存了纯属浪费。hindsight的思路是把记忆分成几层来处理记忆层级存储内容存储介质生命周期Working Memory当前会话最近 N 轮原文内存/Redis会话结束即释放Episodic Memory会话摘要 关键事件关系库 向量库中期保留Semantic Memory抽取的实体、关系、事实图数据库/关系库长期保留Procedural Memory工具调用模式、成功路径关系库长期保留这个分层不是拍脑袋定的而是对应了认知科学里人类记忆的基本模型。Working memory 容量有限但访问极快对应我们当前正在想的事episodic 是“我经历过什么”semantic 是“我知道什么”procedural 是“我会做什么”。2.2 Working Memory 的窗口策略Working memory 最核心的问题是保留多少轮保留太少模型没有上下文保留太多token 成本高且模型注意力分散。我的经验是不要用固定轮数而是用token 预算 重要性加权的方式。具体做法设定一个 token 预算比如 4000 token 给 working memory从最近一轮往前累加直到接近预算对每一轮打一个重要性分数包含实体、数字、指令的轮次分数高如果预算不够优先保留高重要性轮次低重要性的可以压缩成一句话摘要这个策略我在实际项目里跑下来比固定保留 10 轮的方案在“用户追问细节”场景下的准确率提升了大概 30%。因为很多关键信息其实在很早的轮次里固定窗口会把它挤掉。2.3 MCP 协议在记忆系统里的角色MCPModel Context Protocol这两年被讨论得很多但很多人对它的理解还停留在“让 AI 调用工具”这个层面。在hindsight这类记忆系统里MCP 的价值其实更微妙。传统做法是Agent 框架自己管理记忆在构造 prompt 的时候把记忆拼进去。这种做法的问题是记忆逻辑和 Agent 逻辑耦合在一起换个框架就得重写。MCP 的做法是把记忆系统做成一个独立的 server通过标准协议暴露几个工具比如recall_memory、store_memory、search_episodes。Agent 框架只需要知道“有这么几个工具可以调”不需要关心记忆是怎么存的、怎么检索的。这样做的好处解耦记忆系统可以独立升级、独立扩容不影响 Agent复用同一个记忆服务可以给多个 Agent 用可控模型什么时候调记忆、调哪个工具是可观测、可干预的注意MCP 是软件协议层面的概念和硬件协议不是一回事。它的本质是一套基于 JSON-RPC 的通信规范定义了 client通常是 Agent 框架和 server工具提供方之间怎么交换能力描述和调用请求。3. 核心机制记忆的写入、检索与遗忘3.1 写入不是所有对话都值得记我见过很多项目把每一轮对话无差别写入记忆库结果检索的时候噪声一大堆。hindsight这类系统通常会在写入前做一层过滤和抽取。过滤的逻辑大概是去重和最近 N 轮语义相似度超过阈值的不重复写去噪纯寒暄、纯确认类的内容直接丢弃抽取从对话里抽实体人名、订单号、日期、金额、关系属于、导致、修改、事实用户偏好、约束条件分级抽取出来的信息按重要性分级决定存到哪一层抽取这一步可以用 LLM 来做也可以用规则 NER 模型。用 LLM 的好处是灵活坏处是成本和延迟。我的做法是混合高频实体用规则匹配订单号、手机号、日期这种格式固定的开放域信息用 LLM 抽取并且把抽取 prompt 做得尽量短只输出 JSON。一个抽取 prompt 的示例结构{ entities: [ {type: order, id: A123, attributes: {status: refunding}}, {type: user, id: u456, attributes: {city: 上海}} ], relations: [ {from: u456, to: A123, type: owns} ], facts: [ {content: 用户偏好邮件通知, confidence: 0.9} ] }这个 JSON 结构直接对应到存储层entities 进图库或关系表relations 进边表facts 进事实表并做 embedding。3.2 检索多路召回 重排检索是记忆系统里最考验工程能力的地方。单一向量检索的问题前面说过了hindsight通常采用多路召回策略向量召回语义相似度适合模糊查询关键词召回BM25 或全文索引适合精确匹配订单号、人名时间召回最近 N 天的记忆适合时效性查询图召回从实体出发沿关系边扩展适合关联查询四路召回的结果合并后用一个重排模型可以是 cross-encoder也可以是 LLM as judge打分排序取 top-k 返回给 Agent。这里有个实操细节重排的 k 值不要设太大。我试过 top-20 重排延迟直接飙到 2 秒以上。后来改成各路召回 top-5合并后重排取 top-3延迟控制在 500ms 以内效果反而更好因为噪声少了。3.3 遗忘记忆系统也需要“断舍离”这一点很多人会忽略。记忆不是越多越好过期的、矛盾的、低价值的信息留着只会干扰检索。hindsight的遗忘策略通常包括TTL 过期working memory 会话结束即清episodic memory 保留 30 天矛盾消解新事实和旧事实冲突时旧事实标记为 superseded不删除但降权访问衰减长期不被检索到的记忆权重逐渐降低低于阈值后归档容量上限每个用户的 semantic memory 设上限超了按权重淘汰实操心得矛盾消解这块不要直接删旧数据。我踩过坑用户说“我改主意了还是用原来的方案”结果旧方案已经被删了系统完全不知道“原来的方案”是什么。正确做法是保留历史版本用时间戳和状态字段区分。4. Docker 部署实操从零跑起来4.1 环境准备与依赖检查hindsight这类项目通常提供 Docker Compose 一键部署。但在跑起来之前有几个环境问题必须先解决否则你会卡在启动阶段。Windows 用户特别注意Docker Desktop 启动失败最常见的原因是虚拟化没开。报错信息通常是virtualization support not detected或Docker Desktop failed to start because virtualisation support wasnt detected。解决办法进 BIOS/UEFI开启 Intel VT-x 或 AMD-VWindows 功能里确认“Hyper-V”和“虚拟机平台”已启用如果用的是 WSL2 后端确认 WSL2 已安装且版本够新Linux 用户确认内核版本支持 cgroup v2Docker 版本建议 24 以上。用docker info检查存储驱动overlay2 是首选。macOS 用户Apple Silicon 和 Intel 芯片的镜像架构不同拉镜像时注意平台参数。M 系列芯片用--platform linux/arm64Intel 用linux/amd64。4.2 Compose 文件关键配置解析一个典型的hindsight部署会包含这几个服务services: hindsight-api: image: hindsight/api:latest ports: - 8080:8080 environment: - DB_HOSTpostgres - DB_PORT5432 - DB_NAMEhindsight - DB_USERhindsight - DB_PASSWORD${DB_PASSWORD} - REDIS_HOSTredis - VECTOR_STOREpgvector - EMBEDDING_MODELtext-embedding-3-small - LLM_API_BASE${LLM_API_BASE} - LLM_API_KEY${LLM_API_KEY} depends_on: postgres: condition: service_healthy redis: condition: service_started restart: unless-stopped postgres: image: pgvector/pgvector:pg16 environment: - POSTGRES_DBhindsight - POSTGRES_USERhindsight - POSTGRES_PASSWORD${DB_PASSWORD} volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: [CMD-SHELL, pg_isready -U hindsight] interval: 5s timeout: 5s retries: 5 redis: image: redis:7-alpine command: redis-server --appendonly yes volumes: - redisdata:/data volumes: pgdata: redisdata:几个关键点解释一下为什么用 pgvector 而不是专用向量库对于中小规模场景百万级向量以内pgvector 完全够用而且省去了维护两套数据库的麻烦。事务一致性也好处理——记忆的元数据和向量在同一个库里不会出现元数据写了向量没写的情况。规模上去了再考虑 Milvus、Qdrant 这类专用库。Redis 的作用是什么主要存 working memory 和会话状态。Redis 的过期机制天然适合 working memory 的生命周期管理而且读写延迟低。appendonly yes是为了防止重启丢数据虽然 working memory 丢了影响不大但会话状态丢了用户体验会断。健康检查为什么重要depends_on默认只等容器启动不等服务就绪。postgres 容器起来了但数据库还没初始化完API 连上去就会报错。加上condition: service_healthy才能真正等到数据库可用。4.3 启动与验证配置好.env文件后启动命令很简单docker compose up -d然后验证服务状态docker compose ps docker compose logs -f hindsight-apiAPI 正常启动后可以用 curl 测一下健康检查接口curl http://localhost:8080/health返回{status:ok}就说明服务起来了。接下来测试记忆写入和检索# 写入一条记忆 curl -X POST http://localhost:8080/memory \ -H Content-Type: application/json \ -d { user_id: test_user, content: 用户偏好使用深色主题, type: preference } # 检索记忆 curl http://localhost:8080/memory/search?user_idtest_userquery主题偏好如果检索能返回刚才写入的内容说明向量化和检索链路是通的。注意首次启动时 embedding 模型可能需要下载如果网络环境受限会卡在模型加载阶段。建议提前把模型文件挂载进去或者配置国内可访问的模型服务端点。5. MCP 接入让 Agent 真正用上记忆5.1 MCP Server 的暴露方式hindsight作为 MCP server需要暴露几个核心工具。MCP 协议里工具的定义包括名称、描述、参数 schema。一个记忆检索工具的定义大概长这样{ name: recall_memory, description: 检索用户的长期记忆返回与查询相关的历史信息, inputSchema: { type: object, properties: { query: { type: string, description: 检索查询描述你想回忆什么 }, memory_type: { type: string, enum: [episodic, semantic, procedural, all], description: 记忆类型过滤 }, time_range: { type: string, description: 时间范围如 last_7_days }, top_k: { type: integer, default: 3 } }, required: [query] } }工具描述写得好不好直接决定模型会不会正确调用。我见过描述写得太模糊模型该调的时候不调不该调的时候乱调。描述里要明确说清楚“什么时候用这个工具”而不只是“这个工具做什么”。5.2 传输方式选择stdio 还是 SSEMCP 支持多种传输方式本地部署常用 stdio远程部署常用 SSE 或 WebSocket。stdio 方式适合记忆服务和 Agent 在同一台机器上的场景。配置简单不需要网络暴露安全性好。缺点是 Agent 框架需要能启动子进程。SSE/WebSocket 方式适合记忆服务独立部署的场景。多个 Agent 可以共享同一个记忆服务也方便做鉴权和限流。配置时需要注意连接地址要带鉴权 token要处理断线重连要设置合理的超时时间一个 SSE 接入的配置示例{ mcpServers: { hindsight: { url: http://localhost:8080/mcp/sse, headers: { Authorization: Bearer ${HINDSIGHT_TOKEN} } } } }5.3 工具调用时机让模型自己决定MCP 接入后最大的问题不是技术连通而是模型什么时候该调记忆工具。我的经验是不要指望模型每次都判断准确。更可靠的做法是在 system prompt 里给出明确的指引当用户提到“之前”“上次”“以前说过”这类词时先调recall_memory当用户提供了新的个人信息、偏好、约束时调store_memory当用户问的是通用知识问题时不要调记忆工具同时可以在 Agent 框架层面做一个兜底如果用户 query 里包含明确的回溯意图词强制触发一次记忆检索把结果作为上下文注入而不是等模型自己决定。实操心得模型调用工具是有成本的延迟 token。我实测下来在 system prompt 里写清楚调用规则后无效调用能减少 60% 以上。另外recall_memory的返回结果不要直接全量塞给模型最好在服务端做一次摘要压缩只返回最相关的片段。6. 常见问题与排查技巧实录6.1 启动类问题现象可能原因排查方法解决方式Docker Desktop 启动失败虚拟化未开启看报错是否含 virtualizationBIOS 开启 VT-x/AMD-V容器启动后立即退出环境变量缺失docker compose logs检查 .env 文件API 连不上数据库健康检查未通过docker compose ps看状态加 healthcheck 条件端口冲突8080 被占用netstat -ano | findstr 8080改端口映射镜像拉取慢网络问题docker pull手动测试配置镜像加速6.2 记忆检索类问题问题检索结果不相关。先检查 embedding 模型是否一致。写入和检索用的必须是同一个模型否则向量空间不对齐相似度计算完全没意义。我踩过这个坑写入用的 OpenAI embedding检索配了个本地模型结果检索出来的东西驴唇不对马嘴。问题明明存了却检索不到。检查几个点一是 user_id 是否一致多租户场景下很容易串二是时间过滤是否把数据排除了三是向量索引是否建了pgvector 不建索引的话数据量大了检索会超时。问题检索延迟高。用EXPLAIN ANALYZE看查询计划。常见原因是向量索引没建或者建的类型不对。pgvector 支持 IVFFlat 和 HNSW 两种索引HNSW 查询快但建索引慢IVFFlat 建索引快但查询精度依赖参数调优。数据量在百万级以下HNSW 是更好的选择。6.3 模型调用类问题问题LLM request failed: provider rejected the request schema or tool payload。这个报错通常出现在 MCP 工具调用时。原因是工具的参数 schema 和模型实际返回的参数不匹配。排查步骤打印模型返回的 tool call 原始内容对比 schema 定义看是类型不对还是必填字段缺失检查 schema 里有没有模型不支持的 JSON Schema 特性比如 oneOf、anyOf 嵌套太深我的做法是尽量把 schema 写简单能用 string 就不用 object能用 enum 就不用自由文本。模型对复杂 schema 的遵循度会明显下降。问题模型不调用记忆工具。除了前面说的 prompt 指引还要检查工具的 description 是否清晰。另外有些模型对工具数量敏感工具太多会“选择困难”。如果记忆服务暴露了十几个工具考虑合并成几个核心的。6.4 数据一致性类问题问题写入成功但检索不到。大概率是异步写入的问题。很多记忆系统为了降低延迟写入是异步的向量化还没完成就返回成功了。解决办法是在写入接口里加一个wait_for_index参数或者写入后轮询确认。问题同一实体有多条矛盾记录。这是矛盾消解没做好。检查是否有 supersede 逻辑新事实写入时是否把旧事实标记为过期。如果没有补上这个逻辑并且在检索时过滤掉 superseded 的记录。7. 性能调优与扩展思路7.1 向量索引调优pgvector 的 HNSW 索引有几个关键参数m每个节点的最大连接数默认 16。增大能提高召回率但索引体积和构建时间增加。我的经验是 16-32 之间比较平衡。ef_construction构建时的候选集大小默认 64。增大能提高索引质量但构建变慢。数据量大的话可以设到 128。ef_search查询时的候选集大小默认 40。这个可以在查询时动态设置对延迟和召回率影响最直接。-- 建索引 CREATE INDEX ON memory_embeddings USING hnsw (embedding vector_cosine_ops) WITH (m 24, ef_construction 128); -- 查询时调整 SET hnsw.ef_search 80;7.2 缓存策略记忆检索的结果可以缓存。同一个用户短时间内重复问类似问题没必要每次都走完整检索链路。缓存 key 可以用user_id query_hashTTL 设短一点比如 5 分钟。因为记忆是动态更新的缓存太久会返回过期信息。另外embedding 计算也可以缓存。同样的文本不要重复算 embedding用文本 hash 做 key缓存时间可以长一些。7.3 水平扩展单机跑不动的时候扩展路径大概是读写分离Postgres 主从写入走主库检索走从库向量库独立把向量检索拆到专用向量库Postgres 只存元数据分片按 user_id 哈希分片不同用户的数据落到不同实例无状态化 APIAPI 层做成无状态的前面挂负载均衡随意扩缩容不过说实话大部分中小项目在单机 pgvector 上跑到百万级向量都没什么问题。过早优化架构反而增加维护成本。先跑起来遇到瓶颈再拆。8. 我踩过的几个印象深刻的坑第一个坑是embedding 维度不匹配。有次升级 embedding 模型从 1536 维换到 3072 维忘了改数据库的 vector 列定义写入直接报错。更隐蔽的是如果列定义是vector不指定维度pgvector 不会报错但检索时维度不一致会算出莫名其妙的结果。所以建表时一定要指定维度升级时要有迁移方案。第二个坑是working memory 和 long-term memory 的边界。我一开始把两者混在一起检索的时候经常把当前会话刚说的话又“回忆”出来一遍模型收到重复信息回答变得很奇怪。后来明确区分working memory 直接拼在 prompt 里不走检索long-term memory 才走检索工具。两者不重叠。第三个坑是MCP 连接的超时设置。默认超时太短记忆检索稍微慢一点就断了模型收到工具调用失败就会编造一个答案。这个特别危险因为用户看不出来模型在胡说。后来把超时调到 10 秒并且在工具调用失败时明确告诉模型“记忆检索失败请基于当前上下文回答”而不是让它自由发挥。第四个坑是多租户数据隔离。早期版本忘了在所有查询里加 user_id 过滤测试的时候没发现因为测试数据只有一个用户。上线后差点出大事。后来在数据访问层做了强制过滤所有查询必须带 user_id不带就抛异常。9. 后续可以怎么扩展这套记忆系统跑通之后有几个方向可以继续深挖。一是记忆的可视化。用户应该能看到 Agent 记住了什么并且能手动修正。比如用户发现 Agent 记错了自己的偏好能直接改。这需要一套管理界面但价值很大尤其是企业场景。二是记忆的跨 Agent 共享。同一个用户可能同时和多个 Agent 交互客服 Agent、助手 Agent、推荐 Agent如果记忆能共享体验会连贯很多。MCP 协议天然支持这种模式一个记忆 server 服务多个 Agent client。三是记忆的推理能力。现在主要是检索未来可以加上推理。比如从“用户买了婴儿奶粉”和“用户搜索了幼儿园”推理出“用户家里有婴幼儿”这种隐含信息如果能自动抽取记忆的价值会大很多。GraphRAG 和本体ontology的思路在这里很有用。四是记忆的隐私保护。敏感信息身份证号、银行卡号不应该明文存储。可以在写入前做脱敏或者用可逆加密检索时按权限解密。这块在合规要求高的场景里是刚需。这套东西我自己跑了大半年从最初的“能存能查”到现在“分层检索 矛盾消解 MCP 接入”中间迭代了不知道多少版。最大的体会是记忆系统的难点不在存储而在取舍。什么该记、什么该忘、什么时候该回忆、回忆多少这些决策比技术选型重要得多。技术方案可以抄但这些策略得根据自己的业务场景慢慢调。
返回列表