☰
hindsight:基于MCP与Docker的LLM Agent记忆系统落地实践
2026/10/1 3:59:01 网站建设 项目流程

1. 从“hindsight”说起:为什么记忆是 Agent 落地的最后一公里

“hindsight”这个词本身很有意思,字面意思是“事后的洞察力”,也就是我们常说的“后见之明”。把它作为项目标题,放在 agent memory 这个语境里,指向性非常明确:让 LLM Agent 具备回溯、复盘、调用历史经验的能力。说白了,就是给 Agent 装一个“记忆系统”,让它不是每次对话都从零开始,而是能记住之前发生过什么、学到了什么、下次遇到类似情况该怎么处理。

我接触过不少做 Agent 的团队,模型能力其实都不差,工具调用也跑得通,但一到真实业务场景就露怯。问题往往不出在推理能力上,而是出在记忆上。用户上周提过的偏好,今天再问,Agent 完全不记得;同一个任务失败了三次,第四次还是用同样的错误方式去尝试;跨会话的上下文完全断裂,每次都要用户重新交代背景。这些问题的根源,就是 Agent 没有一套可靠的记忆机制。

hindsight 要解决的核心问题,就是 agent memory 的工程化落地。它不是一个纯学术研究项目,而是一套可以跑在 Docker 里的、对接 MCP 协议的、面向 LLM Agent 的记忆层实现。关键词里出现的 MCP、Docker、LLM,基本勾勒出了它的技术轮廓:用 Docker 做部署封装,用 MCP 做工具协议对接,用 LLM 做记忆的提取、压缩和检索。

这篇文章适合谁看?如果你正在做 Agent 应用开发,被跨会话记忆问题困扰;如果你在调研 MCP 协议的实际落地方式;如果你想了解 agent memory 从理论到工程到底要踩哪些坑,那这篇内容应该能给你一些直接可用的参考。我会从整体设计思路讲起,然后拆解核心细节,再给出一套可复现的实操流程,最后把常见问题和排查技巧整理出来。

2. 整体设计与思路拆解:hindsight 到底怎么“记住”东西

2.1 为什么不用简单的向量数据库糊弄过去

很多人一提到 agent memory,第一反应就是“上个向量数据库不就完了”。把对话历史 embedding 一下,存进 Chroma 或者 Milvus,查询的时候做相似度检索。这个方案能跑,但跑不长。原因有三个。

第一,向量检索没有结构。它只能告诉你“这段文本和 query 语义相似”,但没法告诉你“这是用户的身份信息”“这是上次任务的执行结果”“这是一个需要长期保留的偏好”。记忆是有类型的,不同类型的记忆,生命周期、检索方式、更新策略都不一样。第二,向量检索没有时间维度。Agent 的记忆是有时效性的,三天前的临时上下文和三个月前的用户偏好,权重完全不同。第三,向量检索没有主动遗忘机制。记忆越堆越多,检索噪声越来越大,最后 Agent 被一堆无关的历史信息干扰,表现反而下降。

hindsight 的设计思路,是把记忆分成几个层次来处理。这个思路借鉴了认知科学里 working memory 和 long-term memory 的区分。working memory 是当前会话的短期上下文,容量有限,随会话结束而清空或压缩。long-term memory 是跨会话的持久记忆,包括用户画像、历史任务摘要、学到的经验规则等。两者之间有一个“固化”过程,把 working memory 里值得保留的内容,经过 LLM 提取和压缩后,写入 long-term memory。

2.2 MCP 协议在其中的角色

MCP 是 Model Context Protocol 的缩写,可以理解为一种让 LLM 应用和外部工具、数据源对接的标准化协议。你可以把它类比成“AI 世界的 USB 接口”——不管对面是数据库、文件系统还是某个 API,只要实现了 MCP server,LLM 就能通过统一的方式去调用。

hindsight 把记忆系统封装成一个 MCP server,这个选择很关键。它意味着任何支持 MCP 的 LLM 客户端或 Agent 框架,都可以直接接入这套记忆能力,不需要改代码去适配特定的记忆 API。你可以在 Trae IDE 里用,可以在自己写的 Agent 里用,也可以在支持 MCP 的其他工具里用。这种解耦设计,让记忆层变成了一个可插拔的基础设施,而不是绑死在某个框架里的功能模块。

从工程角度看,MCP server 的接口设计要围绕记忆操作来定义。核心的 tool 大概包括这几类:写入记忆、检索记忆、更新记忆、删除记忆、列出记忆摘要。每个 tool 的输入输出都要设计得足够简洁,因为 LLM 在调用工具时,参数越复杂,出错概率越高。

2.3 Docker 封装带来的部署便利

把整个记忆系统跑在 Docker 里,好处是环境隔离和部署标准化。agent memory 系统通常依赖向量数据库、embedding 模型、LLM API 调用等多个组件,本地直接装很容易出现依赖冲突。Docker Compose 可以把这些组件编排在一起,一条命令启动全套服务。

更重要的是,Docker 让这套系统可以轻松迁移。你在开发机上跑通的配置,可以直接搬到服务器上,不用担心环境差异。对于团队协作来说,每个人拉同一个镜像,行为一致,排查问题也方便。

2.4 记忆的写入策略:什么时候该记,什么时候不该记

这是 hindsight 设计里最容易被忽视但最影响效果的部分。不是所有对话内容都值得写入长期记忆。如果每轮对话都往记忆库里塞,很快就会被噪声淹没。hindsight 采用的策略是“事件触发式写入”,具体来说,以下几种情况会触发记忆固化:

  • 用户明确表达了偏好或身份信息,比如“我是做后端开发的”“我习惯用 Python”
  • 一个任务完成或失败,需要记录结果和原因
  • 对话中出现了可复用的经验规则,比如“这个 API 在并发超过 10 的时候会限流”
  • 用户主动要求记住某些内容

触发之后,不是直接把原始对话存进去,而是先用 LLM 做一次提取和压缩,生成结构化的记忆条目。这个条目包含几个关键字段:记忆类型、内容摘要、时间戳、相关实体、置信度。这样后续检索时,可以按类型过滤,按时间排序,按实体关联。

3. 核心细节解析与实操要点:记忆系统的三个关键设计

3.1 记忆条目的结构化设计

前面提到记忆不能只是一段裸文本,那具体该长什么样?hindsight 的记忆条目设计,我拆解下来大概是这样的结构:

字段类型说明
idstring唯一标识,建议用 UUID
typeenum记忆类型:profile / task / rule / context
summarystringLLM 压缩后的摘要,控制在 200 字以内
raw_refstring原始对话的引用或存储路径
entitieslist涉及的关键实体,如人名、项目名、工具名
timestampdatetime记忆创建时间
last_accessdatetime最后一次被检索的时间
confidencefloat置信度,0 到 1 之间
ttlint过期时间,单位秒,0 表示永不过期

这个结构里,type 和 ttl 是两个最关键的字段。type 决定了检索时的过滤条件,比如用户问“我之前说过什么偏好”,就只检索 type=profile 的记忆。ttl 决定了记忆的生命周期,临时上下文可以设短一点,用户画像设永不过期。

confidence 字段的用途是处理冲突。如果用户之前说“我喜欢用 Java”,后来又说“我现在主要用 Go”,两条记忆冲突了怎么办?不是直接覆盖,而是保留两条,但新记忆的 confidence 更高,检索时优先返回高置信度的。同时可以设置一个衰减机制,老记忆的 confidence 随时间缓慢下降。

3.2 检索策略:不只是向量相似度

hindsight 的检索不是单纯的向量检索,而是多路召回加融合排序。具体来说,一次检索会同时走三条路:

第一条是向量检索,用 embedding 做语义相似度匹配。这条路的优势是能处理表达差异,用户问“我上次说的那个编程语言”,即使记忆里写的是“Python”,也能匹配上。

第二条是关键词检索,用 BM25 或类似算法做精确匹配。这条路的优势是处理实体名称、专有名词,比如用户问“关于项目 X 的记忆”,向量检索可能把项目 Y 也召回来,但关键词检索能精确命中。

第三条是时间衰减加权,对近期记忆给予更高权重。这条路的优势是符合人类记忆规律,最近发生的事情通常更相关。

三路召回之后,用一个融合排序算法把结果合并。最简单的做法是加权求和,向量相似度占 0.5,关键词匹配占 0.3,时间衰减占 0.2。具体权重可以根据业务场景调,没有标准答案。

注意:检索返回的结果不是越多越好。我实测下来,返回 top 5 到 top 8 条记忆比较合适。太多会挤占 LLM 的上下文窗口,而且噪声增加,反而降低回答质量。

3.3 记忆压缩:用 LLM 做摘要的实操细节

记忆压缩是 hindsight 里调用 LLM 最频繁的环节。每次触发记忆写入,都要调一次 LLM 做摘要提取。这个环节的 prompt 设计直接决定记忆质量。

我试过几种 prompt 写法,最后稳定下来的版本大概是这个结构:

你是一个记忆提取助手。请从以下对话片段中提取值得长期保留的记忆条目。 提取规则: 1. 只提取事实性信息、用户偏好、任务结果、可复用规则 2. 忽略寒暄、重复确认、临时性上下文 3. 每条记忆用一句话概括,不超过 50 字 4. 标注记忆类型:profile / task / rule / context 5. 如果没有任何值得保留的内容,返回空列表 对话片段: {conversation} 请以 JSON 格式返回,字段包括:type, summary, entities, confidence

这个 prompt 有几个细节值得说。第一,明确列出提取规则,而不是让 LLM 自由发挥。第二,要求返回 JSON,方便程序解析。第三,允许返回空列表,避免 LLM 为了完成任务硬凑记忆。第四,confidence 让 LLM 自己评估,虽然不一定准,但比没有强。

实测下来,用 GPT-4 级别模型做摘要,准确率可以接受。用更小的模型,比如 7B 级别的,摘要质量明显下降,经常把不重要的话也提取出来。如果成本敏感,可以考虑用规则先做一轮过滤,只把可能包含重要信息的片段送给 LLM 处理。

3.4 MCP server 的接口定义

hindsight 作为 MCP server,对外暴露的 tool 需要精心设计。我建议至少包含以下五个:

  • memory_write:写入一条新记忆,参数包括 type、summary、entities、ttl
  • memory_search:检索记忆,参数包括 query、type_filter、top_k、time_range
  • memory_update:更新已有记忆,参数包括 id、新的 summary 或 confidence
  • memory_delete:删除记忆,参数包括 id 或过滤条件
  • memory_summary:返回记忆库的统计摘要,比如各类型记忆数量、最近写入时间

每个 tool 的参数都要尽量简单。比如memory_search的 query 就是一个字符串,不要搞成复杂的嵌套对象。LLM 在调用工具时,参数结构越扁平,成功率越高。

实操心得:MCP server 的 tool 描述(description)非常重要。LLM 是根据描述来决定调哪个 tool 的。描述要写清楚“这个 tool 做什么”“什么时候该用”“参数是什么意思”。我见过太多人把 description 写得含糊不清,结果 LLM 该调的时候不调,不该调的时候乱调。

4. 实操过程与核心环节实现:从零跑通 hindsight

4.1 环境准备与 Docker 编排

先把基础环境搭起来。假设你用的是 Ubuntu 或者 macOS,Windows 用户建议用 WSL2,因为 Docker Desktop 在 Windows 上的网络配置有时候会出幺蛾子。

第一步,确认 Docker 和 Docker Compose 装好了:

docker --version docker compose version

如果没装,Ubuntu 上可以用官方脚本:

curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo usermod -aG docker $USER

装完之后记得重新登录,让用户组权限生效。

第二步,创建项目目录和 docker-compose.yml。hindsight 的依赖组件大概包括:记忆服务本体、向量数据库(Qdrant 或 Chroma)、Redis(做缓存和会话状态)。docker-compose.yml 大概长这样:

version: "3.8" services: hindsight: build: . ports: - "8080:8080" environment: - LLM_API_KEY=${LLM_API_KEY} - LLM_BASE_URL=${LLM_BASE_URL} - VECTOR_DB_URL=http://qdrant:6333 - REDIS_URL=redis://redis:6379 depends_on: - qdrant - redis qdrant: image: qdrant/qdrant:latest ports: - "6333:6333" volumes: - qdrant_data:/qdrant/storage redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis_data:/data volumes: qdrant_data: redis_data:

这里解释几个选择。向量数据库选 Qdrant 而不是 Chroma,是因为 Qdrant 的 Docker 镜像更稳定,持久化配置更清晰,而且支持过滤检索,这对按 type 过滤记忆很有用。Redis 用来存 working memory 和会话状态,因为它的读写速度比向量数据库快得多,适合频繁访问的短期数据。

第三步,准备 .env 文件:

LLM_API_KEY=your_api_key_here LLM_BASE_URL=https://api.your-llm-provider.com/v1

第四步,启动:

docker compose up -d

启动之后用docker compose logs -f hindsight看日志,确认服务正常。

4.2 记忆写入的完整流程

服务跑起来之后,下一步是验证记忆写入。hindsight 的 MCP server 启动后,会监听一个端口,等待 MCP 客户端连接。你可以先用 curl 或者 Postman 手动调一下接口,确认基本功能正常。

写入一条记忆的请求大概是这样:

{ "tool": "memory_write", "params": { "type": "profile", "summary": "用户是后端开发工程师,主要使用 Python 和 Go", "entities": ["Python", "Go", "后端开发"], "ttl": 0, "confidence": 0.9 } }

服务端收到请求后,会做几件事:生成 embedding,写入向量数据库;把结构化字段写入元数据存储;更新 Redis 里的记忆索引。整个过程是异步的,写入请求返回成功不代表已经落盘,但一般延迟在几百毫秒以内。

注意:embedding 模型的选择会影响检索效果。如果 LLM 提供商自带 embedding 接口,直接用就行。如果要本地跑,建议用 bge-m3 或者 text-embedding-3-small 这个级别的模型。太小的模型语义区分度不够,太大的模型推理速度慢。

4.3 检索流程与参数调优

检索是记忆系统里最影响用户体验的环节。hindsight 的检索流程分三步:查询理解、多路召回、融合排序。

查询理解这一步,会先用 LLM 把用户的自然语言 query 转成结构化的检索条件。比如用户问“我之前说过我用什么编程语言”,LLM 会把它转成:

{ "semantic_query": "用户使用的编程语言", "type_filter": "profile", "entities": ["编程语言"], "time_range": null }

然后拿这个结构化条件去多路召回。向量检索用 semantic_query 做 embedding 匹配,关键词检索用 entities 做精确匹配,时间衰减对近期记忆加权。

融合排序的权重我调过几轮,最后稳定在:

召回路径权重说明
向量相似度0.5语义匹配为主
关键词匹配0.3实体精确匹配
时间衰减0.2近期记忆优先

这个权重不是固定的,如果你的场景里用户偏好变化很快,可以把时间衰减权重调高。如果场景里实体名称很重要,可以把关键词权重调高。

4.4 与 Agent 框架的对接

hindsight 作为 MCP server,对接 Agent 框架的方式取决于框架本身对 MCP 的支持程度。如果框架原生支持 MCP,直接在配置里加上 server 地址就行。如果不支持,需要写一个适配层,把 MCP tool 调用转成框架的工具调用格式。

以常见的 Agent 循环为例,对接后的流程大概是:

  1. 用户输入 query
  2. Agent 先调memory_search,检索相关记忆
  3. 把检索到的记忆作为上下文,和用户 query 一起送给 LLM
  4. LLM 生成回复或决定调用其他工具
  5. 对话结束后,Agent 调memory_write,把值得保留的内容写入记忆

这个流程里,第 2 步和第 5 步是关键。第 2 步的检索质量决定 LLM 能不能拿到有用的历史信息。第 5 步的写入质量决定记忆库会不会被噪声污染。

实操心得:不要每轮对话都触发记忆写入。我建议设置一个阈值,比如对话轮数超过 3 轮,或者检测到用户表达了偏好、任务有了结果,才触发写入。这样可以大幅减少无效记忆。

5. 常见问题与排查技巧实录

5.1 记忆检索不准确怎么办

这是最常见的问题。用户明明之前说过某件事,但 Agent 检索不到。排查思路按以下顺序来:

先看记忆有没有写进去。调memory_summary接口,看记忆总数和各类型分布。如果总数很少,说明写入环节有问题。检查触发条件是不是太严格,或者 LLM 摘要时把内容过滤掉了。

再看 embedding 质量。把检索 query 和记忆 summary 分别做 embedding,算一下余弦相似度。如果相似度低于 0.7,说明 embedding 模型对这类语义的区分度不够。考虑换模型,或者在写入时把 summary 写得更具体。

最后看融合排序的权重。如果向量检索召回了正确记忆,但最终排序靠后,说明权重配置有问题。临时把向量相似度权重调到 0.8 试试,看结果有没有改善。

5.2 Docker 网络不通的排查

Docker Compose 启动后,服务之间网络不通是高频问题。典型表现是 hindsight 服务日志里报“connection refused”或者“timeout”。

第一步,确认所有容器都在运行:

docker compose ps

第二步,进入 hindsight 容器,测试网络连通性:

docker compose exec hindsight sh ping qdrant curl http://qdrant:6333/health

如果 ping 不通,说明不在同一个 Docker 网络里。检查 docker-compose.yml 里有没有定义 networks,或者服务是不是在默认网络里。

如果 ping 通但 curl 不通,说明端口或协议有问题。确认 Qdrant 的端口是 6333,Redis 的端口是 6379。

注意:Windows 上跑 Docker Desktop,有时候会出现“virtualization support not detected”的报错。这通常是 BIOS 里虚拟化没开,或者 Hyper-V 和 WSL2 冲突。进 BIOS 开一下 VT-x 或 AMD-V,然后在 Windows 功能里确认 WSL2 已启用。

5.3 记忆冲突与更新策略

用户偏好变了,旧记忆怎么处理?我的做法是不删除,而是降低旧记忆的 confidence,同时写入新记忆。检索时按 confidence 排序,新记忆自然排在前面。

如果冲突很频繁,可以考虑加一个“记忆合并”逻辑。当检测到两条同类型记忆的 entities 高度重叠但 summary 矛盾时,触发一次 LLM 调用,让 LLM 判断哪条更可信,或者生成一条合并后的新记忆。

5.4 性能优化:减少 LLM 调用次数

hindsight 里 LLM 调用主要在两个环节:记忆写入时的摘要提取,检索时的查询理解。这两个环节如果每次都调 LLM,成本和延迟都会很高。

优化思路是加缓存和规则过滤。查询理解环节,如果用户 query 很短且包含明确的实体名称,可以跳过 LLM,直接用规则提取。记忆写入环节,如果对话片段很短且没有明显偏好表达,也可以跳过 LLM,直接丢弃。

我实测下来,加这两层过滤之后,LLM 调用次数能减少 40% 左右,而记忆质量没有明显下降。

5.5 常见问题速查表

问题现象可能原因排查方法解决方案
检索不到记忆写入失败或 embedding 质量差调 memory_summary 看总数检查写入触发条件,换 embedding 模型
检索结果不相关融合排序权重不合理分别测试三路召回结果调整权重,增加 type_filter
Docker 服务启动失败端口冲突或依赖未就绪docker compose logs改端口,加 healthcheck
记忆库增长过快写入触发太频繁统计每日写入量加过滤规则,提高触发阈值
LLM 调用成本高摘要和查询理解太频繁统计 API 调用次数加缓存,加规则过滤
跨会话记忆丢失working memory 未固化检查会话结束时的写入逻辑加会话结束钩子,强制固化

6. 记忆系统的扩展方向与个人体会

hindsight 这套东西跑通之后,扩展空间其实挺大的。一个方向是加记忆的图结构,把 entities 之间的关系也存下来,这样检索时可以做关联召回。比如用户问“项目 X 用的什么技术栈”,不仅能召回项目 X 的记忆,还能召回和项目 X 关联的技术记忆。

另一个方向是加记忆的主动遗忘机制。不是所有记忆都值得永久保留,有些临时上下文过一段时间就没用了。可以加一个后台任务,定期扫描低 confidence、低访问频率的记忆,自动降低权重或标记为过期。

还有一个方向是记忆的跨 Agent 共享。如果多个 Agent 服务同一个用户,它们的记忆库可以打通,这样用户在 Agent A 里说过的话,Agent B 也能知道。MCP 协议本身支持这种多客户端接入,工程上主要是解决记忆的命名空间和权限隔离问题。

我个人在实际操作中的体会是,agent memory 这件事,技术方案只是一半,另一半是产品设计。你得想清楚哪些记忆该记、哪些不该记、记多久、怎么用。这些问题没有标准答案,得根据具体场景去调。我见过太多团队把记忆系统搭得很漂亮,但因为没有明确的记忆策略,最后记忆库变成了一堆垃圾数据,检索效果还不如不用。

最后分享一个小技巧:在记忆写入时,让 LLM 同时生成一个“记忆标签”,用几个关键词概括这条记忆。检索时先用标签做粗筛,再做向量精排。这样能大幅减少向量检索的候选集大小,提升检索速度。标签不用太复杂,三到五个词就够,比如“用户偏好-Python-后端”。这个做法我用了大半年,效果很稳。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询