1. 为什么"能跑"的 Agent 离"越跑越强"还差一整套闭环
我见过太多 Agent 项目卡在同一个地方:Demo 阶段惊艳,上线两周后开始退化。不是模型变笨了,而是它没有一套机制把"跑过的路"沉淀下来。你给它加个新工具,它可能把旧工具用错;你修好一个高频 bug,下次遇到同类问题它照样踩;你换了台机器部署,之前积累的偏好、案例、修正记录全丢了。这就是"能跑"和"越跑越强"之间的鸿沟。
所谓Agent 自进化,说白了就是让 Agent 在真实任务流里形成"评测发现问题 → 记忆沉淀经验 → Skill 固化能力 → 再评测验证"的工程闭环。注意这里的关键词是工程闭环,不是"让模型自己反思一下"这种玄学。反思只是闭环里的一个动作,真正难的是把评测、记忆、Skill 更新这三件事串成一条可观测、可回滚、可复现的流水线。
这篇内容适合三类人看:一是正在搭 Agent 框架、被"记忆到底怎么存"折磨的开发者;二是手里有评测集但不知道怎么和线上反馈打通的工程同学;三是想把零散 prompt 和工具调用整理成可复用 Skill 的团队。我会按"评测怎么建 → 记忆怎么分层 → Skill 怎么更新 → 闭环怎么串起来不崩"这条主线讲,中间穿插我自己踩过的坑和具体参数选择。
先给一个整体心智模型,后面所有细节都挂在这上面:
| 环节 | 核心问题 | 产出物 | 失败信号 |
|---|---|---|---|
| 评测 | 怎么知道它变好还是变坏 | 评测集 + 打分器 | 分数涨了但线上投诉变多 |
| 记忆 | 经验存在哪、怎么取 | 短期上下文 + 长期库 | 检索到无关记忆、污染上下文 |
| Skill | 怎么把经验固化成能力 | 可调用 Skill 单元 | Skill 越加越多、互相冲突 |
| 闭环 | 三者怎么自动流转 | 流水线 + 回滚机制 | 更新后整体退化、无法定位 |
这张表是我做项目时的"体检表",任何一个环节的失败信号出现,就说明闭环有断点。下面逐个拆。
2. 评测集不是越大越好:Agent 评测的构建逻辑与打分器设计
2.1 先分清"能力评测"和"回归评测"
很多人一上来就想搞一个覆盖所有场景的大评测集,结果维护成本爆炸,跑一次要半小时,最后没人愿意跑。我的做法是分两层:
- 能力评测集(Capability Set):用来回答"这个 Agent 到底会不会某类任务"。样本少而精,每个样本代表一类能力,比如"多步工具调用""长文档摘要""歧义澄清"。这类集子更新慢,是北极星指标。
- 回归评测集(Regression Set):用来回答"这次改动有没有把以前能做的搞坏"。样本来自线上真实失败案例,只增不减,每次改动必跑。
能力集我一般控制在 50~150 条,回归集可以到几百上千条但必须能并行跑完。这里有个反直觉的点:回归集的价值远大于能力集。因为能力集涨分往往靠换模型,而回归集才是防止你"改 A 坏 B"的安全网。
2.2 打分器:别只信 LLM-as-Judge
用大模型当裁判很方便,但它有三个坑:位置偏见(偏爱第一个答案)、长度偏见(偏爱长答案)、自我偏好(偏爱和自己风格像的答案)。我在实际项目里的组合是:
- 规则打分:能确定性判断的绝不交给模型。比如工具调用是否命中正确函数、参数 JSON 是否合法、是否包含必须字段。这部分用代码写死,零成本零波动。
- LLM 打分:只用于主观质量,比如回答是否切题、语气是否合适。关键是打分 prompt 要固定版本,和被测 Agent 的 prompt 分开管理,否则你分不清是 Agent 变了还是裁判变了。
- 人工抽检:每周抽 20~30 条 LLM 打分结果复核,算一个"裁判一致率"。如果一致率低于 85%,说明打分 prompt 需要修,而不是 Agent 有问题。
提示:LLM 打分一定要做成对比较而不是绝对打分。让裁判在"A 和 B 哪个更好"里选,比让它打 1~10 分稳定得多。绝对分数在不同批次之间根本不可比。
2.3 评测集构建的实操路径
从零建评测集,我推荐这条路径,成本最低:
- 第一周:手动写 30 条"理想任务",覆盖你 Agent 的核心场景。每条包含输入、期望行为、必须满足的硬约束。
- 第二周起:把线上真实请求脱敏后按失败类型归类,每类挑 5~10 条进回归集。失败类型可以粗分为:工具选错、参数错、多轮丢上下文、幻觉编造、该问不问。
- 持续:每次线上出现新失败模式,先补一条回归样本,再修问题。先有测试再有修复,这个顺序不能反,否则你永远不知道修没修好。
评测集的存储我建议用最简单的 JSONL,一行一条,字段固定:id、input、expected_behavior、hard_constraints、tags、source(manual/online)、added_at。别急着上数据库,JSONL 配合 git 就能做版本管理,diff 清晰,回滚方便。
3. 记忆分层:短期上下文、长期库与"该记什么"的判断
3.1 记忆不是越多越好,是要分层
Agent 记忆最容易被做成一坨:把所有历史对话塞进向量库,检索时 top-k 一捞,结果捞出一堆无关内容污染上下文。我现在的分层是这样的:
- 工作记忆(Working Memory):当前任务的完整上下文,就是对话历史加中间工具结果。容量受模型上下文窗口限制,需要主动压缩。
- 情景记忆(Episodic Memory):过去完成的具体任务记录,包括任务描述、采取的动作、最终结果。用于"我以前遇到过类似情况吗"。
- 语义记忆(Semantic Memory):从多次情景中提炼出的稳定知识,比如"这个 API 的分页参数是 page_size 不是 limit"。用于"我知道这个事实"。
- 程序记忆(Procedural Memory):固化成 Skill 的操作流程,这个下一节展开。
这四层对应不同的存储和检索策略。工作记忆放内存,情景记忆放向量库,语义记忆放结构化存储(键值或小表),程序记忆放 Skill 注册表。混在一起存是灾难的开始。
3.2 短期记忆的压缩策略
上下文窗口再大也会满,压缩是必须的。我试过几种方案,实测下来最稳的是滑动窗口 + 摘要锚点:
- 保留最近 N 轮完整对话(N 取 6~10,看任务复杂度)。
- 更早的内容压缩成一段结构化摘要,包含:任务目标、已完成步骤、关键决策、未解决问题。
- 摘要不是让模型自由发挥,而是用固定模板填充,保证信息不丢。
摘要模板长这样:
任务目标:<一句话> 已完成:<步骤列表,每步一行> 关键决策:<决策 + 理由> 当前状态:<进行中/阻塞/待确认> 未解决:<问题列表>用固定模板的好处是可解析。你可以在下一轮把摘要里的"未解决"单独拎出来提醒 Agent,而不是让它自己从一大段自然语言里找。
3.3 长期记忆的写入与检索
长期记忆最大的坑是写入太随意。我的原则是:不是所有对话都值得记,只有满足以下条件之一的才写入:
- 任务成功完成且过程非平凡(用了 3 步以上工具调用)。
- 出现了明确的纠错(用户说"不对,应该是……")。
- 提炼出了可复用的事实或偏好。
写入时一定要带元数据:时间戳、任务类型、成功与否、来源。检索时不能只靠向量相似度,要加过滤条件。比如检索情景记忆时,优先同任务类型、优先近期、优先成功的。
关于时间衰减,热词里提到"记忆=score+时间半衰期",这个思路是对的。我的打分公式大致是:
final_score = similarity * w1 + recency_score * w2 + success_bonus * w3其中recency_score = 0.5 ** (days_elapsed / half_life),半衰期我一般设 30 天。success_bonus成功任务加 0.1,失败任务减 0.1。权重 w1/w2/w3 我常用 0.6/0.3/0.1,但这个要按你的场景调——如果任务高度依赖历史偏好,recency 权重可以调高。
注意:检索回来的记忆一定要限量。我一般最多取 3~5 条,且总长度不超过上下文窗口的 15%。记忆塞太多,Agent 反而会抓不住当前任务重点,这是实测出来的。
3.4 记忆的跨环境迁移问题
热词里反复出现"换账号/换电脑如何保留记忆",这其实是记忆存储设计的问题。如果你的记忆存在本地文件或某个账号绑定的云端,迁移就是噩梦。我的做法是记忆与运行环境解耦:
- 记忆统一存到一个独立的存储层(可以是本地目录 + 同步,也可以是自建服务)。
- Agent 通过标准接口读写记忆,不关心底层在哪。
- 记忆导出用标准格式(JSONL + 元数据),导入时做去重和冲突检测。
这样换环境时,只要把记忆库搬过去,Agent 立刻"恢复记忆"。冲突检测的逻辑是:同 id 保留新的,不同 id 合并,语义重复的做一次去重。
4. Skill 更新:把经验固化成可调用单元的正确姿势
4.1 Skill 到底是什么,和工具的区别在哪
很多人把 Skill 和工具(Tool/Function)搞混。我的定义是:工具是原子能力(查天气、发邮件),Skill 是完成一类任务的编排逻辑("给客户发跟进邮件"这个任务包含查客户信息、查历史沟通、起草、发送四步)。工具是动词,Skill 是"动词的套路"。
所以 Skill 更新不是加一个新函数,而是把"某类任务该怎么做"这套知识固化下来。它通常包含:触发条件、步骤序列、每步用哪个工具、参数怎么填、异常怎么处理。
4.2 Skill 从哪来:三条生成路径
- 人工编写:最可靠,适合核心高频任务。缺点是慢。
- 从成功轨迹提炼:Agent 成功完成一个非平凡任务后,把轨迹抽象成 Skill 草稿。这是自进化的关键。
- 从失败修正提炼:用户纠错后,把"正确做法"固化成 Skill 或修正已有 Skill。
第二条路径最有价值也最难。我的做法是:任务成功后,用一个独立的"提炼器"(可以是另一个 LLM 调用)把轨迹转成 Skill 草稿,格式固定:
{ "skill_name": "customer_followup_email", "trigger": "用户要求给某客户发跟进邮件", "steps": [ {"action": "get_customer_info", "params": {"customer_id": "$customer_id"}}, {"action": "get_communication_history", "params": {"customer_id": "$customer_id", "limit": 5}}, {"action": "draft_email", "params": {"context": "$history"}}, {"action": "send_email", "params": {"to": "$customer_email", "body": "$draft"}} ], "exceptions": [ {"condition": "客户信息不存在", "handler": "询问用户确认客户标识"} ] }注意草稿不能直接用,必须经过验证才能进 Skill 库。验证方式就是拿它去跑回归集里相关任务,通过率达标才准入。
4.3 Skill 库的版本管理与冲突处理
Skill 越加越多必然冲突。两个 Skill 触发条件重叠、步骤矛盾,Agent 就懵了。我的管理策略:
- 每个 Skill 带版本号,更新是新增版本而非覆盖,旧版本保留可回滚。
- 触发条件做互斥检查:新 Skill 入库前,扫描现有 Skill 的 trigger,如果语义相似度超过阈值(我常用 0.85),强制人工确认是替换还是并存。
- Skill 有优先级和适用范围:不是所有 Skill 都全局可用,按任务类型打标签,检索时先按标签过滤。
| 冲突类型 | 表现 | 处理方式 |
|---|---|---|
| 触发重叠 | 两个 Skill 都想响应同一请求 | 提高优先级或合并 |
| 步骤矛盾 | 同一任务两种做法 | 保留通过率高的,另一个降级 |
| 参数不兼容 | 工具签名变了 Skill 没更新 | 版本绑定,工具变更触发 Skill 复审 |
| 覆盖不全 | 新场景无 Skill 可用 | 走通用流程并记录,事后提炼 |
4.4 Skill 更新的触发时机
不是每次任务成功都更新 Skill,那样会爆炸。我的触发条件是:
- 同一类任务连续成功 3 次以上且没有对应 Skill,才提炼新 Skill。
- 已有 Skill失败率超过 20%(按周统计),触发复审。
- 用户明确纠错,立即触发对应 Skill 的修正流程。
这个阈值是调出来的。太低会导致 Skill 库噪声大,太高会错过进化机会。3 次和 20% 是我在中等复杂度任务上的经验值,你可以按自己场景微调。
5. 闭环怎么串:让评测、记忆、Skill 自动流转而不互相打架
5.1 闭环的数据流
把前面三块串起来,数据流是这样的:
- 线上任务执行,产生轨迹。
- 轨迹进评测流水线,打分并归类。
- 成功且非平凡的轨迹 → 提炼情景记忆 + Skill 草稿。
- 失败轨迹 → 进回归集 + 触发相关 Skill 复审。
- Skill 草稿/更新 → 跑回归集验证 → 通过则入库。
- 新 Skill 和记忆 → 影响后续任务执行 → 回到第 1 步。
这条链路里最容易断的是第 5 步。很多人提炼了 Skill 直接就用,结果引入回归。验证环节不能省,哪怕只是跑一个 20 条的小回归集。
5.2 防止"自进化"变成"自退化"
自进化最大的风险是正反馈失控:Agent 自己提炼的 Skill 有偏差,用这个偏差 Skill 又产生更多偏差轨迹,越滚越歪。三个防护措施:
- 准入门槛:Skill 入库必须过回归集,通过率不低于现有基线。
- 灰度发布:新 Skill 先在小流量任务上用,观察一周再全量。
- 一键回滚:Skill 库和记忆库都要有快照,出问题能退回上一个稳定版本。
我踩过最惨的一次坑:一个从成功轨迹提炼的 Skill 在特定边界条件下会死循环调用工具,因为提炼时没覆盖那个边界。后来加了"单任务工具调用次数上限"作为硬约束,任何 Skill 超过 15 次调用就强制中断并上报。这个上限救过我好几次。
5.3 可观测性:没有日志就没有闭环
闭环要能运转,前提是每一步都可观测。我必埋的日志点:
- 每次任务:输入、用到的 Skill、工具调用序列、最终结果、耗时、token 消耗。
- 每次记忆检索:查询、召回的记忆 id、最终用了哪几条。
- 每次 Skill 更新:新旧版本、触发原因、回归集通过率。
- 每次评测:批次 id、各指标分数、和上一批的 diff。
这些日志用结构化格式(JSON)打到统一的地方,方便做聚合分析。我一般用一个简单的看板展示:Skill 库规模趋势、回归集通过率趋势、记忆命中率、平均任务步数。这四个指标任何一个异常波动,就说明闭环某处出问题了。
5.4 一个最小可跑的闭环实现
如果你现在什么都没有,我建议按这个顺序搭,两周能跑起来最小闭环:
- 第 1~2 天:定义轨迹日志格式,所有任务执行都落盘。
- 第 3~4 天:手写 30 条回归样本,写一个规则打分脚本。
- 第 5~7 天:实现情景记忆的写入和检索,先用最简单的向量库。
- 第 8~10 天:实现 Skill 的 JSON 格式和注册表,手动加 2~3 个 Skill。
- 第 11~12 天:写一个提炼器,把成功轨迹转 Skill 草稿。
- 第 13~14 天:把验证环节接上,草稿必须过回归集才能入库。
这个最小闭环跑通后,再逐步加时间衰减、冲突检测、灰度发布这些高级特性。先跑通再优化,别一上来就设计完美架构,那样永远上不了线。
6. 几个我反复踩过的坑和对应的解法
6.1 记忆污染:检索到"看起来相关"但实际无关的内容
向量检索的相似度高不等于有用。我遇到过检索出语义相似但任务类型完全不同的记忆,Agent 照着做直接跑偏。解法是混合检索:向量相似度 + 元数据过滤(任务类型、时间范围、成功状态)。元数据过滤能砍掉大部分噪声,剩下的再按相似度排。
6.2 Skill 膨胀:加了 200 个 Skill 反而更笨
Skill 太多,Agent 选择困难,触发准确率下降。解法是分层 Skill:高频核心 Skill 常驻,长尾 Skill 按需加载。具体做法是给 Skill 打场景标签,任务开始时先判断场景,只加载该场景下的 Skill。我实测把常驻 Skill 控制在 20 个以内,触发准确率明显回升。
6.3 评测和线上脱节:分数涨了体验没涨
评测集如果全是自己编的理想样本,和线上真实分布差太远。解法是定期用线上流量刷新评测集,保证评测集里至少有 50% 来自真实请求。另外评测指标要和业务指标对齐,比如你关心的是任务完成率,就别只盯着回答质量分。
6.4 更新不可回滚:改坏了只能干瞪眼
早期我没做版本管理,一次 Skill 更新把线上搞挂,只能手动改回来。现在所有 Skill 和记忆库都做快照,每次更新前自动备份,出问题一条命令回滚。这个成本极低,但救命。
7. 关于这套闭环,我个人的几点体会
搭这套东西两年多,最大的感受是:Agent 自进化不是让模型自己变聪明,而是让工程系统帮它把经验管好。模型能力是天花板,闭环决定你能不能摸到天花板。我见过模型很强但闭环稀烂的项目,上线就退化;也见过模型一般但闭环扎实的项目,越跑越稳。
如果只让我保留一个环节,我会保留回归评测集。它是整个闭环的地基,没有它,记忆和 Skill 的更新都是盲目的。记忆和 Skill 是上层建筑,可以慢慢加,但评测集必须从第一天就有。
最后一个实操建议:别追求全自动。我现在的闭环里,Skill 入库和记忆清理这两个环节都保留了人工确认。全自动听起来酷,但一旦出错,排查成本远高于省下的那点人力。半自动、可观测、可回滚,才是能长期跑下去的形态。