写了几十个Skill之后,我总结出这套工程方法:从「触发不了」到「生产可用」

这儿不讲skill和prompt的区别,也不说MCP/A2A是啥子,说这些的文章已经很多了。
本文默认你已经晓得skill是个啥,且已写过几个skill了。
下面只摆一件事:咋把skill写对、写稳、写到能在团队里长期维护。


先分清遇到的是哪一类问题

写过skill的人,大多都卡在过下面两种情况:

  • 一是装了等于没装:skill明明在目录里,agent一次都没用过。
  • 二是用了等于没用:偶尔触发了,但跑着跑着开始自由发挥,SOP形同虚设。

遇到这些情况,多数人要么把提示词写得更长或语气更强,要么加一堆「必须」「严禁」「ALWAYS」「NEVER」。
但这两个情况是两码事。前者是路由失败,发生在模型决定要不要加载skill之前;后者是执行失败,skill已经加载了,但步骤、工具或验收没被遵循。
一个是找不到,一个是没做到。病因不一样,修法也不一样,混在一起改,是很多skill改不好的原因。
下面就讲一下怎么处理更合适。


一、skill有三层,不是简单的一个文件

很多人的skill就是一坨SKILL.md,触发条件、步骤、参考资料、示例、注意事项,全塞进一个文件。
这种skill短期能用,很快就失控:正文越来越长,关键约束被稀释。
更好的方式是「渐进加载」,把发现、理解、执行拆成三个成本完全不同的层。

层级 加载时机 承载内容 设计责任
L1元数据 启动时始终加载 namedescription 精确发现与路由
L2指令 判定相关后才加载 SKILL.md正文:步骤、约束、示例 控制执行行为
L3资源与代码 按需加载 脚本、模板、参考资料 提供事实、确定性与产物

这三层是anthropic官方说过的,好处是:常驻上下文,单个skill只有少量token的元数据,主要是在加载之后才付成本。
skill写的时候在哪层偷懒,问题就会堆到哪层:

  • 在L1塞正文,元数据膨胀,每项任务都在为没用到的skill付token;
  • 在L2塞参考资料,每次触发都读一堆无关信息,关键信息被淹没;
  • 在L2用自然语言描述确定性逻辑,模型开始「大致照做」,边界条件开始出错。

下面逐层展开讲一哈子咋个整更合适。


二、L1触发

description是用于路由发现的

2.1 绝大多数「不生效」,问题出在这一行

description是整个skill里最值得花心思写的一行字。它决定了模型在会不会选中这个skill。

最常见的反面教材写法长这样:

# ❌ 反例
name: docs-helper
description: 帮助处理文档和相关内容。

这句话的问题是:它只说了「我大概能干啥」,没说「什么情况下该找我」,也没说「什么情况下别找我」。模型拿着这个描述只能猜。

好的description至少要回答三件事:

  1. 做什么(能力声明,一句话讲清业务结果)
  2. 什么时候用(正向触发,最好包含用户的真实说法)
  3. 什么时候不用(排除条款+该找谁)
# ✅ 正例
name: docx-contract-review
description: |
  审查 .docx格式的合同文件,检查缺失条款、主体名称、日期和签章栏是否完整。

  使用场景(USE WHEN):输入是一份 .docx 合同,或者用户说「帮我审一下这份合同」
  「看看这个协议有没有问题」。

  不适用于(DO NOT USE FOR):
  - PDF内容提取 → 改用pdf-extract
  - 合同修订批注 → 改用docx-redline
  - 通用写作建议 → 不适用本skill

2.2 排除条款比触发条款更重要

「不是X时不要触发」比「是X时触发」更能提升路由准确率。因为正向描述天然是模糊的,「review this contract」和「summarize this contract」语义上离得很近,模型很容易误判;但如果明确写了「不适用于:通用摘要总结,改用doc-summarize」,边界就清楚了。

排除条款还有个好处:它能告诉你路由错了该往哪走。写上「改用pdf-extract」,相当于给模型一张路由表,比单纯说「不要用于PDF」有用得多。

2.3 触发描述里应该写「用户的原话」

「使用场景」里列举的那些说法,不是给关键词硬匹配用的,而是弥补模型对真实表达习惯的推断偏差。

用户不会说「请执行灰度发布协调流程」,他会说:

  • 「把payments-api灰度到prod」
  • 「先放5%流量试试」
  • 「1.42.0这个版本先小流量验证一下」

把这些真实说法写进去,路由命中率会有不少提升。

2.4 一个容易踩的物理坑:先确认它「存在」

在改description之前,先确认skill真的被扫描到了。

我遇到过这么个情况:用npx skills add装的skill,实际落到了~/.agents/skills/,而我当时用的claudecode扫描的却是~/.claude/skills/。安装位置和运行时扫描位置不是一回事,用户以为装好了,环境里根本没有。具体目录名会随工具链和版本变,不用死记这两个路径,关键是意识到「装到哪儿」和「运行时扫哪儿」可能是两回事。
所以调试顺序应该从外到内:

物理存在→清单声明→路由选择→正文加载→脚本执行→输出验收

目录都没被扫描的话,改一百遍措辞也不会有任何信号。

2.5 控制同时挂载的数量

skill挂太多会互相抢注意力,误触发率明显上升。经验上,同时挂载控制在20个以内比较稳,按角色或场景打包,不要一股脑全塞进去。
真需要很多的话,就分层:常驻少量高频的,其余按项目或工作区动态装载。


三、L2正文

SKILL.md是地图,不是仓库

3.1 要默认假设「模型已经很聪明」

写skill最容易犯的错,是把模型当小学生,什么都教一遍。
其实只需要写它不知道的。领域惯例、公司内部流程、非常规的踩坑点,这些才是skill正文该承载的内容。「先仔细阅读代码」「确保没有语法错误」这类废话,不但没用,还会稀释真正重要的约束。

3.2 正文的最小有效结构

我常用的骨架是这个:

# 灰度发布协调

## 目标与非目标
协调一次分阶段的生产环境发布。
非目标:不负责集群开通,不负责数据库迁移。

## 前置条件
- 已确认service、image_tag、namespace三个参数;
- 调用方已审批本次生产变更;
- 已存在基线SLO数据。

## 执行步骤
1. 预检:执行./scripts/preflight.sh,任何非零退出码立即停止。
2. 规划:输出目标版本号、灰度比例、回滚阈值。
3. 实施:严格只执行一次声明好的部署命令。
4. 验证:运行冒烟测试,tests/smoke.yaml中的断言必须全部通过。
5. 决策:SLO达标才放量,否则执行回滚并发出审计事件。

## 强制检查项
- 部署操作必须是幂等的;
- 灰度流量比例必须在策略允许范围内;
- 任何密钥都不得回显到输出中。

## 失败与回退
- 仅网络类瞬时故障可重试一次;
- 触发阈值告警时,调用rollback.sh并立即停止;
- 遇到歧义时只提一个聚焦的问题,不要猜测目标服务。

## 示例
输入:「把payments-api 1.42.0灰度到prod-us」
→ 预检 → 放量 5% → 冒烟 → 放量或回滚

反例:
输入:「payments-api为什么这么慢?」
→ 不要调用本skill,应改用k8s-troubleshoot。

几个要点:

  • 「非目标」和「前置条件」不能省。前者防止模型过度扩张职责,后者让模型知道「条件不满足时该停下来问」,而不是硬着头皮往下跑。
  • 步骤要编号、要有停止条件。「跑一下测试,看看结果,再决定要不要继续」这种写法等于没写。
  • 示例和反例成对出现。反例划定边界,优先写最容易混淆的那个相邻skill。

3.3 控制体量

经验值:SKILL.md正文控制在500行以内。超过了就该拆:

  • 长篇参考资料拆到references/,正文里写明「什么时候去读」;
  • 确定性计算与校验拆到scripts/
  • 输出模板与固定格式拆到assets/

anthropic官方的说法是:脚本由agent执行(bash、python、node等均可),只有脚本输出进入上下文;参考资料由skill显式指向,需要时才读取。

3.4 别把ALWAYS/NEVER当万能药

堆砌强指令词是个很常见的误区,它有两个问题:

  1. 用多了就疲了,模型不会因为写三个MUST就更听话;
  2. 它只说「不能做什么」没说「为什么」,而模型在遇到边界情况时,恰恰是靠「为什么」来类推的。

更好的写法是规则+原因:

# ❌ 反例
严禁直接向main分支提交代码。

# ✅ 正例
不要直接向main分支提交:每周四会自动从main切出发布分支,
直接提交的代码可能静默错过这一班发布列车。
如果需要紧急修复,请开hotfix分支并通知发布负责人。

给了原因,模型在遇到你没预料到的情况时才能做出正确类推。


四、指令该写多死?按风险定档

我在实践中的经验是:指令的严格程度,应该由出错代价决定,而不是由你的焦虑程度决定。
把所有步骤都锁死,skill会变得脆弱,环境稍有变化就卡住;全部放开,高风险操作又容易出事。

风险等级 典型场景 推荐写法 校验方式
数据库迁移、删除操作、生产写入、权限变更 精确脚本+强约束+审批门 脚本校验+人工确认
部署、批量重构、配置变更 伪代码+参数化步骤 dry-run+检查点
代码审查、文案润色、信息总结 自然语言指令,留出判断空间 输出格式约定

说白了,就看这一步做错了代价多大:

  • 不可逆的,脚本化+审批;
  • 可逆但影响面大的,dry-run+检查点;
  • 随便改改就行的,给个方向,让它自己发挥。

anthropic在《building effective agents》里也表达过类似的观点:能用预定义代码路径解决的就用workflow,把灵活性留给真正需要模型动态决策的地方。过度agent化本身就是一种反模式。


五、L3护栏

把「应该」变成「检查点」。

这一章里的checks:side_effects:on_failure:等字段,不是agent skills官方frontmatter的一部分,当前没有任何运行时会自动解析并执行它们。它们是一份「显式化契约」,把约束写清楚,好让agent照着走、让脚本去落实、让人能审。要让它们真的生效,得在scripts/或正文里实现对应逻辑。

5.1 三成原则

我的经验是:一个成熟的skill,至少30%的约束应该被转成机器可校验的检查点。
自然语言适合承载无法穷举的语义判断;代码更适合格式、数值、存在性、幂等性这些东西。

# ❌ 反例
注意不要泄露密钥,并且要校验输入参数。

# ✅ 正例
checks:
  - id: 密钥泄露扫描
    run: ./scripts/scan_secrets.sh
    on_failure: block        # 直接中断
  - id: 版本号格式校验
    run: ./scripts/semver_check.sh "${image_tag}"
    on_failure: block
  - id: 幂等性预演
    run: ./scripts/dry_run.sh
    on_failure: ask_user     # 停下来询问

注意on_failure有三种取值,对应三种处置策略:block(直接中断)、ask_user(停下来问)、retry(可重试)。把处置策略也写进去,skill才完整。

5.2 声明副作用契约

如果skill会产生外部副作用,最好把它写清楚:

inputs:
  service: string              # 服务名
  image_tag: semver            # 镜像版本
  namespace: enum(prod-us, prod-eu)
side_effects:
  - 对目标 namespace 执行 kubectl apply
idempotent: true               # 是否幂等
dry_run: scripts/dry_run.sh    # 预演脚本
rollback: scripts/rollback.sh  # 回滚脚本
requires_approval: true        # 是否需要人工审批

幂等性这一项特别重要,因为agent可能会在各种情况下重试:网络抖动、工具超时、上下文压缩后重新规划。如果skill不幂等还不给它写清楚,一次重试就可能变成两次部署。

5.3 失败策略要分级

无脑「失败时重试三次」不是好的失败处理策略。

retry:
  max: 1
  conditions: [网络超时]        # 只有瞬时故障才重试
escalate:
  conditions: [权限不足, 数据破坏风险]   # 结构性错误直接上报
human_approval:
  conditions: [生产写入, 回滚操作]
never_skip: [预检, 阈值触发回滚]  # 这些步骤永不跳过

重试只适用于「可证明是瞬时的」问题;权限不足、数据破坏风险、未知状态,必须停止或升级,重试并没有nuan用。

5.4 一个隐藏的运行时约束

如果skill里存在等待人工输入、授权确认或外部回调的环节,务必声明它是否能在子agent中使用。
我曾经遇到过一个例子:skill在子agent中被调用时,交互式的授权提示会原样返回给编排器,但当时并没有可供编排器注入回复、恢复子agent会话的原语。结果流程就卡死了。
所以在发布前必须识别skill是否依赖交互式门控,并明确它是「顶层会话专用」还是「可在子agent中使用的」。


六、什么时候该拆,什么时候该合

6.1 拆分标准:看「变化原因」,不看长度

最常见的错误是按文件长度拆分,比如SKILL.md超过500行就砍一刀,基本上都是错的。
更好的是:想砍开的这两部分会不会因为不同的原因而改变?

判断维度 结论
触发条件不同
验证标准不同
权限与审批要求不同
输入输出契约不同
只是步骤变长 不拆
仅调用的工具不同,流程相同 参数化即可
输出格式不同但流程一致 加模板即可

说白了,该不该拆,看触发、验证、权限这些「变化原因」一不一样,而不是看长度。

6.2 skill/tool/RAG的边界

我总结了下面这个表:

资产 核心责任 典型故障 治理手段
skill 步骤、约束、决策、验收 触发错误、步骤漂移、验收缺失 description、清单、eval
tool/function 操作接口与副作用 参数错误、权限不足、异常 schema、错误码、幂等、日志
RAG/知识库 易变事实 检索错误、版本陈旧 来源元数据、引用校验
memory 用户与任务状态 越权、过期、污染 作用域、TTL、写入审批
  • 「能不能调」出问题找tool,
  • 「事实对不对」出问题找RAG,
  • 「行为稳不稳」出问题找skill。

顺带说一下MCP:MCP解决的是「连到哪儿」,skill解决的是「怎么用好」。两者可以互补,不是互怼。


七、没有eval,就没有skill迭代

绝大多数人写完skill,跑两次觉得没问题就提交了,三个月后,没人敢改它,因为不知道改了会不会坏。

7.1 四个指标必须分开测

只评价最终答案,会把四类问题混在一起。建议拆成:

指标 定义 诊断的问题
路由准确率 正确首选或进入候选的样本数÷路由样本数 触发与边界
步骤遵循率 必经步骤被遵循的样本数÷正确路由进该skill的样本数 SOP表达
检查点通过率 自动检查点通过的试验数÷总试验数 硬约束执行
任务成功率 满足最终环境断言的试验数÷总试验数 整体结果

指标的下降方向,直接指向该改哪里:

  • 路由准确率掉了,改description
  • 步骤遵循率掉了,改正文指令;
  • 检查点通过率掉了,改脚本或校验逻辑;
  • 全都掉、怎么改都救不回来,这个skill该重新设计了。

7.2 一个最小可用的路由测试集

路由是最容易测、也最该先测的。建议每个skill维护20条样本:

类型 数量 目的
典型正例 5 验证核心任务能触发
近义改写 5 验证自然表达漂移下的鲁棒性
强负例 5 验证排除条款生效
相邻技能边界 5 验证候选之间的区分度

「强负例」这一组特别有价值,它测的就是在「不适用于」里写的那些东西到底有没有生效。

7.3 不要只看最终输出

anthropic在讲agent eval时强调过一个关键点:模型说「已预订」,不等于环境里真的存在预订记录。

所以断言要打在环境状态上,而不是模型输出上:

# ❌ 反例:只检查模型说了什么
assert "已放量" in 最终输出

# ✅ 正例:检查环境的真实状态
assert 查询部署("payments-api").版本号 == "1.42.0"
assert 查询SLO("payments-api").错误预算百分比 > 5

另外,每组至少跑3次试验。agent输出有波动,单次结果说明不了问题。

7.4 从「改提示词」转向「读trace」

当不知道skill哪里出问题时,先去看trace,不要凭感觉改措辞。
一条有用的trace至少应该包含:

  • skill的id与版本号
  • 触发时的全部候选及选中理由
  • 正文加载范围
  • 脚本调用、参数、输出
  • 检查点结果
  • 回滚事件

调试流程:

  1. 复现:固定prompt、模型、工具状态、环境变量;
  2. 分层重放:先mock工具验证步骤逻辑,再接真实环境验证副作用;
  3. 差异对比:并排看成功与失败的trace,定位差异出现在路由、参数、分支还是检查点;
  4. 最小化修复:每次只改一类(description、正文、脚本、数据),然后重跑同一份评测集。

一次改四样,效果好了也不知道是哪样的功劳;效果差了更惨,得再改回来。


八、版本化:让skill可维护

进入团队协作后,skill就不再是「对话附件」了,它是代码资产。
推荐的目录结构:

skills/
  release-canary/
    SKILL.md              # 元数据 + 指令 + 示例
    CHANGELOG.md
    metadata.schema.json
    scripts/
      preflight.sh        # 预检
      dry_run.sh          # 预演
      rollback.sh         # 回滚
    tests/
      smoke.yaml
      eval/
        001-灰度发布-正常路径.yaml
        002-SLO超阈值-触发回滚.yaml
    references/
      release-policy.md   # 发布策略细则

版本号建议遵循major.minor.patch

变更类型 版本规则 示例
触发条件或契约变化 major 移除自动放量、修改必填输入
新增步骤或检查 minor 增加SLO预算校验
文案或示例修订 patch 调整描述、补充示例

CHANGELOG里显式标注破坏性变更:

v2.0.0 - 2026-08-20
- BREAKING: 移除自动放量,调用方必须显式审批
- description: 增加「临时排查调试」场景的排除条款
- checks: 放量前要求校验SLO预算

没有版本和CHANGELOG,就无法把一次失败归因于「这次改坏了」。而归因能力,是持续迭代的前提。


九、一份可以直接抄的检查清单

写完一个skill,发布前过一遍下面这些事项:

触发(L1)

正文(L2)

执行与护栏(L3)

评估

我觉得可以把上面这些项(甚至是本文)写成一个检查skill的skill,哈哈哈!
希望这篇对你有用。如果有不同意见或者踩过别的坑,欢迎在评论区聊。

posted @ 2026-09-03 22:20  人才瘾大  阅读(3)  评论(0)    收藏  举报