ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Hindsight 实战:为 AI Agent 构建事后记忆与经验提炼机制

Hindsight 实战:为 AI Agent 构建事后记忆与经验提炼机制 1. 为什么“hindsight”这个词值得单独拿出来聊“hindsight”直译过来是“后见之明”放在 AI Agent 的语境里它指向一个非常具体、也非常要命的问题Agent 在完成任务之后能不能回头看清自己刚才做了什么、为什么这么做、哪一步走对了、哪一步走歪了。这件事听起来像是复盘实际上它决定了 Agent 能不能从一次性工具变成可积累、可迭代的系统。我接触过不少做 Agent 的团队大家一开始都把精力砸在工具调用、提示词编排、流程串联上跑通一个 demo 很快但一旦进入真实业务问题就集中爆发同一个错误反复犯、上下文一长就失忆、换了会话就像换了个人。根子往往不在模型能力而在记忆机制——尤其是事后记忆这一层几乎是空白。hindsight 要解决的就是让 Agent 拥有“回头看”的能力把执行轨迹沉淀成可检索、可复用的经验。这篇文章适合三类人看正在做 Agent 落地的工程师、被 Agent 稳定性折磨的产品负责人、以及想搞清楚 agent memory 到底怎么设计的技术爱好者。我会从整体设计思路讲到具体实现包括 LLM、MCP、Docker 这些热词在其中的位置尽量把每一步的“为什么”说透让你看完能直接照着搭一套。2. hindsight 的整体设计与思路拆解2.1 先搞清楚hindsight 和普通 memory 的区别在哪市面上讲 agent memory 的文章很多但大多数讲的是working memory也就是 Agent 在当前任务里临时记住的东西比如用户刚说的话、刚查到的数据。这类记忆的生命周期很短任务结束基本就丢了。hindsight 不一样它关注的是任务结束之后的记忆属于长期记忆里偏“经验”的那一类。打个比方working memory 像是你做饭时手边摆的食材和调料做完这顿饭就收拾了hindsight 像是你做完之后写的一张卡片“今天火开太大糖色炒糊了下次中火。”这张卡片不会影响你今晚吃什么但会影响你下次做这道菜的成功率。Agent 也一样hindsight 让它把“这次为什么失败”变成“下次怎么避免”。从技术上看hindsight 的核心动作有三个捕获执行轨迹、提炼可复用经验、在后续任务中按需召回。这三个动作串起来才构成一个完整的 hindsight 闭环。只做捕获不做提炼那就是日志只做提炼不做召回那就是文档。三者缺一不可。2.2 为什么选 LLM 来做经验提炼而不是规则引擎有人会问既然执行轨迹已经有了为什么不用规则去提取经验比如“如果报错码是 404就记一条资源不存在”。我试过纯规则方案结论是规则能覆盖的场景太窄而且维护成本随场景数量指数上升。LLM 在这里的价值是它能理解语义。同样是失败可能是参数写错、可能是工具选错、可能是时机不对规则很难区分但 LLM 可以。比如 Agent 调用某个接口失败LLM 能判断出“这次失败是因为在未认证状态下调用了需要鉴权的接口”这种经验用规则写要写一堆条件用 LLM 一句话就出来了。当然用 LLM 做提炼也有代价成本和延迟。所以我的做法是分层——高频、明确的失败用轻量规则先过滤剩下的模糊情况再交给 LLM。这样既控制了成本又保留了语义理解能力。这个取舍在后面实操部分会详细讲。2.3 MCP 在 hindsight 里扮演什么角色MCP 是软件协议层面的东西全称是 Model Context Protocol你可以把它理解成 Agent 和外部工具之间的“标准插座”。以前每个工具都要单独写适配现在只要工具实现了 MCPAgent 就能用统一方式调用。这对 hindsight 的意义在于执行轨迹的捕获可以标准化。如果 Agent 调工具的方式五花八门那捕获轨迹就要为每种工具写一套逻辑累且容易漏。有了 MCP所有工具调用都走同一套协议轨迹捕获就变成了在协议层加一个中间件的事。我在实际项目里就是这么干的在 MCP 的请求和响应链路上挂一个记录器所有调用自动落库不用改任何业务代码。提示MCP 是协议不是硬件别被“硬件协议”那种说法带偏。它解决的是软件之间怎么对话的问题和物理接口没关系。2.4 Docker 为什么是这套方案的默认底座Agent memory 这套东西涉及好几个组件轨迹存储、向量检索、LLM 调用、MCP 网关。如果每个都手动装环境问题能吃掉你一半时间。Docker 和 Docker Compose 的价值就是把这一堆东西打包成可复现的环境。我用 Docker Compose 编排了四个服务轨迹库Postgres、向量库比如 Qdrant 或 pgvector、MCP 网关、以及 hindsight 服务本身。一条docker compose up就能起来换台机器也一样。这对团队协作太重要了——新人入职不用配环境直接拉起来就能跑。后面我会给出具体的 compose 配置。3. 核心细节解析与实操要点3.1 执行轨迹到底该记什么这是 hindsight 最容易被做错的地方。很多人一上来就把所有东西都记下来结果存储爆炸、检索噪声大。我的经验是记“决策点”和“结果”不记“过程细节”。具体来说一次 Agent 执行里值得记的包括用户意图、Agent 选择的工具、工具入参、工具返回、Agent 的下一步决策、最终结果。不值得记的包括模型内部的 token 流、中间推理的每一句话、重复的轮询。前者是经验后者是噪音。我一般用一张表来存轨迹字段设计大概是这样的字段类型说明trace_idstring一次执行的唯一标识step_indexint第几步intenttext当前步骤的意图tool_namestring调用的工具名tool_inputjsonb工具入参tool_outputjsonb工具返回decisiontextAgent 的决策说明outcomestringsuccess / fail / partialtimestampdatetime时间戳这张表的好处是结构清晰检索时能按 intent、tool_name、outcome 快速过滤。jsonb 字段让入参和返回保持灵活不用为每个工具改表结构。3.2 经验提炼的提示词怎么写才有效提炼经验这一步提示词设计直接决定质量。我踩过的坑是一开始让 LLM“总结这次执行”结果它写出来的东西又长又泛根本没法用。后来改成结构化输出效果立刻不一样。我的提示词模板大致是这样你是一个 Agent 经验提炼器。下面是一次 Agent 执行的轨迹。 请输出一条可复用的经验格式如下 - 场景什么情况下适用 - 问题这次遇到了什么问题 - 原因根本原因是什么 - 对策下次应该怎么做 要求只输出这一条经验不要复述轨迹不要写无关内容。 如果这次执行完全成功且没有值得记录的点输出 NONE。关键在最后那句“如果完全成功且没有值得记录的点输出 NONE”。不加这句LLM 会强行编经验把正常执行也说成有问题。加了之后只有真正有价值的经验才会被存下来信噪比高很多。3.3 召回策略什么时候该翻旧账经验存下来不是目的用起来才是。召回策略我分两种主动召回和被动召回。主动召回是在任务开始前根据用户意图去检索相关经验塞进 Agent 的上下文。比如用户说“帮我部署一个服务”系统就去检索历史上部署相关的经验把踩过的坑提前告诉 Agent。被动召回是在执行过程中当 Agent 遇到失败时实时检索相似失败的经验帮它快速定位。两种召回我都用向量检索打底但加了结构化过滤。纯向量检索的问题是语义相似但场景不对的经验也会被召回所以我先用 tool_name、outcome 这类结构化字段过滤再做向量相似度排序。这样召回的准确率高很多。3.4 存储选型为什么我用 Postgres 加 pgvector向量库的选择很多Qdrant、Milvus、Weaviate 都行。我最后选 Postgres 加 pgvector理由有三个一是轨迹本身是结构化数据放关系库天然合适二是 pgvector 够用中小规模场景性能没问题三是少维护一个组件。如果你的轨迹量到了千万级以上或者对检索延迟要求极高那单独上专用向量库是合理的。但大多数团队在早期Postgres 加 pgvector 能省掉大量运维成本。我见过太多项目一上来就堆组件结果维护不动反而拖慢了迭代。注意pgvector 的索引类型要选对。数据量小的时候用 IVFFlat数据量大且要求召回率用 HNSW。选错了要么慢要么不准。4. 实操过程与核心环节实现4.1 用 Docker Compose 把环境搭起来先把底座搭好后面才好干活。下面是我用的 compose 配置精简版能直接跑version: 3.9 services: postgres: image: pgvector/pgvector:pg16 environment: POSTGRES_USER: hindsight POSTGRES_PASSWORD: hindsight POSTGRES_DB: hindsight ports: - 5432:5432 volumes: - pgdata:/var/lib/postgresql/data mcp-gateway: image: your-mcp-gateway:latest ports: - 8080:8080 depends_on: - postgres hindsight: build: ./hindsight environment: DATABASE_URL: postgres://hindsight:hindsightpostgres:5432/hindsight MCP_ENDPOINT: http://mcp-gateway:8080 ports: - 8000:8000 depends_on: - postgres - mcp-gateway volumes: pgdata:这里选pgvector/pgvector:pg16而不是官方 postgres 镜像是因为它预装了 pgvector 扩展省得自己编译。启动之后进数据库执行CREATE EXTENSION vector;就能用向量功能了。Windows 用户如果遇到 Docker Desktop 起不来先检查虚拟化有没有开。BIOS 里的 VT-x 或 AMD-V 要打开否则 Docker Desktop 会报 virtualization support not detected。这个坑我见过太多次很多人以为是软件问题其实是 BIOS 没开。4.2 轨迹捕获中间件怎么写MCP 网关是捕获轨迹的最佳位置。下面是一个简化的中间件逻辑用 Python 写import json from datetime import datetime def capture_trace(trace_id, step_index, intent, tool_name, tool_input, tool_output, decision): outcome success if tool_output.get(status) ok else fail record { trace_id: trace_id, step_index: step_index, intent: intent, tool_name: tool_name, tool_input: json.dumps(tool_input), tool_output: json.dumps(tool_output), decision: decision, outcome: outcome, timestamp: datetime.utcnow().isoformat() } db.insert(traces, record)这段代码挂在 MCP 请求的响应回调里每次工具调用返回就记一条。注意 outcome 的判断逻辑要根据你的工具返回格式调整别硬套。我见过有人直接判断 HTTP 状态码结果工具返回 200 但业务失败的情况全被记成 success经验提炼就全废了。4.3 经验提炼服务的实现提炼服务是一个独立进程定时扫描新轨迹批量提炼。核心逻辑def extract_experience(trace_group): prompt build_prompt(trace_group) response llm.complete(prompt) if response.strip() NONE: return None experience parse_experience(response) embedding embed(experience[scenario] experience[problem]) db.insert(experiences, { **experience, embedding: embedding, source_trace: trace_group[trace_id] }) return experience这里有个细节embedding 我用的是“场景加问题”的拼接而不是整条经验。因为召回时用户意图最接近的是场景和问题对策部分反而不那么关键。这个选择是我试了几版之后定下来的召回准确率比整条嵌入高不少。批量提炼的节奏我一般设成每 5 分钟一次每次最多处理 50 条轨迹。太快了 LLM 调用压力大太慢了经验滞后。这个参数可以根据你的轨迹产生速度调。4.4 召回环节怎么接进 Agent召回要在 Agent 执行前和执行中两个点接入。执行前的代码大概是这样def recall_before_task(user_intent): embedding embed(user_intent) candidates db.query( SELECT * FROM experiences ORDER BY embedding %s LIMIT 20 , [embedding]) filtered [c for c in candidates if c[score] 0.75] return filtered[:5]是 pgvector 的余弦距离操作符。先取 20 条候选再用阈值过滤最后取前 5 条。阈值 0.75 是我调出来的经验值太低会引入噪声太高会漏掉有用经验。你可以根据自己的数据分布微调。执行中的召回类似只是触发条件是 Agent 报告失败。这时候检索的关键词是失败信息加当前工具名召回更精准。5. 常见问题与排查技巧实录5.1 经验越存越多召回越来越差怎么办这是 hindsight 最典型的退化问题。经验库膨胀之后相似经验互相干扰召回质量下降。我的解法是经验去重加衰减。去重是在入库时做新经验和已有经验的向量相似度超过 0.9就不入库而是给已有经验加一个 hit_count。衰减是定期任务超过 90 天没被召回过的经验降低权重或者归档。这样经验库保持精简召回质量稳定。5.2 LLM 提炼出来的经验格式不对怎么处理LLM 偶尔会不按格式输出尤其是模型能力弱的时候。我的做法是解析失败就重试重试两次还失败就丢弃。不要试图用正则去硬抠抠出来的东西质量没保证反而污染经验库。另外提示词里加 few-shot 例子能显著提升格式稳定性。我在提示词里放了两条正例一条反例格式错误率从 15% 降到了 3% 左右。5.3 Docker 网络不通导致服务连不上Compose 里服务之间用服务名通信比如 hindsight 连 postgres 用postgres:5432不是localhost:5432。这个坑新手常踩。如果连不上先docker compose exec hindsight ping postgres看通不通。不通的话检查是不是在同一个 network 里Compose 默认会创建一个共享网络但如果你手动指定了 network 就要确认配置一致。5.4 常见问题速查表问题可能原因排查方向轨迹没记录中间件没挂上检查 MCP 网关回调配置经验提炼为空提示词太宽松加 NONE 分支和 few-shot召回不准阈值设置不当调整相似度阈值加结构化过滤服务起不来端口冲突检查 5432、8000、8080 是否被占向量检索慢索引没建检查 pgvector 索引类型和参数Docker 报虚拟化错误BIOS 未开 VT进 BIOS 开启虚拟化支持5.5 几个我踩过的坑第一个坑是过早引入复杂向量库。我一开始上了 Milvus结果运维复杂度陡增团队没人熟出问题排查半天。后来换回 pgvector简单稳定性能也够。教训是先用最简单的方案跑通有瓶颈再换。第二个坑是经验提炼频率太高。我一开始设成实时提炼每条轨迹出来就调 LLM结果成本飙升而且单条轨迹信息量太少提炼质量差。改成批量提炼后成本降了八成质量还提升了。第三个坑是忽略经验的时效性。有些经验是特定版本、特定环境下的环境变了就失效了。后来我在经验里加了 environment 字段召回时按环境过滤避免用过期经验误导 Agent。6. 关于 hindsight 后续可以怎么扩展这套东西跑通之后能扩展的方向不少。我目前在做的是经验分级把经验分成“通用经验”和“场景经验”通用经验跨任务复用场景经验只在特定任务类型下召回。这样召回精度能再上一个台阶。另一个方向是经验共享。多个 Agent 实例之间共享经验库一个 Agent 踩过的坑其他 Agent 不用再踩。这在多 Agent 协作场景下价值很大但要注意经验冲突的处理——不同 Agent 可能对同一场景有不同结论需要一套仲裁机制。还有一个我比较看好的方向是经验的反向验证。定期拿历史经验去回放看它是否还成立。不成立的经验自动降权或淘汰。这相当于给经验库做体检保持它的健康度。我个人在实际操作中的体会是hindsight 这套机制的价值不在于技术多复杂而在于它逼着你去想清楚“Agent 到底该从执行中学到什么”。想清楚这个问题实现反而是水到渠成的事。很多团队卡住不是因为技术难是因为没想明白要记什么、怎么用。先把这两个问题回答清楚再动手写代码能少走很多弯路。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进