400-100-5265

预约演示

专业 Skill 的工程化写法

2026-06-18

你有没有遇到过这种情况:给 AI Agent 写了一个 Skill,第一次跑得挺顺,第二次换个场景就开始飘,隔几天再用还冒出一个莫名其妙的 bug。

这类问题很像写脚本。一个脚本在自己电脑上跑通,和一个工具能交给团队稳定使用,中间隔着的不是语法,而是工程化。Skill 也是一样。它不是把提示词写长一点,也不是把 API 文档塞进去就完事了。

专业的 Skill,本质上是在给 Agent 写一份可执行、可判断、可回退的工作手册。它要解决的不是“Agent 知不知道做什么”,而是“Agent 在不同上下文里,能不能稳定做对”。

一、Skill 的边界先要想清楚

很多 Skill 写不稳,问题一开始就埋下了:作者把它当成 prompt 写。

Skill 更接近 Agent 的操作手册。它通常要回答四个问题:

  • 什么时候应该触发
  • 具体按什么步骤执行
  • 出错时怎么判断和回退
  • 做完后怎么验证结果

一个典型 Skill 的目录结构大概是这样:

my-skill/
├── SKILL.md              # 核心指令,必需
├── scripts/              # 可执行脚本
│   └── helper.py
├── references/           # 参考文档
│   ├── api.md
│   └── best-practices.md
└── templates/            # 模板文件
    └── output.md

SKILL.md 是唯一必需文件,但专业 Skill 和随手写的 Skill,差距往往就在这些辅助目录里。

原因也简单:真实任务里,Agent 很少只靠一段自然语言就稳定完成复杂流程。它需要明确的输入输出、约束条件、异常分支,以及可以直接执行的脚本。

如果一个 Skill 只是“一段描述 一条命令”,它可以解决一次性任务。但如果这个 Skill 会被反复调用、给别人用,或者承载关键流程,就需要按工程资产来维护。

二、能跑和专业差在哪里

可以用两个层级来对比。

一个入门级 Skill,比如查询 AI 资讯,核心目标很明确:拉取最近 24 小时的数据。它的 frontmatter 可能长这样:


name: aihot

核心命令也很直接:

since=$(date -u -v-24H  %Y-%m-%dT%H:%M:%SZ)
curl -s "https://aihot.virxact.com/api/public/items?mode=selected&since=$since"

这个 Skill 能不能用?当然能用。

它结构清晰,代码量小,维护成本低。如果任务边界足够窄,这种写法没有问题。工程上最怕的不是简单,而是把简单任务硬做复杂。

但换成一个公众号全流程助手,比如 WeWrite 这类写作流水线 Skill,情况会变得完全不同。它不仅要生成内容,还要处理选题、风格、素材、质量检查、禁用词、历史记录、重复规避等问题。

两者差异大概是这样:

维度 入门级 Skill 专业级 Skill
Frontmatter name description 完整元数据、tags、related_skills、version
触发条件 一句话描述 多个触发关键词 反向触发条件
工作流 一个主路径 多步骤流水线,每步有输入输出
错误处理 基本没有 每个关键步骤有 fallback 和坑位说明
辅助文件 很少或没有 references、scripts、templates、personas 等
验证方式 人工看结果 自动检查、质量评分、规则扫描
迭代机制 临时修改 记录历史元数据,避免重复和回归

文件大小可能从十几 KB 增长到上百 KB。关键不是“写得长”,而是每一部分都在降低 Agent 的自由发挥空间。

这是专业 Skill 的核心工程取舍: 用更多前置设计,换更少运行时不确定性。

当然,这也有成本。一个一次性查询任务没必要搞 7 个 references 和 5 个 scripts。复杂度应该服务于任务稳定性,而不是服务于作者的仪式感。

三、Frontmatter 决定能不能被正确加载

Frontmatter 是很多人最容易糊弄的地方,但它其实非常关键。

Agent 在决定是否加载一个 Skill 时,最先看到的就是 namedescription。如果这部分写得含糊,后面的工作流再专业也可能没有机会执行。

入门级写法通常是这样:


name: my-skill

问题在于太泛。

“帮我做 X 事”可能匹配大量请求,容易过度触发;也可能因为缺少关键词,在真正该触发时没有被选中。触发不准的 Skill,用起来会非常别扭:该出现时不出现,不该出现时抢活干。

更稳妥的写法应该把适用场景、触发词和排除场景说清楚:


name: commit-message-generator  Use when the user wants to generate conventional commit messages based on staged git changes.
  Covers feat/fix/docs/refactor/test/chore types.
  Triggers on: "commit message", "生成提交信息", "git commit", "提交说明".
  Do NOT use for: general text writing, PR descriptions, or changelogs.
version: 1.2.0
author: your-name
license: MIT
metadata:
  hermes:
    tags: [git, commit, conventional-commits, automation]
    related_skills: [github-pr-workflow, github-code-review]

这里有几个细节值得注意:

  • description 要控制长度,比如 Hermes 场景下有 1024 字符限制
  • 触发词要覆盖用户真实会说的话,而不是只写系统术语
  • 反向触发条件很重要,它能减少误加载
  • version 不是装饰,后续排查问题时能定位行为变化
  • tagsrelated_skills 有助于分类检索和关联推荐

很多团队做到这里会卡住,因为他们习惯把 Skill 当“说明文档”写,而不是当“路由规则”写。Frontmatter 的核心价值不是介绍自己,而是帮助 Agent 做选择。

四、工作流要能按图施工

入门级 Skill 常见写法是段落式 这类描述对人类还算友好,对 Agent 就有点危险。“处理一下”这种表达,会把大量判断空间交给模型。一次结果不错,不代表下次还稳定。

专业 Skill 更适合用步骤、表格、代码块组合表达。

比如一个 API 查询步骤,可以先定义参数:

参数 说明
endpoint /api/public/items 主数据接口
mode selected 精选模式,默认值
since 动态计算 最近 24 小时
take 50 最大返回数量

然后给出可执行命令:

#!/usr/bin/env bash
set -euo pipefail

UA="Mozilla/5.0 SkillRunner/1.0"

# 计算时间窗口:兼容 macOS 和 Linux
if [[ "${OSTYPE:-}" == "darwin"* ]]; then
  since=$(date -u -v-24H  %Y-%m-%dT%H:%M:%SZ)
else
  since=$(date -u -d '24 hours ago'  %Y-%m-%dT%H:%M:%SZ)
fi

curl -sH "User-Agent: $UA" \
  "https://example.com/api/public/items?mode=selected&since=$since&take=50"

再明确输出格式:

[
  {
    "id": "item-001",
    "title": "Example title",
    "category": "AI",
    "url": "https://example.com/item-001",
    "published_at": "2026-01-01T08:00:00Z"
  }
]

这比一句“调用接口获取数据”稳定得多。

这里有个很现实的工程细节:macOS 和 Linux 的 date 参数不一样。很多脚本在本地跑没问题,到了容器或 CI 里就挂了。Skill 如果面向 Agent 执行,就必须提前写清楚这些差异。

专业工作流通常还会给每一步定义输入输出:

Step 输入 输出 失败处理
拉取数据 时间窗口、API 参数 JSON 数组 空结果时扩大时间窗口
过滤数据 原始 JSON 候选列表 字段缺失时跳过该条
生成内容 候选列表、模板 Markdown 内容为空时回退摘要模式
验证结果 Markdown 检查报告 不通过则重新生成或提示人工确认

这样写的好处不是“更规范”,而是降低上下文误解。Agent 不需要猜上一步给下一步什么,也不需要猜失败时怎么办。

五、错误处理才是分水岭

入门级 Skill 通常默认一切顺利。专业 Skill 默认一定会出错。

这个判断有点残酷,但生产环境基本就是这样。接口会返回空数组,认证会过期,编码会乱码,依赖版本会变化,用户输入会缺字段。你不写,Agent 不一定能自己补对。

一个好的 pitfalls 章节,建议按“现象 → 原因 → 处理”组织。

API 返回空结果

现象:items 数组为空,但 HTTP 状态码是 200。

可能原因:

  • 时间窗口内确实没有数据
  • since 参数格式错误
  • 接口使用 UTC 时间,而本地时间被误用

处理方式可以写成可执行逻辑:

#!/usr/bin/env bash
set -euo pipefail

response=$(curl -sH "User-Agent: Mozilla/5.0 SkillRunner/1.0" "https://example.com/api/public/items?mode=selected&since=$since")

count=$(echo "$response" | python3 -c '
import sys, json
data = json.load(sys.stdin)
print(len(data) if isinstance(data, list) else len(data.get("items", [])))
')

if [ "$count" -eq 0 ]; then
  echo "⚠️ 无数据,尝试扩大时间窗至 7 天" >&2

  if [[ "${OSTYPE:-}" == "darwin"* ]]; then
    since=$(date -u -v-7d  %Y-%m-%dT%H:%M:%SZ)
  else
    since=$(date -u -d '7 days ago'  %Y-%m-%dT%H:%M:%SZ)
  fi

  curl -sH "User-Agent: Mozilla/5.0 SkillRunner/1.0" \
    "https://example.com/api/public/items?mode=selected&since=$since"
else
  echo "$response"
fi

User-Agent 导致 403

现象:直接 curl 返回 403 Forbidden

原因:部分接口会拦截默认 curl UA,或者对未知客户端做限流。

处理:

  • 所有请求统一带浏览器 UA 或约定 UA
  • 不要在不同步骤里临时拼 curl 参数
  • 需要时把 UA 抽成变量,避免漏写

中文编码乱码

现象:终端输出中文乱码,或者写入文件后格式异常。

原因:

  • 远程终端 locale 不是 UTF-8
  • shell 命令中硬编码中文
  • 管道处理时混用了不同编码

处理:

  • 传递结构化数据时优先使用 JSON 文件
  • 避免把中文直接塞进复杂 shell 参数
  • 检查环境变量:LANGLC_ALL

专业 Skill 的 pitfalls 不应该等到最后“补一章”。更好的方式是边用边记。每次踩坑就加一条,长期看,这部分就是 Skill 的护城河。

六、别把 SKILL.md 写成垃圾桶

Skill 复杂以后,很多人会自然地把所有东西都塞进 SKILL.md。这很快会出问题。

一方面,SKILL.md 通常有大小限制。例如某些系统会限制在 100,000 字符左右,超过后可能截断。另一方面,内容太多会稀释关键路径,Agent 加载后反而抓不住重点。

更合理的拆法是:

professional-skill/
├── SKILL.md                  # 核心路径,控制在 8-15K 字符
├── references/
│   ├── api-reference.md       # API 完整文档
│   ├── error-codes.md         # 错误码对照表
│   └── best-practices.md      # 最佳实践
├── scripts/
│   ├── validate.sh            # 自动验证脚本
│   └── deploy.sh              # 部署脚本
└── templates/
    └── output-template.md     # 输出模板

SKILL.md 里只保留关键路径和必要引用:

## API Reference

详见 `references/api-reference.md`,覆盖以下端点:

| 端点 | 用途 | 认证 |
|---|---|---|
| `/api/data` | 数据采集 | 无 |
| `/api/publish` | 发布内容 | OAuth |
| `/api/media` | 上传图片 | OAuth |

比较稳的分工是:

文件位置 放什么 不适合放什么
SKILL.md 触发条件、关键流程、决策逻辑、验证入口 大段 API 文档、长模板、历史记录
references/ API、错误码、规范、背景资料 必须立即执行的核心步骤
scripts/ 可运行脚本、检查工具、自动化任务 需要模型理解的策略描述
templates/ 输出模板、固定格式 动态判断逻辑

这里的取舍很明确: SKILL.md 要短到 Agent 能抓住主线,辅助文件要细到复杂任务有据可查。

七、验证机制要自动化

靠人眼检查 Skill,早期可以,长期一定会漏。

最基础的是发布前 checklist:

- [ ] Frontmatter 以 --- 开头,无前导空白行
- [ ] name ≤ 64 字符,仅使用小写字母和连字符
- [ ] description ≤ 1024 字符
- [ ] description 包含触发词和反向触发词
- [ ] 所有代码块都能直接复制运行
- [ ] curl 命令都带 User-Agent
- [ ] 时间窗口计算兼容 macOS 和 Linux
- [ ] pitfalls 至少覆盖 3 个已知错误场景
- [ ] 总文件大小 ≤ 100,000 字符
- [ ] related_skills 引用的 Skill 确实存在

更进一步,可以写一个最小验证脚本。

下面是一个示意性的质量检查工具,适合放在 scripts/validate_skill.py

#!/usr/bin/env python3
"""
Skill 质量检查工具
用于检查 SKILL.md 的 frontmatter、长度、代码块和 pitfalls 覆盖情况。
"""

import json
import pathlib
import re
import sys

import yaml

def validate_skill(skill_path: str) -> dict:
    path = pathlib.Path(skill_path)
    content = path.read_text(encoding="utf-8")
    issues = []

    # 检查 1:frontmatter 必须在文件开头
    if not content.startswith("---"):
        issues.append("❌ 必须以 --- 开头,前面不能有空白行")

    # 检查 2:解析 frontmatter
    fm = {}
    try:
        parts = content.split("---", 2)
        if len(parts) >= 3:
            fm = yaml.safe_load(parts[1]) or {}
        else:
            issues.append("❌ frontmatter 未正确闭合")
    except yaml.YAMLError as exc:
        issues.append(f"❌ frontmatter YAML 解析失败: {exc}")

    # 检查 3:name 格式
    name = fm.get("name", "")
    if not name:
        issues.append("❌ 缺少 name")
    elif len(name) > 64:
        issues.append(f"❌ name 长度 {len(name)},超过 64 字符")
    elif not re.match(r"^[a-z0-9-] $", name):
        issues.append("❌ name 只能包含小写字母、数字和连字符")

    # 检查 4:description 长度
    desc = fm.get("description", "")
    desc_len = len(desc)
    if not    elif desc_len > 1024:
        issues.append(f"❌ description {desc_len} 字符,超过 1024 限制")

    # 检查 5:代码块数量
    code_blocks = re.findall(r"```[\s\S]*?```", content)
    if len(code_blocks)  100_000:
        issues.append(f"❌ 文件大小 {len(content)} 字符,超过 100,000 限制")

    return {
        "skill": skill_path,
        "valid": not any(item.startswith("❌") for item in issues),
        "issues": issues,
        "stats": {
            "size_chars": len(content),
            "code_blocks": len(code_blocks),
            "has_pitfalls": has_pitfalls,
        },
    }

if __name__ == "__main__":
    target = sys.argv[1] if len(sys.argv) > 1 else "SKILL.md"
    result = validate_skill(target)
    print(json.dumps(result, indent=2, ensure_ascii=False))

运行方式:

python3 scripts/validate_skill.py SKILL.md

输出示例:

{
  "skill": "SKILL.md",
  "valid": true,
  "issues": [],
  "stats": {
    "size_chars": 12458,
    "code_blocks": 7,
    "has_pitfalls": true
  }
}

这个脚本不复杂,但价值很高。它把一些低级错误挡在提交前,比如 frontmatter 前多了空行、description 超长、缺少 pitfalls。真正麻烦的线上问题,很多就是这些小地方滚出来的。

八、写一个提交信息 Skill

用 Git 提交信息生成器做例子,能看清一个专业 Skill 的基本骨架。

先创建目录:

mkdir -p commit-gen/{scripts,references,templates}
touch commit-gen/SKILL.md

SKILL.md 的 frontmatter 可以这样写:


name: commit-message-generator  Use when user wants to generate conventional commit messages from staged git changes.
  Supports feat/fix/docs/refactor/test/chore/style/ci/perf/revert types.
  Triggers on: "commit message", "提交信息", "git commit", "生成提交", "commit msg".
  Do NOT use for: PR descriptions, changelogs, release notes.
version: 1.0.0
author: your-name
license: MIT
metadata:
  hermes:
    tags: [git, commit, conventional-commits, automation]
    related_skills: [github-pr-workflow]

核心工作流从读取 staged diff 开始:

git diff --cached --no-color

如果没有 staged 变化,不要继续生成。直接提示用户先执行:

git add 

接着根据变更内容判断 commit type:

变更特征 type 示例
新功能、新文件 feat feat: add user login endpoint
Bug 修复 fix fix: handle null auth token
文档变更 docs docs: update API reference
重构且不改变行为 refactor refactor: extract validation logic
测试相关 test test: add coverage for auth flow
构建或 CI ci ci: upgrade node version to 20
性能优化 perf perf: reduce redundant DB queries
格式调整 style style: format lint warnings
回滚提交 revert revert: revert OAuth login change

输出遵循 Conventional Commits:

type(scope): subject

body

footer

要求可以写得更具体:

  • 第一行 type(scope): subject
  • scope 可选,优先取变化文件最多的目录
  • subject 控制在 50 字符以内
  • 正文说明 why,而不是重复 diff 里的 what
  • footer 用于 BREAKING CHANGECloses #123

示例输出:

feat(auth): add OAuth2 Google login

- Integrate with Google Identity Platform
- Support token refresh flow
- Add unit tests for token validation

Closes #42

最后由用户确认后执行:

git commit -e -F /tmp/commit-msg.txt

这里建议保留 -e,让用户在提交前编辑。完全自动 commit 看起来很爽,但风险也高。提交信息属于项目历史的一部分,给人类最后确认权更稳。

这个 Skill 还应该有 pitfalls:

场景 现象 处理
空 diff 用户没执行 git add 提示 staged 为空,不生成提交
subject 过长 第一行超过 50 字符 压缩措辞或把细节移到 body
scope 歧义 多个模块都有修改 取变更最多的目录,或省略 scope
diff 太大 上下文过长 先按文件摘要,再生成提交信息
混合变更 同时有 feat 和 fix 建议拆分提交,或选择主要变更类型

对应 checklist:

- [ ] 支持 feat/fix/docs/refactor/test/chore/style/ci/perf/revert
- [ ] staged diff 为空时优雅降级
- [ ] subject 长度 ≤ 50 字符
- [ ] scope 可以省略,不强行编造
- [ ] diff 过大时先摘要再生成
- [ ] 使用 git commit -e -F 保留人工确认
- [ ] 兼容 Git 2.0 

到这里,一个可维护的 Skill scaffold 就成型了。它不一定复杂,但关键部件齐全:触发、流程、异常、验证都有了。

九、从能跑到稳定的检查表

写 Skill 时,可以用这张表做最后检查。

层次 检查项 入门级 专业级
Frontmatter name / description 清晰
Frontmatter 包含触发词和反向触发词
Frontmatter 有 version / author / metadata
结构 分步骤工作流
结构 表格定义参数
结构 代码块可直接运行 ⚠️
结构 明确输入输出格式
鲁棒性 有 pitfalls 章节
鲁棒性 覆盖至少 3 个错误场景
鲁棒性 处理 OS / 版本兼容
工程化 拆分 references
工程化 有验证脚本
工程化 有历史记录机制
成本 总工作量 约 30 分钟 约 2-4 小时

这里没有绝对答案。

如果 Skill 只用一次,入门级完全够了。多写一堆结构化内容,反而浪费时间。工程上要警惕“为了专业而专业”。

但如果一个 Skill 会被频繁调用、多人复用,或者挂在关键业务流程上,就值得把它做成专业级。多花的那几个小时,通常会在后续调试、返工、误触发排查里赚回来。

专业 Skill 解决的不是“能不能做”,而是“能不能稳定做 100 次”。这也是 AI Agent 工程化里最容易被低估的一层:模型能力很重要,但把模型能力约束进可靠流程里,才是系统真正可用的开始。[DONE]

创作声明:本内容包含AI辅助创作,观点仅供参考。