别再手动写提示词了 — SkillOpt 让技能文档自己进化
别再手动写提示词了 — SkillOpt 让技能文档自己进化
你有没有注意到一个挺有意思的矛盾?大语言模型本身越来越强,冻结在那里不动,能力已经够用了。但要让它们在某个具体任务上真正“精通”,我们却还在用最原始的办法——手写提示词。一个领域专家花几周时间打磨出一份技能文档,模型用它跑起来表现不错,可换个场景就抓瞎了,你又得从头再来。更讽刺的是,模型自己明明能看到它在哪些地方做错了,但你偏偏不让它从错误中学习。
最近看到的一个项目SkillOpt,就是在做一件我一直觉得早就该有人做的事:把自然语言写的技能文档当作“权重”,在不触碰模型参数的前提下,让这份文档像训练神经网络一样,自己进化。
背景:强模型 + 弱技能的困境
GPT-5.x、Claude、Qwen 这些模型,能力已经强到不需要再证明什么了。给它们一份精心设计的技能文档——也就是一个描述“你应该怎么一步步做这件事”的 markdown 文件——模型在 QA、具身交互、代码生成、数学推理等任务上的表现会有显著提升。但问题来了:这份文档,谁来写?
再来看看现有的三种主流思路。它们各自都有自己的逻辑,但走到今天,也都卡在了同一个地方。
人工编写(Human skill)
LLM 一次性生成(LLM skill)
梯度类比优化(TextGrad)
这三种方案不是“烂”——在它们各自的设计范围内,都有道理。人工方案在稳定场景下够用,一次性生成方案在不需要迭代的场景下够快,TextGrad 在探索文本优化可能性时也有价值。但当你需要的是那种能持续进化的技能——从失败中学习、在成功中巩固、在跨 epoch 中积累经验——它们都遇到了瓶颈。
技术诞生:从“训练模型”到“训练文档”
SkillOpt 不是凭空冒出来的。它的推导链条其实很清晰:
模型权重不能改 → 但模型行为必须能改 → 行为由技能文档驱动 → 技能文档是自然语言 → 自然语言可以被 LLM 编辑 → 那就让技能文档像权重一样经历“训练循环”。
这个推导的核心转折点,是把“训练”这个动作从权重空间搬到了文本空间。神经网络训练里的前向传播、反向传播、梯度裁剪、验证集评估,每一步都在文本空间里找到了对应的东西。这可不是比喻游戏——它是结构映射。Rollout 对应前向传播(用当前技能在环境中执行任务),Reflect 对应反向传播(分析失败轨迹生成“文本梯度”),Aggregate 对应梯度聚合(合并多个 minibatch 的 patch 建议),Select 对应梯度裁剪(排序并裁剪 edits),Update 对应参数更新(把 edits 应用到技能文档),Evaluate 对应验证集评估(纯函数,决定 accept 还是 reject)。再加上 Slow Update 对应 epoch 级别的纵向修正,Meta Skill 对应优化器状态记忆——一套完整的训练隐喻,被忠实映射到了文本空间。
项目长什么样
SkillOpt 是一个 Python 库 + CLI 工具。你通过 skillopt-train 命令启动训练,它读取 YAML 配置,在 6 个 benchmark 环境上迭代优化技能文档,最终产出进化后的 markdown 文件。技术栈方面:Python,核心依赖 openai(LLM 后端)、pyyaml(配置加载)、numpy(分数统计)、azure-identity(认证)。辅助依赖包括 alfworld(仿真环境)、claude-agent-sdk(Claude 后端)、vllm(本地推理)、gradio(WebUI 仪表盘)。
它的目录结构直接映射了训练隐喻:
| 目录 | 角色 | 对应的 NN 训练概念 |
|---|---|---|
skillopt/engine/ | 管线编排 | Training loop runner |
skillopt/gradient/ | 梯度计算 | Backward pass |
skillopt/optimizer/ | 参数更新 | Optimizer (SGD/Adam 等) |
skillopt/evaluation/ | 验证门控 | Validation evaluation |
skillopt/model/ | LLM 后端 | GPU / compute |
skillopt/envs/ | 环境适配器 | Dataset loader + task env |
skillopt/datasets/ | 数据加载 | Data pipeline |
skillopt/prompts/ | 提示词 | Loss function template |
skillopt/utils/ | 工具 | Utils |
skillopt/config.py | 配置引擎 | Hyperparameters |
skillopt/types.py | 类型系统 | Tensor shapes |
scripts/ | CLI 入口 | Training script |
configs/ | 配置文件 | Config files |
skillopt_webui/ | 可视化 | TensorBoard |
项目规模中等偏大——核心管线模块约 15 个文件,6 个 benchmark 环境适配器,加上 WebUI 和配置系统。这不是一个周末 hack 出来的,是一个完整的训练框架。
它能做什么
SkillOpt 有六个核心能力,按功能域来分:
训练管线域
skillopt/engine/trainer.py:516)。6 阶段迭代:Rollout → Reflect → Aggregate → Select → Update → Evaluate。每一步接收上一步的输出,epoch 级别再追加 Slow Update 和 Meta Skill。梯度计算域
skillopt/gradient/reflect.py:438) 和层级化 Aggregate (skillopt/gradient/aggregate.py:143)。前者批量分析轨迹,发现跨轨迹的共同模式;后者用 MapReduce 的方式并行合并多个 minibatch 的 patch 建议。参数更新域
skillopt/optimizer/skill.py)、Gradient Clipping (skillopt/optimizer/clip.py)、Rewrite (skillopt/optimizer/rewrite.py)、Slow Update (skillopt/optimizer/slow_update.py:302)、Meta Skill (skillopt/optimizer/meta_skill.py:33)、LR Scheduler (skillopt/optimizer/scheduler.py)、Autonomous LR (skillopt/optimizer/lr_autonomous.py)。三种更新模式(patch / rewrite_from_suggestions / full_rewrite_minibatch),四种 LR 策略(constant / linear / cosine / autonomous)。验证门控域
skillopt/evaluation/gate.py:31)。3-way 比较:candidate > current AND > best → accept_new_best;> current → accept;否则 → reject。而且强制不可禁用。环境适配域
基础设施域
_base_ 继承)、数据加载(BaseDataLoader/SplitDataLoader)、LLM 后端(多路由 + optimizer/target 分离)、WebUI(Gradio 仪表盘)。这些功能构成了两条完整管线:主训练循环(6 阶段 step 级迭代)和 epoch 级慢更新(Slow Update + Meta Skill 纵向修正)。
它怎么做到的
ReflACT 训练循环:6 阶段管线
整个训练循环从 ReflACTTrainer.train (skillopt/engine/trainer.py:516) 启动。每一步走完 6 个阶段,然后 epoch 级追加 Slow Update 和 Meta Skill。调用链是这样的:
train(trainer.py:516) →① Rollout: adapter.rollout → list[RolloutResult]② Reflect: run_minibatch_reflect(reflect.py:438) → list[RawPatch]③ Aggregate: merge_patches(aggregate.py:143) → merged Patch④ Select: rank_and_select(clip.py:25) / decide_autonomous_learning_rate → ranked Patch⑤ Update: apply_patch_with_report(skill.py:128) → candidate_skill (str)⑥ Evaluate: evaluate_gate(gate.py:31) → GateResult
①
Rollout
②
Reflect
fmt_minibatch_trajectories (reflect.py:108)——多条轨迹合并为一次 LLM 输入,让 analyst 不只看单个失败,而是看“这些任务都犯了什么共同的错”。③
Aggregate
_hierarchical_merge (aggregate.py:70)——while 循环每轮按 batch_size 切分 patch 为批次,ThreadPoolExecutor 并行 _merge_batch,直到只剩 1 个。④
Select
⑤
Update
_apply_edit_with_report (skill.py:48)——5 种字符串操作:append(追加到末尾)、insert_after(在指定文本后插入)、replace(替换指定文本)、delete(删除指定文本)、以及 slow_update 区域保护。最后一种很关键——step 级的 edits 不能修改 Slow Update 保护区( 到 之间的内容)。短期修改尊重长期指导。⑥
Evaluate
gate.py:31) 是纯函数——比较 candidate hard score、current hard score、best hard score,三路决策。candidate > current AND > best → accept_new_best(同时更新 current 和 best);candidate > current → accept(只更新 current);否则 → reject(什么都不改)。这个 gate 强制不可禁用——配置 use_gate=false 直接 raise ValueError。设计哲学很明确:没有验证的优化是不负责任的优化。Minibatch Reflect:发现跨轨迹模式
Reflect 阶段不是逐条分析每条失败轨迹。run_minibatch_reflect (reflect.py:438) 先 shuffle 再按 minibatch_size 分组,每组多条轨迹合并为一次 LLM 输入。ThreadPoolExecutor 并行跑 error_analyst 和 success_analyst。核心变换在 fmt_minibatch_trajectories (reflect.py:108)——不是一条一条喂给 LLM,而是把多条轨迹拼成一段文本,让 analyst 看到模式:"80% 的失败都因为没检查边界条件",比"这次失败因为没检查边界条件"有更大说服力。
Slow Update + Meta Skill:epoch 级纵向优化
每步的 6 阶段是横向迭代——改一版技能,看分数变了没有。但技能的进化不只是横向的。相邻 epoch 的技能在同样本上的表现对比,能揭示更深层的规律:哪些错误是旧版犯的而新版修好了(improved)、哪些是新版新犯的(regressed)、哪些一直犯(persistent_fail)、哪些一直没问题(stable_success)。
Slow Update (slow_update.py:302) 是 target-facing guidance——给技能本身写指导。build_comparison_pairs (slow_update.py:152) 把前版和现版技能在同样本上的表现分成四类(improved / regressed / persistent_fail / stable_success),然后 LLM 分析这些对比,输出一段纵向指导文本,注入到技能文档的 保护区域。Step 级的 edits 不能修改这个区域——短期修改尊重长期指导。
Meta Skill (meta_skill.py:33) 是 optimizer-facing memory——不修改技能本身,而是从纵向对比中提炼优化器指导。输出写入独立 JSON 文件,被 format_meta_skill_context 渲染为 prompt 上下文块,供下一步 Reflect/Aggregate/Select 的 optimizer 消费。相当于优化器有了"上次我犯了什么错、什么策略有效"的记忆。
两条链的关键差异:
- Slow Update 输出写入技能文档保护区(target-facing),Meta Skill 输出写入独立 JSON(optimizer-facing)
- Slow Update 在 epoch >= 2 时必执行,Meta Skill 需额外配置 use_meta_skill=True
- Slow Update 的内容会被后续 rollout 读到(影响模型行为),Meta Skill 的内容只被 optimizer 读到(影响优化策略)
数据流向
SkillOpt 有三条数据管线:
主管线(每步 6 阶段)
YAML config → load_config(config.py:244) → flat dict → ReflACTTrainer.train(trainer.py:516)BatchSpec → adapter.rollout → list[RolloutResult]→ adapter.reflect → list[RawPatch]→ _normalise_patches → failure + success patches→ merge_patches → merged Patch→ rank_and_select → ranked Patch→ apply_patch_with_report → candidate_skill (str)→ evaluate_gate → GateResult→ accept/reject → 更新 current_skill / best_skill
Epoch 级慢更新管线
prev_skill + curr_skill + sample items→ adapter.rollout(prev) + adapter.rollout(curr)→ build_comparison_pairs → 4类(improved/regressed/persistent_fail/stable_success)→ run_slow_update → dict(reasoning, slow_update_content)→ replace_slow_update_field → updated_skill (注入保护区)
数据加载管线
YAML/JSONL → load_raw_items → list[dict]→ ratio split + shuffle → train/val/test→ plan_train_epoch → list[BatchSpec]
核心数据结构扮演不同角色:BatchSpec 是输入载体(批次请求规格),RolloutResult 是中间状态(单个任务执行结果),Edit/Patch/RawPatch 是中间状态(编辑建议),GateResult 是最终输出(门控决策),SlowUpdateResult 是最终输出(epoch 级慢更新结果)。数据从 YAML 配置流入,经过 6 阶段变换,最终产出 accept 或 reject 的决策——如果 accept,candidate skill 成为新的 current skill;如果 reject,什么都不改。
技能文档本身以 markdown 文件形式持久化,带版本号 skill_vXXXX.md。这是另一个关键设计决策:人可读比二进制格式重要。你随时可以打开技能文件看到它在说什么,直接手动修改也行——它就是一个 markdown 文件,只不过这个文件在被自动训练。
项目评价
做得好的
训练隐喻的一致性是最值得称道的设计。不是随便用了几个术语凑个类比——每个 NN 训练概念都有忠实的文本空间映射,而且映射后的实现是完整可运行的。Rollout 真的是“前向传播”(用当前参数跑任务),Reflect 真的是“反向传播”(分析失败生成修改建议),Gate 真的是“验证集评估”(不通过就 reject)。这不是概念包装,是结构映射。
Optimizer/Target backend 分离也很聪明。optimizer 用推理分析力强的模型(比如 GPT-5.x)来分析轨迹、生成 edits,target 用任务执行力强的模型来实际执行任务。反思和执行需要不同能力——让同一个模型既做反思又做执行,就像让一个人同时当教练和选手。
Gate 强制不可禁用是最让人信服的设计决策。没有验证的优化就是瞎改——可能一步改好、下一步改崩。Gate 确保只有确实变好了才 accept,否则 reject 回退。这不是限制,是保护。
还不够的
全项目没有任何测试。 这是最大风险。6 阶段管线、3 种更新模式、4 种 LR 策略、epoch 级慢更新——这么多路径组合,零测试意味着每个组合只靠手动验证。_apply_edit_with_report 的 5 种字符串操作出了边界 case 你不会知道,直到生产环境里 silently skip。
Skill patch 依赖字符串精确匹配。 replace 和 delete 操作的 target 必须字面出现在技能文档中。如果 LLM 生成的 edit 里 target 字符串有一点点偏差——多一个空格、少一个换行——操作就 silently skip,没有任何日志告诉你它跳过了。这在真实使用中是高频失败点。
Optimizer backend 只支持 openai_chat 和 claude_chat。 不支持 qwen/codex。如果你用本地模型跑 target,optimizer 还得走 API——这对成本和延迟都有影响。
可配置的能力边界
| 配置项 | 默认值 | 作用 |
|---|---|---|
train.num_epochs | 4 | 训练 epoch 数 |
train.batch_size | 40 | 每 step 样本数 |
optimizer.learning_rate | 4 | 每 step 最多 edits 数 |
optimizer.lr_scheduler | cosine | 编辑预算调度策略 |
optimizer.skill_update_mode | patch | 技能更新方式(patch/rewrite) |
optimizer.use_slow_update | true | 是否启用 epoch 级纵向更新 |
optimizer.use_meta_skill | true | 是否启用 optimizer 端元技能记忆 |
gradient.minibatch_size | 8 | 反思 minibatch 大小 |
evaluation.use_gate | true(不可设 false) | 验证门控 |
SkillOpt 适合的场景:你有一个冻结模型,需要让它在某个具体任务域上持续进化表现,不想微调权重,有 API 预算跑 optimizer。不适合的场景:你需要完全本地化运行(optimizer 必须走 API)、你需要保证每次 patch 都成功应用(字符串匹配可能 silently skip)、你需要确定性输出(LLM 生成 edits 本身是非确定性的)。
说实话,SkillOpt 的想法让人兴奋。不是因为它的实现已经完美——缺测试、字符串匹配脆弱、backend 支持面不够广。而是因为把自然语言文档当作可训练权重这件事,终于有人做得够完整了。从 Rollout 到 Reflect 到 Gate 到 Slow Update 到 Meta Skill,每一步都有实际代码、有真实 benchmark 数据、有明确的设计决策。这不是概念 demo,是一个可以跑的框架。
6 个 benchmark 上全面超越 Human skill——不是因为技能文档更“聪明”,而是因为技能文档进化了。从失败中学习、从成功中巩固、在跨 epoch 中积累纵向指导——这是手动写提示词做不到的。
如果你也在纠结“怎么让冻结模型在具体任务上更好”,不妨去 SkillOpt 看看。跑一个 benchmark,看你的技能文档在几轮训练后怎么变化。你可能会觉得,训练文档这件事,终于有了靠谱的框架。