让任何制品自己训练自己:我做了一个通用自进化引擎
上个月我在调一个 Prompt,改了大概十几版。
每次改完我都觉得,“这次应该行了”。跑几个测试——有的好了,有的坏了,继续改。改到第八版的时候,我已经不记得第三版为什么被丢掉了,也说不清第六版和第八版到底哪个更好。
这种感觉你应该很熟悉。手工调优就是这样——靠直觉,靠记忆,靠运气。
那段时间我正好在看 Karpathy 的 autoresearch。突然想到一个事儿:他让 AI 自己迭代训练代码,每次改一点,跑指标,好了留下,坏了回滚。这个 Loop 的本质,跟我手工调 Prompt 是同一件事。只不过他用程序跑,我用脑子跑。
然后我又看到 Stanford 的 Meta-Harness 论文,讲的是怎么让 AI 诊断另一个 AI 的失败。再加上 Anthropic 的 skill-creator 提供了现成的评测引擎。
三个东西拼在一起,我觉得有戏。
所以我做了一个 self-evolution Skill。不只是给 Skill 用——而是让任何可评测的制品,Prompt、代码、文档、配置,都能丢进去跑自进化 Loop。制品提供数据和指标,Loop 负责试错和保留,人只需要定义什么叫”好”。
我先把灵感来源说清楚,不然后面的设计你会看不懂。三根柱子。
第一根,Karpathy 的 autoresearch,给了 Loop 的骨架。
今年 3 月 Karpathy 发了一个 630 行的 Python 脚本,就干一件事:让 AI Agent 迭代优化一段 LLM 训练代码。每次改一点,训练 5 分钟,看指标,好了留下,坏了回滚。两天跑了 700 个实验,找到 20 个优化,性能提升 19%。
后来 Udit Goenka 把这个思路泛化成了 Claude Code Skill,提炼出 5 条原则:one metric、constrained scope、fast verification、automatic rollback、git as memory。
我看到这个的时候想的是——这不就是我在干的事吗?
第二根,Stanford 的 Meta-Harness,给了诊断的大脑。
这篇 Paper 解决的是一个很具体的问题:你让 AI 去优化另一个 AI 的时候,该给它看什么?
答案是——完整的原始执行轨迹,而不是分数摘要。
他们的消融实验显示,只给分数,相比给完整 Trace,效果差了 44%。你可以这么理解:让一个医生看完整的病例、化验报告、影像,他能给出好诊断。你把这些压缩成 300 字摘要,他只能靠猜。
这一点对我触动很大。我之前调 Prompt 的时候,知道某个 Case 挂了,但不知道具体挂在哪一步、为什么挂。如果能拿到完整的执行 Trace,诊断的精度会完全不一样。
第三根,Anthropic 的 skill-creator,给了评测引擎的底座。
它提供了 quick_validate 做结构检查,grader 做逐条打分,comparator 做 A/B 盲审,还能自动生成 Ground Truth 测试用例。评测这件事最烦的是从零搭,有现成底座可以直接用。
三根柱子各自解决一个问题:autoresearch 管循环,Meta-Harness 管诊断,skill-creator 管评测。我要做的,是把它们接成一条能跑起来的闭环。而且——不限定在某一种制品上。
为什么不限定在某一种制品上?
我一开始想过只做 Prompt 优化器。但拆到底层就发现,自进化 Loop 需要的输入其实只有三个东西:
第一,Artifact,被改的东西。第二,Ground Truth,测试用例,定义什么叫”好”。第三,Execution Method,执行方法,怎么从制品产出输出、供评测。
Prompt 满足这三个条件,Skill 也满足,代码也满足,甚至一份创业 BP 也满足——只要你能定义什么叫”好”。
所以我没有做”Prompt 优化器”或者”Skill 优化器”,而是定义了五种制品类型。
Prompt 类,就是系统提示词、few-shot 模板,发给 LLM 捕获回复。Skill 类,就是 SKILL.md 加 references 加 scripts,加载 Skill 跑 Claude。Code 类,源代码文件,跑测试或 shell 命令。Idea 类,商业文档、设计方案,LLM 直接评估文档本身。Config 类,YAML、JSON、.env 配置,应用配置后检查系统行为。
这个分类很关键。因为不同类型的制品,Mutation 策略完全不一样。改一个 Prompt 的关键词是表面层,改它的指令逻辑是核心层,把它拆成 chain-of-thought 多步骤是架构层。但改代码的表面层是变量名和常量,核心层是函数实现和算法,架构层是模块结构和接口。
泛化不是把”Skill”改成”Artifact”这么简单。每一层的 Mutation 边界、评测方式、安全检查,都要重新定义。
接下来说核心机制,怎么跑一次自进化。
整个流程分两部分:Phase 0 的一次性准备,加上每轮迭代的 8 个阶段。
Phase 0 做的事比你想的多。
不只是建目录。它要分析制品类型、检查 Git 状态——没 Git 就自动 init。然后准备 Ground Truth 数据,做三路切分:Dev 集 70%,Holdout 集 20%,Regression 集 10%。再跑一次 Baseline 评测,最后生成一份 evolve plan,包含评测策略、门控阈值、起始 Mutation 层。
这份 Plan 决定了后面所有轮次的行为。没有这个 Plan,Loop 就是盲跑。
每轮迭代 8 个阶段,我挑几个值得说的展开。
Phase 1 Review,读记忆。读 results.tsv、experiments.jsonl、上一轮的 Trace。Phase 2 Ideate,基于 Trace 证据提出一个原子化改动。Phase 3 Modify,执行改动。Phase 4 Commit。Phase 5 Verify,三层评测。Phase 6 Gate,五维门控,决定 KEEP 还是 DISCARD。Phase 7 Log,写日志。Phase 8 Loop,继续、升层、或者停止。
Phase 2 有一个硬规矩:没有 Trace 证据不许动手。
每个改动提案必须引用具体的 Trace 文件。比如这样——
“Case 12 失败了,因为 Agent 把离职问题路由到了邮箱分类。Trace 显示模型在第 3 步选择了错误的索引分支。如果我在 root_index 加一个易混淆提示,输出应该从’邮箱’变成’通讯录’。“
没有这种级别的具体证据,就不许改。
这不是形式主义。这是防止”感觉这里可以优化”这种 vibe-based mutation。LLM 特别擅长给你一个看起来合理、但实际没有根据的改动建议。不加约束的话,很多轮次会浪费在这种”感觉对”的修改上。
Phase 4 有一个反直觉的操作:先 Commit,再验证。
为什么不是验证通过了再 Commit?因为 Git 是安全网。每个 Mutation 都有 Commit 记录,即使最后被 Revert 了,审计轨迹也在。你可以回头看第 7 轮的改动到底改了什么、为什么被丢弃。这个信息在后面的 Review 阶段会用到。
还有一个原子性测试:描述这个改动的时候,如果需要用”和”字,就该拆成两轮。改完之后如果 diff 超过 5 个文件,大概率不是原子的。
说说三层评测。核心思路是:便宜的先跑,贵的后跑。
第一层,Quick Gate,秒级,每轮都跑。纯程序检查,不调 LLM。检查制品结构是否完整、安全扫描——硬编码密钥、危险删除命令,一共 14 条规则、随机抽 3 个 GT Case 验结构。两条 Critical 规则不过直接阻断。坏的迭代,成本压到最低。
第一层挂了就不跑第二层。这很重要。如果每次都跑完整评测再告诉你结构就有问题,Token 烧得冤。
第二层,Dev Eval,分钟级,每轮都跑。拿全量 Dev 集逐条跑。8 种 Assertion 类型,6 种程序直接判——contains、not_contains、regex、file_exists、json_valid、script,2 种需要 LLM 做 YES/NO 分类——llm_judge 和 fact_coverage。结果写入 per-case JSON,供下一轮诊断用。
第三层,Strict Eval,大概 10 分钟,条件触发。只在三种情况下跑:每 N 轮自动触发、Dev pass rate 超过阈值、层晋升之前。它跑的是 Holdout 集,就是优化器从来没见过的数据。如果 Holdout pass rate 比 Dev 低 15% 以上,说明过拟合了。
Holdout 集的存在,是从 ML 训练直接搬过来的。Dev 集上分数涨了,不代表制品真的变好了,可能只是在”背答案”。Holdout 是你留的一手,用来验证泛化能力。
然后说五维 AND 门控。
每一轮改动都要过 5 道关,全部 PASS 才保留,任何一个 NO 就自动 git revert。
第一,结构——第一层评测过了吗?第二,进步——pass rate 大于等于历史最佳吗?第三,回归——有没有之前过了现在挂了的 Case?第四,成本——Token 和时间在 Baseline 两倍以内吗?第五,安全——有没有新的安全违规?
为什么是 AND,不是加权求和?
加权求和的问题是这样的:质量涨了 10%,但 Token 消耗翻倍——加权求和可能给你 PASS。pass rate 涨了 5%,但有 2 个 Case 回归——加权求和也可能给你 PASS。
AND 逻辑不会。每个维度必须独立通过。没有”拿一个维度的高分,补另一个维度的低分”这种操作。
这个决定在实践中被证明是对的。最常见的陷阱就是:改了一个地方修好了 3 个 Case,但悄悄搞坏了 1 个。加权求和会把这种改动放过去,AND 门控会拦住。
回归维度还有一个噪声处理。如果恰好只有 1 个 Case 回归,同时其他几个 Case 改善了,会把那个回归的 Case 跑 3 次。三次里两次通过就算 LLM 噪声,不算真回归。两次失败才算真回归。
因为 LLM 评测本身有随机性。同一个 Skill 状态、同一份 GT,Claude 跑 4 次 pass rate 可以在 0.79 到 0.92 之间漂移。
再说分层 Mutation。核心思路是:从最便宜的地方开始改。
这个设计类比的是 ML 里的学习率调度——先大幅调整便宜参数,再精细调整贵参数。
三层。
Layer 1,表面层,最便宜、风险最低。对 Prompt 来说是措辞和格式,对代码来说是变量名和常量值,对文档来说是标题和用词。改这些不太可能搞坏什么。
Layer 2,核心层,主要逻辑。Prompt 的指令逻辑、代码的函数实现、文档的论证结构。中等成本,中等风险。
Layer 3,架构层,最贵的。Prompt 拆成多步 chain-of-thought,代码改模块结构,文档重新定位目标受众。高风险,但有时候不动架构就是改不动了。
规矩是这样的:从 Layer 1 开始,在当前层连续 3 次 DISCARD 才升到下一层。不准跨层,一次迭代只能改一层的东西。三层都试过都没改善,迭代结束。
这里有一个实际观察:大部分改善发生在 Layer 1 和 Layer 2。Layer 3 的改动风险太高,成功率明显低于前两层。但有时候问题确实出在架构上,Layer 1 和 2 怎么改都改不动,升到 Layer 3 一刀切,反而解决了。
设计想清楚了,工程落地我决定不硬依赖任何外部框架。
skill-creator 很好用,但我不想把整个系统绑死在上面。万一用户要优化的是一段 Python 代码,装一套 skill-creator 就显得多余了。
所以我写了三个独立的 Python 脚本。
第一个,evaluate_assertions.py,处理 6 种程序化 Assertion。LLM 类型的标记为 SKIPPED,交给上层处理。
第二个,structural_check.py,做第一层的结构和安全检查。14 条安全规则扫描,按制品类型做针对性检查。
第三个,results_tracker.py,管理 results.tsv 和 experiments.jsonl 两个日志文件。
GT 格式我做了兼容——如果用户已经有 skill-creator 的 evals.json,直接能用。同时扩展了 contains、regex、script_check 等前缀语法,在一个 expectations 数组里混用程序化和 LLM 评测。
Trace 的处理也值得说一句。我没有把完整 Trace 塞进 Prompt——那可能是几万 Token 的执行记录。Phase 2 的 Proposer 只会拿到 Trace 文件的路径,需要哪个自己去读。这是 Meta-Harness 思路的工程化:给地图,不给全文。
最后说说踩过的坑,和还没解决的问题。
第一个,LLM 评测的噪声。这个问题是我跑前几轮就撞上的。同一份制品、同一套 GT,跑 3 次结果能差出好几个百分点。你改了一行文本,pass rate 从 0.85 变成 0.87——这是你的功劳,还是 LLM 随机波动?分不清。我的处理方式是:优先用程序化 Assertion,LLM 评测的跑 3 次取多数票,Temperature 固定 0。不完美,但比裸跑好。
第二个,GT 质量是天花板。GT 标注本身有问题的话,无论跑多少轮都修不好那个 Case。我加了一条规则:如果一个 Case 连续 5 轮在所有层都没被修复,先怀疑 GT 而不是制品。标记为”不可修”,排除出评测集。
第三个,成本。有些制品类型,比如 Skill 需要启动 Claude 进程,每个 Case 的执行成本不低。跑一轮完整的 L2 Eval 如果有 30 个 Case,光 Token 就不少。L3 跑一次大概 10 分钟。跑个十几轮下来,钱包是能感受到的。这不是免费的午餐。
第四个,前几轮需要人盯。前 3 到 5 轮最好看一眼。不是不信任系统,是 experiments.jsonl 里的记忆还不够厚。Proposer 在没有足够历史数据的时候,有时候会提出重复的改动方向,或者在错误的层级上尝试。等跑了 5 轮以上,成功和失败的 Pattern 积累起来了,后面的迭代方向会准很多。
第五个,Idea 类型的评测特别难。技术类制品有硬指标,script Assertion 跑出来就是过或不过。但 Idea 类型——比如一份商业计划书——几乎全靠 llm_judge 和 fact_coverage,噪声大很多。而且有一个 Assertion Gaming 的问题:LLM 为了过 fact_coverage,会加一句凑数的话,但内容质量并没有真正提升。我在 Ideate 阶段加了一条要求——“深度优先,不许刷分”。但说实话,这条规则执行起来还是靠 LLM 的判断力,不算完全可靠。
说到最后。
这个工具让我改变了一个看法。
以前我觉得 Prompt Engineering 是门手艺。靠经验,靠直觉,靠反复试。
现在我觉得它更像训练。有数据、有指标、有反馈循环。你定义好 GT,剩下的事情让 Loop 去跑。你不需要猜”这个改动是不是好了”,门控函数会告诉你。你不需要手动回滚”那个改坏了的版本”,git revert 自动执行。
这个转变对我来说挺大的。
但我也想说一句诚实的话。这个系统不会替你想清楚”好的制品应该长什么样”。GT 是你写的,指标是你定的,制品类型是你选的。它只是帮你在你定义的搜索空间里,比你自己更系统地试错。如果你的 GT 本身就偏了,它会非常忠实地朝着错误的方向迭代。
该想清楚的事情,还是得你自己想。