拆解 Skill Pipeline:AI 编程工具的「技能注入」到底是怎么实现的?

拆解 Skill Pipeline:AI 编程工具的「技能注入」到底是怎么实现的?

我一直以为 Claude 模型「理解」Skills。

这个错觉维持了整整一个月——从 1 月 22 日第一次被 Agent Skills 震撼到夜不能寐开始,到今天被自己的追问彻底打碎。

一个被广泛误解的事实

先说结论:

LLM 模型(无论是 Claude、GPT 还是 DeepSeek)对 Skill 这个概念一无所知。Skill Pipeline 是 100% 的应用层魔法。

这话什么意思?当你在 Claude Code 里触发一个 Skill——比如说你输入「帮我做个 PPT」,系统调用了 pptx 这个 Skill——整个过程中,Claude 模型只做了一件事:读 prompt、写回复。它不知道自己在「调用 Skill」,它只知道 prompt 里有一堆关于怎么生成演示文稿的详细指令。

让我拆解这个过程。

三段式流水线:注入 → 推理 → 执行

Skill Pipeline 的本质可以画成这样:

①
应用层 · 注入 Inject
  1. 启动时扫描 .claude/skills/ 目录
  2. 解析每个 SKILL.md 的 YAML frontmatter
  3. 把 name + description 注入 LLM 上下文
▼
②
模型层 · 推理 Reason
只做两件事:① 看到 Skill 描述列表 → 推理哪个匹配 → 请求加载;② 收到 SKILL.md 内容 → 按指令执行。
模型不知道「Skill」是什么,它只看到 prompt 文本。
▼
③
应用层 · 执行 Execute
  1. LLM 说「我要读 pptx」→ 应用层加载完整内容
  2. 把 SKILL.md 正文作为 tool_result 回传给 LLM

关键洞察:模型层做的是纯粹的自然语言推理——「这段描述和用户的请求很像,我应该先读取这个文件」。它用的是 general reasoning 能力,不是什么特殊的 Skill 解析模块。

Skill 的代价:到底多了几次 LLM 调用?

看完三段式流水线,一个自然的问题是:装了 Skill 之后,每次对话到底请求了多少次 LLM?

直觉上你可能觉得:「LLM 要先判断用不用 Skill,判断本身就是一次调用;再拿到 SKILL.md 执行,又是一次——那至少两次吧?」

不一定。用一个具体例子说清楚:

假设你装了 30 个 Skill(pptx、paper-writer、blog-writer……),现在你发了一条消息。

场景 A:你说「今天天气怎么样?」

─── API Call #1 ────────────────────────────────────────
发送给 LLM 的内容:
  - system prompt(CLAUDE.md 等 Instructions)
  - tools 数组:
      ├─ Skill tool(description 里列着 30 个 Skill 的摘要)
      ├─ read_file
      ├─ create_file
      └─ ... 其他工具
  - 用户消息: "今天天气怎么样?"

LLM 的决策(一次前向传播里同时完成):
  ✗ 不调 Skill(没有天气相关的 Skill)
  ✗ 不调 read_file(不需要)
  ✓ 直接输出文本: "我没有实时天气数据..."

结果: 1 次 API 调用,结束。
────────────────────────────────────────────────────────

场景 B:你说「帮我做个 PPT」

─── API Call #1 ────────────────────────────────────────
发送内容: 同上(tools 数组 + 用户消息)

LLM 的决策:
  ✓ 调用 Skill tool: { skill_name: "pptx" }
    (它在 description 里看到 pptx 的描述和用户请求匹配)
────────────────────────────────────────────────────────
     ↓ 应用层拦截,读取 SKILL.md 完整内容,回传给 LLM
─── API Call #2 ────────────────────────────────────────
发送内容: 之前的上下文 + tool_result(SKILL.md 正文)

LLM 的决策:
  ✓ 按 SKILL.md 里的指令开始工作
  ✓ 调用 create_file 创建 .pptx ...

结果: 2 次 API 调用(+ 后续工具调用 N 次)
────────────────────────────────────────────────────────

区别很清楚了:LLM 不需要「专门花一次调用来判断要不要用 Skill」。判断是它在处理你消息时的顺便推理——跟它决定要不要调 read_file、要不要调 run_terminal 是完全一样的机制。没匹配到就不调,不额外花一次 round-trip。

场景API 调用次数说明
无 Skill 匹配1 次跟没装 Skill 一模一样
触发 Skill2 次多的那 1 次 = 加载 SKILL.md 那一轮
Skill + 后续工具2 + N 次后续 create_file 等每个又是一轮

那「装了 Skill 但没触发」的代价在哪?不是 round-trip,而是 token。 每次请求都带着 30 个 Skill 的描述列表(≈ 几百到一千多 token),不管最终有没有触发。这就是 progressive disclosure 的设计哲学——用少量 token 的「目录」换取按需加载的灵活性。

Skill 的代价模型:固定开销 = 描述列表 token(每次请求都带,~数百 token)。触发开销 = +1 次 API round-trip + SKILL.md 正文 token(~数千 token)。不触发时,唯一的代价就是那几百 token 的「目录」——零额外延迟。

Claude Code 的实现:Meta-Tool 方案

今天做了快速调研,找到了两个关键信息源——都是别人做的硬核逆向工程:

来源作者方法
Han Lee 深度拆解Han-chung Lee源码逆向 + 代码重构
AgiFlow 网络抓包Vuong Ngo修改 Claude Code 注入网络日志

Han Lee 的源码逆向

Han Lee 做了一件非常硬核的事:他读了 Claude Code 的源码(JavaScript 混淆后的),逆向还原了 Skill 机制的完整实现。

核心发现:Skill 在 Claude Code 内部被实现为一个 meta-tool(元工具),不是 system prompt 的一部分。而且 Skill 不是普通工具——它的 prompt 字段是动态生成的。

每次 API 请求时,一个内部函数(Han Lee 重构后标记为 fN2())会重新聚合所有可用 Skill 的 name + description,生成 Skill tool 的 description:

// Han Lee 逆向重构的 Skill Tool 对象结构(简化)
{
    name: "Skill",
    async description() {
        // fN2() — 动态生成!每次 API 请求时重新聚合
        const skills = await getAllSkills();
        return skills.map(s =>
            `## ${s.name}\n${s.description}\nPath: ${s.path}`
        ).join("\n\n");
    },
    // 不是每个 Skill 一个 tool,而是一个统一的 Skill tool
    // Claude 通过 input 参数指定要读哪个 Skill
}

注意:不是「每个 Skill 一个 tool」,而是一个统一的 Skill tool,通过参数指定读哪个。所有 Skill 的描述聚合在这个 tool 的 description 里,每个 Skill 只占约 30-50 tokens。

调用后的核心机制是双消息注入——call() 方法 yield 出两条消息:

  • 第一条(isMeta: true):Skill 的元信息,作为上下文补充
  • 第二条(isMeta: false):SKILL.md 的完整正文,这才是真正的指令

为什么这样设计?因为一个项目可能有 30+ 个 Skill,如果全部正文都塞进 system prompt,token 直接爆炸。用 meta-tool 方案,初始 token 成本只有描述列表(几百 token),只有被选中的 Skill 才加载完整内容(可能几千 token)。这就是 progressive disclosure(渐进式披露)。

一个关键结论:

"The skill selection mechanism has no algorithmic routing or intent classification at the code level. Claude Code doesn't use embeddings, classifiers, or pattern matching... The decision happens inside Claude's forward pass through the transformer, not in the application code."

Skill 选择靠的是 LLM 通用推理能力,不是应用层的定制路由。代码层面没有 embedding、分类器、正则匹配——一切发生在模型的前向传播中。

AgiFlow 的网络抓包验证

AgiFlow 的 Vuong Ngo 更狠——他们修改了 Claude Code 的代码,拦截了所有发往 Claude API 的请求,完整记录了 JSON payload(data/skills.log)。

抓包结果直接确认:Skill 出现在 tools 数组中,不在 system prompt 里:

// 发送给 API 的请求结构(简化)
{
    "tools": [
        {
            "name": "Skill",
            "description": "## pptx\nPresentation creation, editing, and analysis...\n## paper-writer\nAI/ML 学术论文写作顾问..."
        },
        // ... 其他普通 tools
    ]
}

// Claude 请求调用 Skill
{
    "type": "tool_use",
    "name": "Skill",
    "input": { "skill_name": "pptx" }
}

// 应用层 yield 双消息:
// 消息 1 (isMeta: true) — 元信息
// 消息 2 (isMeta: false) — SKILL.md 完整正文
{
    "type": "tool_result",
    "content": "# PPTX\n\nPresentation creation, editing, and analysis..."
}

// Claude 按 SKILL.md 指令开始执行
{
    "type": "text",
    "content": "好的,我来帮你做演示文稿..."
}

完整生命周期是标准的 tool_use → 双消息注入(meta + body)→ 按指令执行。

VS Code Copilot 的实现:Chat Extension API 架构

我原本以为 VS Code Copilot 只是「透传」——把 SKILL.md 内容丢给后端模型就完了。错了。

Copilot Chat 的实现是闭源的(在 Copilot 扩展内部),但从社区调试和 VS Code Output panel 中可以看到关键线索:

  1. 扫描阶段:启动时扫描 .github/copilot-instructions.md、.github/skills/ 等路径
  2. 解析阶段:提取 YAML frontmatter 中的 name、description
  3. 注入阶段:Skill 被注册为 chatParticipant 的工具,描述列表注入到和模型交互的 prompt 中
  4. 匹配阶段:Output panel 的 [tool-selection] 标签可以看到 score: 置信度值——模型给每个 Skill 打分,选最高的

和 Claude Code 的核心区别在于架构层次:Copilot 基于 VS Code 的 Chat Extension API,Skill 本质上是 chatParticipant 的工具;而 Claude Code 用更底层的 tools 数组直接和 API 交互。但五阶段 Pipeline 完全一致——应用层扫描 → 描述注入 → 模型推理 → 延迟加载。

在 Output panel 的 [context] 标签下,你能看到 Copilot 准备注入的所有上下文,包括 Skill 描述。这不是黑盒——VS Code 给了足够的调试信息。

OpenClaw 的实现:最工程化的方案

如果说 Claude Code 是「精巧」,VS Code Copilot 是「稳健」,那 OpenClaw(我本地也在用的开源 AI 助手)就是「工程化到极致」。

我读了 OpenClaw 的源码,它的 Skill Pipeline 复杂度远超前两者:

六层优先级的多源发现

优先级从低到高:
① extra dirs     — 配置的额外目录 + 插件目录
② bundled        — 项目内置 /skills/
③ managed        — ~/.openclaw/skills/(用户手动安装)
④ agents-personal — ~/.agents/skills/(个人全局)
⑤ agents-project — <workspace>/.agents/skills/(项目级)
⑥ workspace      — <workspace>/skills/(最高优先级)

同名 Skill 高优先级覆盖低优先级——这意味着你可以用项目级 Skill 覆盖全局默认行为,非常灵活。

丰富的 Frontmatter 扩展

OpenClaw 在标准的 name + description 之外,扩展了大量元数据:

---
name: github
description: "GitHub operations via `gh` CLI..."
metadata:
  openclaw:
    emoji: "🐙"
    always: false          # 是否始终加载
    os: ["darwin", "linux"] # 平台限制
    requires:
      bins: ["gh"]          # 必须安装的 CLI 工具
      env: ["GITHUB_TOKEN"] # 必须设置的环境变量
    install:                # 自动安装依赖!
      - method: brew
        package: gh
---

这意味着 OpenClaw 的 Skill 可以:

  • 声明运行时依赖:需要 gh CLI?需要 GITHUB_TOKEN?frontmatter 里写清楚
  • 平台过滤:macOS 专属 Skill 不会出现在 Linux 上
  • 自动安装:缺少依赖时可以自动用 brew/npm/pip 安装

注入方式:System Prompt + <available_skills> XML

## Skills (mandatory)
Before replying: scan <available_skills> <description> entries.
- If exactly one skill clearly applies: read its SKILL.md with `read`, then follow it.
- If multiple could apply: choose the most specific one, then read/follow it.
- If none clearly apply: do not read any SKILL.md.
Constraints: never read more than one skill up front; only read after selecting.
<available_skills>
  <skill name="github" path="~/.openclaw/skills/github/SKILL.md">
    GitHub operations via gh CLI...
  </skill>
  ...
</available_skills>

OpenClaw 用 system prompt 注入而非 meta-tool,并且多了几个精巧的工程细节:

  • 路径压缩:/Users/polly/.openclaw/ → ~/...,节省 token
  • 二分搜索裁剪:当 Skill 数量太多时,按字符预算自动裁剪,保证不超过 maxSkillsPromptChars(默认 30,000 字符)
  • chokidar 文件监控:Skill 目录变化时自动热重载,不需要重启

命令分派:用户也能手动触发

OpenClaw 独有的一个设计——Skill 可以通过 frontmatter 声明为用户可调用的命令:

user-invocable: true           # 允许 /github 手动触发
disable-model-invocation: true  # 但不允许 LLM 自动选择
command-dispatch: tool          # 直接分派到具体工具
command-tool: gh_cli            # 目标工具名

这等于在 LLM 匹配之外,提供了一条确定性的调用路径——用户输入 /github 就一定触发 github Skill,不依赖 LLM 推理。

三家实现横向对比

看了 Claude Code、Copilot 和 OpenClaw,可以画一张对比表:

维度Claude CodeVS Code CopilotOpenClaw
注入方式Meta-tool(tools 数组)System instructionsSystem prompt + XML
加载触发tool_use → tool_result模型推理 + 内置机制LLM 调用 read 工具
发现源项目 + 全局项目 + 全局6 层优先级
运行时过滤无无OS/依赖/配置检查
热重载无无chokidar 监控
自动安装无无brew/npm/pip/go
用户手动触发/skill-name(自动补全)/skill-name(自动补全)/command 斜杠命令
Token 优化isMeta 两阶段未公开路径压缩 + 二分裁剪
底层模型Claude 专属多模型(GPT/Claude/Gemini)多模型

核心洞察:三家的五阶段框架(发现→解析→注入→匹配→加载)完全一致,差异全在工程细节上。

SKILL.md:一个正在成型的开放标准

在研究这三家实现的过程中,我发现了一件有意思的事:SKILL.md 已经在 2025-2026 年间演化为一个事实上的开放标准。

agentskills.io 记录了这个标准的采纳情况——20+ 平台原生支持同一格式:

平台项目级目录个人级目录
Claude Code.claude/skills/~/.claude/skills/
GitHub Copilot.github/skills/~/.copilot/skills/
Cursor.cursor/skills/~/.cursor/skills/
Gemini CLI.gemini/skills/~/.gemini/skills/
OpenAI Codex.agents/skills/~/.agents/skills/
OpenClawskills/~/.openclaw/skills/

这意味着什么?你写的 Skill 可以跨工具复用。 同一套 SKILL.md 放到 .claude/skills/ 被 Claude Code 读取,做个 symlink 到 .github/skills/ 就能被 Copilot 读取——因为格式完全一致,只是目录约定不同。

这就是为什么我说 Skill 是跨模型、跨工具的通用模式:不是某一家的私有 feature,而是整个行业正在收敛的标准。

先等一下:Cursor Rules 是 Skill 吗?

这里要做一个重要的概念辨析。很多人把 Cursor 的 .cursorrules、Windsurf 的规则文件等同于 Skill。它们不是。

特性Instructions (Rules)Skills
加载时机每次请求都加载按需延迟加载
Token 成本固定消耗(全量注入)只有被选中时才消耗
匹配机制无(始终生效)LLM 语义推理匹配
粒度单文件,项目级多文件目录,可独立管理
代表.cursorrules, CLAUDE.md, .github/copilot-instructions.md.claude/skills/, .github/skills/

Instructions/Rules 等价于「让每个人都背一本通用手册」;Skills 等价于「给你一个专家团队的通讯录,需要谁就叫谁」。

这是一个质的飞跃:从全量注入到智能按需加载。

Cursor 目前只有 Instructions 层(.cursorrules),没有原生 Skill 系统。同理,Cline、Roo Code 的 custom instructions 也是 Instructions 层。真正实现了 Skill Pipeline 的,目前只有 Claude Code、VS Code Copilot 和 OpenClaw。

Description 是 Phase 4 的灵魂

五阶段模型中,Phase 4(匹配)的成败取决于一个字段——description。

这是我写了 30+ 个 Skill 后总结出的实战经验:

好的 description——像一份精准的岗位说明书:

Presentation creation, editing, and analysis. Use when Claude needs to
work with .pptx files for: (1) Creating new presentations, (2) Modifying
content, (3) Working with layouts, (4) Adding speaker notes.
NOT for PDF or Word documents (use pdf/docx skills instead).

差的 description——像一句废话:

A skill for presentations.

区别在哪?LLM 在 Phase 4 做的是 description vs. 用户输入 的语义匹配。如果 description 太模糊,LLM 无法做出准确判断,要么不触发,要么触发错的 Skill。

写 description 的三个原则:

  1. 列举具体场景:「(1) Creating presentations, (2) Modifying content, (3) Adding layouts」
  2. 包含触发关键词:「.pptx, presentation, slides, 演示文稿」——直接告诉 LLM 什么词该匹配
  3. 说明不适用场景:「NOT for PDF or Word documents (use pdf/docx skills)」——让 LLM 知道什么时候不该选你

纠正我的两个误解

在今天的研究之前,我有两个错误的心智模型:

误解 1:「Claude 模型在解析 SKILL.md」

真相:Claude 模型对 SKILL.md 的格式(YAML frontmatter、--- 分隔符等)没有任何特殊处理。frontmatter 解析是应用层(Claude Code 客户端)用正则 / YAML 库做的。模型看到的只是处理过的 prompt 文本。

误解 2:「VS Code Copilot 只是透传」

真相:Copilot 有自己的完整实现——文件扫描、frontmatter 解析、描述提取、prompt 注入、延迟加载——一整套 pipeline,和 Claude Code 平行独立。

这两个纠正带来一个重要推论:

任何 LLM 应用都可以实现 Skill 系统,因为它不依赖模型能力,只依赖应用层工程。

通用 Skill Pipeline 五阶段模型

综合 Claude Code、VS Code Copilot 和 OpenClaw 三家的实现,Skill Pipeline 可以抽象为五个阶段:

1
发现 Discovery
扫描约定路径(~/.claude/skills/、.github/skills/、skills/ 等),找到所有 SKILL.md 文件,支持多源聚合(全局 + 项目级 + 自定义)
2
解析 Parse
提取 YAML frontmatter(name, description, trigger_keywords...),正文暂不加载(延迟加载),构建 SkillEntry 数据结构
3
注入 Inject
只注入摘要信息(name + description),≈ 几百 token
方案 A: 作为 tool(Claude Code meta-tool) | 方案 B: 作为 system prompt 片段(OpenClaw)
4
匹配 Match
LLM 通用推理:description vs. 用户输入,结果为选中 0 或 1 个 Skill
可选加速: 关键词精确匹配 | 可选兜底: LLM 语义匹配
5
加载 Load
读取完整 SKILL.md 正文,LLM 按正文指令执行后续任务
方案 A: 作为 tool_result 回传(Claude Code) | 方案 B: 追加到 system prompt(OpenClaw)

每个实现可以在每个阶段做不同选择,但五阶段框架是通用的。

为什么这个架构如此优雅

回头看,这个设计之所以优雅,是因为它完美利用了 LLM 的两个核心能力:

  1. 语义理解:模型能从几十个 Skill 描述中找到最匹配的那个(Phase 4)
  2. 指令遵循:模型能按 SKILL.md 中的详细指令执行复杂工作流(Phase 5)

而应用层只需要做 LLM 不擅长的事情:文件 I/O、YAML 解析、token 预算管理。

这种分工——应用层做结构化管道,模型层做语义推理——是当前 AI 应用架构的一个典范。

那为什么 Claude Code 体验更好?

既然 Skill Pipeline 是应用层魔法,任何强 LLM 都能跑,为什么大家普遍觉得 Claude Code 的 Skill 体验最好?

不是因为 Claude 模型「认识」SKILL.md,而是三个工程原因:

  1. Claude 模型的指令遵循能力强——注入的 SKILL.md 指令它真的会一步步跟,不打折、不跳步
  2. Claude Code 的 progressive disclosure 做得最成熟——meta-tool + isMeta 双消息注入,token 效率高
  3. Skill 可以声明 allowed-tools——在 SKILL.md 里指定该 Skill 需要哪些工具权限,权限模型更精细

但本质上,任何足够强的 LLM + 任何实现了 Skill 加载的 IDE,都能跑同一套 Skills。 这也是为什么我的同一套 Skills 可以同时被 Claude Code 和 Copilot 读取——只要把目录也放到 .github/skills/ 或做个 symlink。

从误解到精进

一个月前我在那篇觉醒文章里写道:「Agent Skills + MCP 的组合,完全改变了游戏规则。」

现在我理解得更深了。Skills 改变游戏规则的方式不是靠什么黑魔法,而是靠一个极其朴素但极其有效的应用层设计模式。

它朴素到任何一个开发者都能在 30 分钟内实现出来(我在 Jarvis 中就是这么做的——从设计到全功能验证通过,不到半小时)。它有效到 Claude Code、Copilot、Gemini CLI、OpenAI Codex 等 20+ 平台全都在用它——而且用的是同一套 SKILL.md 格式。

理解底层机理后,Skill 不再是「别人家的魔法」,而是「自己能掌控的架构模式」。

这种感觉,比盲目的惊叹好太多了。


延伸阅读:

Comments