创建:现在不是难点
先交代两个前提:本文说的 skill 遵循开放标准 Agent Skills(一个文件夹 + 一个 SKILL.md,给智能体补上某项能力);而创建和优化它,用的是 Anthropic 官方的 skill-creator —— 它本身也是一个 skill,能帮你生成 skill、写测试、跑评测、看结果。下面整套流程都基于它。
skill-creator 会问你这个 skill 做什么、什么时候该触发、期望输出是什么,然后生成 SKILL.md 和目录骨架。这一步已经很成熟,不展开 —— 值得花时间的是后面。
这套做法不是拍脑袋。Claude Code 团队在官方博客 《构建 Claude Code 的经验:我们怎样使用 skill》 里盘点了 Anthropic 内部全部 skill,归成 9 大类。我们下面要打磨的「接口故障排查」,正属于第 8 类 「运维手册」(Runbook) —— 症状 → 多工具排查 → 结构化报告。文中还有个反直觉结论:回报最高的不是教模型「怎么做」,而是 「验证类」—— 教它确认自己做对了。
「最好的 skill 干净地落在某一类;想做太多的会横跨几类、把智能体搞糊涂。」—— 单一职责,正是后面我们会反复碰到的主线。
手动调优:给 skill 写测试,带 / 不带各跑一遍
生成完别急着用。skill-creator 内置了一套评估驱动的调优流程:给 skill 写几个真实测试用例,每个用例跑两遍 —— 一遍带这个 skill、一遍不带作对照,自动打分,再生成页面让你逐个对比真实输出。
我们拿一个真实例子贯穿全程:debug-playbook —— 一套「接口故障排查清单」的 skill。先看它初版(v1)的全文 —— 看着挺专业,问题就藏在最后那句「优先在这五类里定位」:
---
name: debug-playbook
description: 排查接口/爬虫类故障的标准流程。当遇到接口报错、返回空、
数据异常、数据缺失、抓取失败等任何故障时,使用本流程定位问题。
---
# 接口故障标准排查流程
遇到接口报错、返回空、数据缺失、数据异常等故障,严格按以下五步顺序排查,
确认上一步无误再进入下一步:
1. **查参数**:确认请求参数完整、格式正确、必填项齐全。
2. **查鉴权**:确认 token / cookie / 签名有效且未过期。这是最常见的根因。
3. **查缓存**:确认是否命中了过期缓存或脏缓存,尝试清缓存重试。
4. **查网络 / 代理**:确认网络连通、代理可用、目标站可达。
5. **查限流**:确认是否触发频率限制(429、滑块、空响应)。
## 规则
- 必须按 1→5 的顺序逐步排查。
- 绝大多数接口故障的根因都落在这五类中,**优先在这五类里定位**。
- 不要一上来就假设是业务代码逻辑问题——先排除以上环境 / 请求因素。
- 输出:定位到的根因属于上述哪一类,以及对应的修复动作。
它的评测具体怎么跑
这套流程操作上就六步:
- 告诉它这个 skill 做什么、什么时候触发 → 它生成 SKILL.md 和目录。
- 给几个真实测试用例(覆盖「该用」和「不该用」)。
- 它自动起两组 subagent:带 skill 和 不带,并行各跑一遍。
- 自动打分,生成一个 review 界面让你逐个对比。
- 你逐个看真实输出 + 评分,写下反馈。
- 据反馈改 skill,再跑下一轮 —— 直到满意。
review 界面长这样 —— 每屏一个 run,带 / 不带的真实输出摆在一起,还能切到 Benchmark 看汇总:
带 / 不带,左右一比就清楚
给它三个故障现象当测试用例。下面切换不同故障,左边是不带 skill 的诊断、右边是带 v1 的诊断 —— 同一个模型、同一个问题,差别一目了然。这些都是真实输出,没有改写。
1认证凭据 / 密钥过期 —— JWT/API Key/证书到期,中间件校验阶段直接 reject,body 为空正是中间件层拦截的典型特征。
2认证服务 / 依赖组件不可用(上游验证服务宕机)。
1鉴权失效 —— token/API Key/证书到期或被吊销(401 全量、无代码变更、时间吻合)。
2鉴权配置失效 —— 密钥被轮换或环境变量被覆盖。
正例:这类故障正落在清单第 2 步「查鉴权」的能力范围里,带不带都直奔根因。这是 skill 该用的地方。
1时区不一致 —— 生产 UTC 与本地时区不同,「今天」的日期边界错位:上午数据还没进入 UTC 的「今天」,昨天数据因已跨日被错误纳入。完整解释了「本地正常」。
2定时触发与数据就绪窗口的竞态。
date / now() 与本地对比,时区不同即坐实。1缓存 —— 服务端或 CDN 对 date 参数缓存了前一次响应,导致昨天数据混入、今天上午未更新。
2参数 —— 时区 / 时间偏移导致 date 值偏差。
反例 —— 关键:真实根因(时区)根本不在那五步里。但现象「数据不对」恰好能被「查缓存」解释得通,清单就把模型的注意力从时区锚到了缓存,排到第一。真实排查里,这就是先去查缓存、绕远路。
展开:这条的完整原始输出(未删减)
不带 skill
最可能根因方向(Top2,按可能性):
1. 时区不一致——生产服务器(常见为 UTC)与本地开发机(本地时区)不同,导致"今天"的日期边界计算错位:UTC 0 点不等于业务时区 0 点,上午数据尚未进入 UTC 意义的"今天",昨天数据则因 UTC 已跨日而被错误纳入
2. 定时任务触发时刻与数据就绪窗口的竞态——任务在每天 0 点刚过就触发,但上游数据源按业务时区延迟写入,导致抓取时上午数据还不存在
第一步排查动作: 在生产机上直接打印 date 命令或 now()/datetime.now() 输出(含时区偏移),与本地开发机对比——若两者时区标识不同,时区问题即可坐实为根因
带 v1 skill
最可能根因方向(Top2,按可能性):
1. 缓存——服务端或 CDN 对 date 参数缓存了前一次响应,导致昨天的数据混入、今天上午的数据未更新
2. 参数——定时任务执行时的时区/时间偏移与服务端不一致,导致传出的 date 值偏差(本地机器时区与服务器时区不同,跑出来的结果就不一样)
第一步排查动作: 对比定时任务服务器与本地开发机的系统时区(TZ 环境变量 / date 命令输出),确认两者生成的 date 参数值是否一致;同时检查定时任务触发时刻(如 00:00 前后)与服务端「今天」的边界是否对齐。
1缓存 key 设计缺陷 —— key 未含城市维度,不同城市命中同一条 cache。清缓存后暂时恢复,随多城市交叉写入重新乱掉。
2请求上下文 / 线程局部变量泄漏。
1查缓存 —— 缓存 key 未含城市参数,不同城市命中同一条记录;清缓存后暂时正确、TTL 到期旧数据重新污染。
2查参数 —— 城市参数在某些路径未正确传递。
正例:这个故障的真根因(缓存 key)恰好落在清单第 3 步「查缓存」附近,skill 没帮倒忙。skill 的好坏取决于故障是否落在它的能力范围内 —— 这正是为什么要逐个测。
打分汇总也是一目了然 —— 这是 评测界面的 Benchmark 标签:
反例不止时区 —— v1 在好几种故障上都会带偏
四类故障,真因都在代码里,但表面都像「接口问题」。不带 skill 直奔真因;带上 v1 五步清单,全被锚到了环境层:
| 故障现象 → 真因 | 不带 skill | 带 v1 五步清单 |
|---|---|---|
| 数据缺上午、本地正常 → 时区 | ✓ 直指时区 | ✗ 缓存排第一 |
| 导出 CSV 中文乱码 → 导出编码 | ✓ 直指编码 | ✗ 硬塞「查缓存」 |
| 高峰看到别人购物车 → 并发/共享 | ✓ ThreadLocal 竞态 | ✗ 查缓存 key |
| 列表少最后一页 → 分页 | ✓ 分页循环 | ⚠ 归类「查参数」 |
看到问题之后怎么改
在对比里看到被带偏,改法不是删掉 skill,而是给它一条边界:开头加一句方向判断,并写明「何时不要用」。下面就是改后的 v2 全文(和前面的 v1 比,核心只多了开头那段边界);改完用同一组用例重跑,看三个用例 改前(v1)对改后(v2) 的效果:
Claude Code 团队把「指令写得太具体、把模型逼上固定轨道」称作 「逼上铁轨」(railroading),给的建议是「给模型需要的信息,但留出适应当下情况的灵活度」。我们这条边界 —— 不删清单、只补一句「何时不要用」—— 就是这个思路的落地。原文 ↗
---
name: debug-playbook
description: 排查接口/爬虫类故障时的诊断线索清单。当接口整体报错、被限流、
鉴权失败这类请求层问题时参考;若接口正常返回、只是数据内容不对,
优先直接读相关代码,不要套用本清单。
---
# 接口故障诊断线索(优先级提示,非强制顺序)
先判断根因在哪一层,再决定是否套用本清单:
- 接口**整体报错、被限流、鉴权失败**(请求层问题)→ 本清单大概率有效。
- 接口**正常返回、但数据内容不对**(不是该有的结果)→ 根因多半在处理数据的代码里,
**先直接读那段代码**,本清单可能把你带偏。
## 常见请求层根因(按经验概率,不是必须顺序)
- 鉴权失效(token / cookie 过期)—— 通常整体 401 / 空
- 触发限流(429、滑块、达到阈值后转空)
- 缓存命中脏数据
- 网络 / 代理不可达
- 请求参数缺失 / 错误
## 何时不要用本 skill
先看接口本身有没有报错。如果接口正常返回、只是返回的数据内容不对(不是该有的结果),
根因多半在处理数据的代码里,先去读那段代码,别套用本清单。
只有接口整体报错、被限流、鉴权失败这类请求层问题,才用本清单。
输出:根因方向判断 + 具体根因 + 修复动作。
1鉴权失效 —— token/API Key/证书到期或被吊销。
2鉴权配置失效 —— 密钥被轮换或环境变量被覆盖。
1鉴权凭证失效 —— Token/Secret/证书到期或被轮换。
2鉴权服务 / IdP 故障(今天突然全挂、无发版)。
不受影响:401 是请求层故障,正是清单的能力范围。v2 加的边界(「数据内容不对才去读代码」)对它不触发,两版都直奔鉴权。
1缓存 —— 服务端或 CDN 对 date 参数缓存了前一次响应。
2参数 —— 时区 / 时间偏移导致 date 值偏差。
1时区不一致 —— 服务器与本地时区不同,「今天」边界错位,上午数据落在服务器视角的「昨天」。
2定时任务触发时机与数据就绪时间错位。
救回:v2 开头先判方向「数据内容不对就先读代码」,模型没再被「查缓存」带走 —— 首选从缓存纠正回时区。一条边界,重测即救回。
1查缓存 —— 缓存 key 未含城市参数,不同城市命中同一条记录。
2查参数 —— 城市参数在某些路径未正确传递。
1缓存 key 设计有误 —— 城市参数没被纳入 key,不同城市命中同一条缓存。
2并发竞态写缓存 —— 后写的城市覆盖前一个槽位。
方向更准:两版都挖到了 key,但 v1 把它当请求层「查缓存」,v2 因「时好时坏」识别为数据问题,直接指向读代码看 key 拼接 —— 更贴近真因。这一改一测,就是 skill-creator 设计的迭代闭环。
回到那四个反例 —— v2 一句边界,全救回
前面被 v1 带偏的四个反例(时区 / 编码 / 并发 / 分页),换成带边界的 v2 再跑一遍:
| 故障 → 真因 | 带 v1(被带偏) | 带 v2(救回) |
|---|---|---|
| 数据缺上午 → 时区 | ✗ 缓存排第一 | ✓ 时区 |
| 导出乱码 → 编码 | ✗ 硬塞缓存 | ✓ 导出编码 |
| 串购物车 → 并发 | ✗ 查缓存 key | ✓ 读代码 |
| 少一页 → 分页 | ⚠ 查参数 | ✓ 直指分页 |
边界怎么写,才不是「背答案」
上面 v2 加的边界很有效,但有个坑要避开 —— 边界写到什么程度,决定了它是真本事还是假象。三个层次,从最糟到最稳:
- 写答案(在边界里点名「时区、缓存 key」)—— 最糟。等于把测试用例的答案抄进 skill,它「救回」只是泄题,换道题就废。
- 写症状清单(「时好时坏、间歇、错位」)—— 比答案安全,但如果这些症状正好照着测试用例描述,就是隐性过拟合:你在教它「认出这几道题」,不是教通用判断。
- 写判断维度(「接口报没报错」)—— 最稳。给的是客观、可泛化的分类标准,不绑定任何具体症状词。v2 最后用的就是这层。
这也对上官方的两条:别写模型默认就会做的事,skill 里信号最高的是 「踩坑提示」(gotchas)。一条好的边界,本质就是一条「踩坑提示」—— 不重复常识,只标出模型会栽的地方。原文 ↗
官方还给了 8 条制作建议
上面是把一个 skill 打磨好的全过程。跳出这个例子,Claude Code 团队在同一篇博客里给了一组通用心法 —— 前三条我们前面已经实践过,后面几条是值得收藏的补充:
自动进化:skill 多到手动跑不过来时
手动跑对照,优化一两个 skill 没问题。可一旦 skill 多了、还要持续维护,就需要自动化。先把上面这套调优画成一个循环:
说白了,自动化就是把上面那步「人来改」换成「机器自己改」。先看它真能不能自己改 —— 把 v1 和它那次时区失败的记录丢给模型,让它自己诊断、自己提改进:
它的思考(真实推理,未改写)
先抓到关键信号:不带 skill 答对、带了反而错 → 问题出在 skill,不在模型。通读后定位两处元凶:① 五步清单里根本没有「时区」这一类,模型无处安放它;② 最后那句「不要假设是代码问题」,把时区这种环境配置问题也一起堵死了 —— 而「本地正常、线上异常」明明是环境差异的强信号,清单却没利用。
它给出的两处改动,像改代码一样(− 原文 / + 改后):
GEPA 反思式进化 gepa-ai/gepa ↗
跑一遍,失败了就让 AI 回头看整个过程 —— 哪一步错了、为什么,然后照着改 skill。相当于让它自己记错题、再订正。
聪明在哪 · 它不会只留一个「当前最好」的版本,而是留一小批各有所长的(这个擅长这类题、那个擅长那类),不一条道走到黑。这样改起来又省试错(约 35 倍)、效果还更好。
SkillOpt 当参数训练 microsoft/SkillOpt ↗
把 skill 的文字当成「可以一点点拧的旋钮」,一轮轮地调:拿真实任务跑一遍、打个分,再照着分数改一改,而且越往后改得越小步。
关键 · 每改完先拿一批没动过的题目验一下,没变好就撤回。但这个「验完再决定」一定要开着 —— 有的实现默认改了就认,等于自己骗自己、越改越歪。
Darwinian 种群进化 imbue-ai/darwinian_evolver ↗
一次养一群候选,挨个打分:好的多留种,再随机改一改、生出下一代;还故意鼓励试些没走过的方向,别都挤在一条路上。
关键 · 把题目分两堆 —— 一堆用来改、一堆藏起来只用来考。改得好不好看藏起来那堆的成绩,免得「只在练习册上好看、一考真题就垮」。
| 维度 | GEPA | SkillOpt | Darwinian |
|---|---|---|---|
| 进化对象 | prompt / 指令(可多模块系统) | skill 文档 | 任意工件(prompt / 代码 / agent) |
| 改的方式 | 反思失败轨迹、语言诊断 | 六阶段:跑 → 反思 → 汇总 → 选 → 改 → 验 | 可插拔变异器 + 选择 |
| 选择机制 | 帕累托前沿(Pareto frontier)(保多样) | 留出验一下(求单调、不退化) | 加权选父代 + 新颖度 |
| 评估开销 | 极低(比 GRPO 省约 35×) | 高(每轮真实任务跑分) | 高(种群 × 代数) |
| 防过拟合 | 帕累托前沿 + 留出 | 留出验证 | 训练 / 留出案例分离 |
| 出身 | DSPy 生态 · ICLR 2026 Oral | microsoft | imbue · 受达尔文-哥德尔机启发 |
该用哪个:按 skill 类型对号
先看能不能自动、可信地打分;能的话,按 skill 类型对号:
| skill 类型(举例) | 用哪个 | 为什么 |
|---|---|---|
| 文本提取 / 抽字段、分类、结构化输出、有标准答案的问答 | GEPA | 是提示词优化、能客观自动打分、想少花评测 |
| 绑定固定真实环境长期跑(某平台抓取、内部工单分流、特定代码库规范) | SkillOpt | 要贴合该环境、有真实任务集、扛得住每轮真实跑分 |
| 进化的不止提示词(agent 结构 / 工具 / 生成的代码)、要开放探索 | Darwinian | 进化对象是任意工件、容忍大量失败变异 |
| 主观质量、没法自动打分(创意写作、文风、读着顺不顺) | 手动对照 | 没有可信评分,自动优化只会自欺 |
如果 skill 数量不多、或还没有可信的自动评分 —— 别急着上自动化,前面那套手动「带 / 不带对照」已经够。
但自动化也有命门:打分得可信
自动进化把「改」整个交给了机器,而机器只认一个东西 —— 分数。所以这套能不能用,全看分数可不可信:
- 用例要客观可判(机器能打分),不能靠模型自己说「我觉得更好了」。
- 用例要足够多样,否则机器会专门去迎合那几道题 —— 就是前面说的过拟合。
- 改和验的数据分开(上面那条铁律)。
五条
- 创建用 skill-creator,几分钟,不是难点。
- 别止步于「生成」—— 点开它的评测,跑「带 / 不带」对照,尤其盯住反例(能力范围外被带偏)。
- 看真实推理过程,别只看通过率 —— 通过率会骗你。
- 改 skill 靠加边界、不靠删;改完用同一组用例重测。
- skill 多了再上自动进化,记住一条:改和验的数据要分开。