当前位置:首页 > 编程开发 > 正文

从零写一个真能用的 Skill:目录、脚本、验证,外加一次被自己工具打脸的迭代

约 4998 字 · 预计阅读 13 分钟
一句话总结:Skill 不是把 prompt 存成文件那么简单,它真正的价值在第三层:脚本。这篇从头做一个「文章发布前体检」的 Skill,30 行元数据加正文、100 行检查脚本、一份规则参考。做出来之后跑两个样本验证:故意写坏的那篇五项全抓,干净的那篇通过。中间还撞了一次误判,把词表精度改好了。

一、先说为什么要做这个

我每天发文章,每篇发布前要过一遍检查:禁用词有没有、破折号是不是太密、段落是不是太长、图片路径对不对。

这些事每一件都简单,但每天手动重复一遍,早晚会漏。漏了就得发出去再改,改完还得重新发布。

这正是 Skill 该管的事:一件有固定流程、你重复做过三次以上的事。

判断标准很简单。如果你想不出"这件事我上周做过几次",那还不值得做成 Skill。

二、前置条件

一个能跑 Python 的终端。不需要装任何依赖库。

目录位置看你的 Agent 环境,通常是用户级 skills 目录或者项目级目录。我这次建在项目目录下,验证通过再决定装不装。

整个 Skill 就三个文件,加起来不到 200 行。

从零写一个真能用的 Skill:目录、脚本、验证,外加一次被自己工具打脸的迭代

图 1:左边是文件,右边是它什么时候被加载、每次花多少上下文

三、第一步:把触发条件写清楚

这是全篇最重要的一步,也是最容易糊弄的一步。

SKILL.md 开头必须是 frontmatter,name 和 description 两个字段是必需的。

---
name: article-lint
description: 中文技术文章发布前体检。当用户要求检查文章、审稿、发布前自查,或提到禁用词、破折号密度、段落过长、图片缺失等问题时使用。
---

description 是 Agent 唯一的判断依据。启动时它只读这两行,靠这段文字决定要不要打开这个 Skill。

所以它必须同时回答两个问题:做什么,什么时候用。

我改成过三版。第一版写的是"检查文章质量",太笼统,什么文章都能算。第二版写"检查中文文章的禁用词",太窄,用户说"帮我审一下稿"就匹配不上。第三版才把动作和触发词都摆进去。

这一段写不好,后面做得再好也没有机会被调用。

四、第二步:正文只写判断,不写实现

第二层是 SKILL.md 的正文,Agent 触发 Skill 后整段读进上下文。

这里最容易犯的错误是把它写成一个说明书,把每一步怎么做都描述一遍。

我的写法是只写三件事:什么时候用、三步流程、判定标准。具体怎么检查交给脚本,正文不描述算法。

判定标准用表格,因为表格最省 token 也最好读:

项目 阈值 为什么
禁用词0 处套话和空评价,出现即改
破折号密度≤ 2 处/千字超了读起来喘不上气
「不是 A 而是 B」≤ 1 处用多了像模板
单段句数≤ 4 句长了手机上是一堵墙

正文我控制在 800 token 左右。官方建议不超过 5,000,但那是上限不是目标,写得越短命中后占的上下文越少。

五、第三步:把确定性的活交给脚本

这一层是 Skill 和"一段长 prompt"的分水岭。

如果正文里写"检查一下有没有禁用词",Agent 每次的判断标准可能不同,同一个词这次算违规下次算放过。写成脚本,结果就是确定的、可复现的、可测试的。

BANNED = ["赋能", "助力", "生态", "闭环", "抓手", "底层逻辑", "护城河",
          "降维打击", "组合拳", "综上所述", "总而言之", "值得注意的是",
          "不难看出", "由此可见", "首先", "其次", "再者", "最后",
          "令人惊叹", "颠覆性", "革命性", "里程碑", "遥遥领先"]

def check_banned(text):
    hits = []
    for i, line in enumerate(lines_of(text), 1):
        if line.strip().startswith("|"):     # 表格里的阈值说明不算正文
            continue
        for w in BANNED:
            if w in line:
                hits.append((i, w))
    return hits

关键的一点在 Skill 的机制里:脚本是执行的,不是读取的。

Agent 用 bash 跑这个脚本,只有标准输出进上下文。脚本本身那 100 行代码,一行都不占。这就是为什么第三层"几乎免费"。

六、第四步:把解释性内容扔进参考文件

词表放脚本里,但"为什么禁这个词、该换成什么"这种解释不能塞进正文,否则正文会膨胀。

扔进 references/rules.md,在正文里写一句"不确定怎么改的时候查那份"。

Agent 只有在需要替换建议时才会去读。不读的时候,它不占任何空间。

七、第五步:装上,然后造两个样本来测

写完了不能只看格式对不对,得真跑。

我造了一个故意写坏的样本:两句话里塞了八个禁用词、三个破折号、两处「不是 A 而是 B」、一个七句的长段、一个指向不存在文件的图片引用。

文章:sample-bad.md
字数:156(去空白)

[FAIL] 禁用词        8 处
        第 3 行:赋能
        第 3 行:生态
        第 3 行:闭环
        第 3 行:护城河
        第 3 行:首先
        第 3 行:其次
        第 3 行:最后
        第 9 行:最后
[FAIL] 破折号        3 处(全文不足 500 字,不判密度)
[FAIL] 不是A而是B    2 处(上限 1)
[FAIL] 段落长度      最长 7 句(上限 4)
[FAIL] 图片          1 个引用,缺 1 个
        找不到:images/not-exist.png

结论:5 项不达标

五项全抓到,退出码 1。这个退出码的意义在于:它能直接挂进发布流程,有问题就卡住。

再跑一个干净样本:

文章:sample-good.md
字数:476(去空白)

[ OK ] 禁用词        0 处
[ OK ] 破折号        0 处(全文不足 500 字,不判密度)
[ OK ] 不是A而是B    0 处
[ OK ] 段落长度      最长 3 句
[ OK ] 图片          1 个引用,全部存在

结论:通过

两边都能出结果,说明它不是"永远报红"或者"永远报绿",判断是真的在工作。

八、一次被自己工具打脸的迭代

第一次跑干净样本,它报了错:

[FAIL] 禁用词        1 处
        第 1 行:最后

第 1 行是标题:「缓存键怎么设计:把变化的部分放到最后」。

这里的「最后」是实义词,指位置。而词表里的「最后」指的是篇章连接词,像「最后,我们总结一下」。

一刀切的字符串匹配区分不了这两种用法,这就是典型的误报。

改法是加语境判断:

LAST_WORD_PATTERN = re.compile(r"最后[,,、是]|最后(我们|说|一点|再|来看)|^最后")

if w == "最后" and not LAST_WORD_PATTERN.search(line):
    continue

改完再跑,坏样本恢复 8 处(「最后是生态」重新被抓住),好样本通过。

这一轮迭代本身就是个教训:凡是靠词表判断的工具,第一版一定有误报,而误报的样板往往来自你自己的文档。

顺带说,这个 Skill 的 SKILL.md 自己跑检查也不通过,因为里面就有那一整张禁用词表。工具检查自己的说明书,必然报红。这不是 bug。

九、坑清单

1. description 太笼统,永远不触发。 「辅助写作」这种描述匹配不上任何具体请求。要写具体动作加具体触发词。

2. 正文写成操作手册,塞进几千字。 触发一次就吃掉几千 token,而那些内容脚本一行就能做完。正文只说"做什么"和"什么算好"。

3. 脚本报错但静默退出。 一定要设退出码,非 0 才算不通过。没有退出码的检查脚本等于没有检查。

4. 相对路径按脚本位置算,不是按执行目录算。 图片检查这块最容易出问题。我在脚本里用 os.path.dirname(os.path.abspath(文件)) 作为基准,而不是当前工作目录。

5. 没测过两种输入。 只测坏样本,你不知道它会不会什么都报红;只测好样本,你不知道它有没有在工作。两边都要跑。

十、边界

这个 Skill 只管表达层面。事实对不对、数据来源标没标、论证有没有漏洞,它一条都管不了。

词表是硬编码的,遇见新的套话要手动加。这类工具永远追不上语言的变化,只能定期维护。

另外它按字符串匹配,遇到"讲解某个禁用词本身"的文章必然误报。我在正文里写了这条,让 Agent 看到时跳过,但那靠的是判断,不是脚本能力。

结语

回到最开始那句:Skill 的价值不在"把 prompt 存成文件",在于它让你能把确定性的部分固化成代码,把判断性的部分留给模型。

一个只有正文的 Skill,本质还是一段长 prompt。加了脚本之后,它才变成一个工具。

想动手的话,别从大的开始。挑一件你每周做三次、每次五分钟、步骤固定的小事,做成第一个 Skill。跑通了再往上加。

站里之前那篇从零写一个 MCP 服务器讲的是给 Agent 接外部能力,和这篇是两个方向:一个给它手,一个给它经验。Skill 本身是什么,在这篇里讲过。

参考资料

目录导航
  • 一、先说为什么要做这个
  • 二、前置条件
  • 三、第一步:把触发条件写清楚
  • 四、第二步:正文只写判断,不写实现
  • 五、第三步:把确定性的活交给脚本
  • 六、第四步:把解释性内容扔进参考文件
  • 七、第五步:装上,然后造两个样本来测
  • 八、一次被自己工具打脸的迭代
  • 九、坑清单
  • 十、边界
  • 结语
    • 参考资料
  • 相关文章:

  • Command 按按钮、Skill 给经验、Subagent 借个脑子:三者到底怎么配合
  • 扫了 83 个 Skill,七成有问题:坏得最多的是引用,不是长度
  • Skill 到底是什么:一个文件夹三层加载,和 MCP 干的不是一回事
  • WebSocket 帧怎么还原:18 个字节里,6 个是头,12 个被掩码过
  • 照着 DeepSeek 的 Super-agent 写个最小版:Router 分流、两级过滤、Worker 并行
  • Anthropic 给四个旧模型回填水印:改一个采样参数就能绕过去
  • 从乱码里逆出 XOR 密钥:256 次爆破的实战,和一条会骗人的捷径
  • 交付前先测 Agent 的三件坏事:越权、编出处、谎报完成
  • 发表评论

    ◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。