Skills根本不是你想的那样!

Han Lee 通过抓包和逆向工程从第一性原理深度解析 Claude Agent Skills 系统的内部架构与工作机制,还原了Skills的运行原理(原文链接见文末)

一句话:Claude Skills 并非可执行代码,而是一套基于 prompt 注入的元工具架构。

bf9a0d8e-b648-42e0-83c1-65d7a3959b79.png

原文包含了大量的图表和概念,理解起来还是会有一些些困难的,所以我另外再结合官方的仓库提供的SkillA社自家的Claude  Skills 仓库都有哪些宝贝技能?将流程进行了进一步的简化,相信会让你对于Skills 全流程有一个更清晰的了解。

在使用官方Skill处理一个 PDF 文件时,你在终端输入:

> 帮我从 report.pdf 提取文字

现在我们来看背后到底发生了什么

第一步:Claude Code 启动时做了什么

你看到的

$ claude
Claude Code v1.0.0
Ready.
>

实际发生的(抓包内容)

Claude Code 扫描了这些目录:

~/.claude/skills/          # 你的个人 skills
.claude/skills/            # 项目 skills  
plugins/*/skills/          # 插件提供的 skills

找到了一个 pdf skill,文件结构:

~/.claude/skills/pdf/
├── SKILL.md              # 主文件
├── scripts/
│   └── extract.py        # Python 脚本
└── references/
    └── api-docs.md       # API 文档

读取 SKILL.md 的头部:

---
name: pdf
description: 从 PDF 提取文字和表格。当用户提到 PDF、文档提取时使用
allowed-tools: Bash(python:*), Read, Write
---

注意这里只读这几行! 完整的 SKILL.md 有 800 行,但现在不读

第二步:你的请求发送到 Claude

实际的 HTTP 请求简化后的版本如下:

POST https://api.anthropic.com/v1/messages
{
  "model": "claude-sonnet-4-5",
  "messages": [
    {
      "role": "user",
      "content": "帮我从 report.pdf 提取文字"
    }
  ],
  "tools": [
    {
      "name": "Skill",
      "description": "执行一个 skill\n\n可用的 skills:\n\"pdf\": 从 PDF 提取文字和表格。当用户提到 PDF、文档提取时使用\n\"excel\": 创建和编辑 Excel 文件\n\"ppt\": 创建演示文稿",
      "input_schema": {
        "type": "object",
        "properties": {
          "command": {"type": "string"}
        }
      }
    },
    {
      "name": "Bash",
      "description": "执行 bash 命令",
      "input_schema": {...}
    },
    {
      "name": "Read",
      "description": "读取文件",
      "input_schema": {...}
    }
  ]
}

关键点

  1. pdf skill 的 800 行内容没有发送
  2. 只发送了描述:“从 PDF 提取文字和表格。当用户提到 PDF、文档提取时使用”
  3. 这只有 20 个 token

第三步:Claude 决定调用 pdf skill

Claude 的响应(抓包内容)

{
  "content": [
    {
      "type": "text",
      "text": "我会帮你从 PDF 中提取文字。"
    },
    {
      "type": "tool_use",
      "id": "toolu_abc123",
      "name": "Skill",
      "input": {
        "command": "pdf"
      }
    }
  ]
}

Claude 的思考过程,我们看不到,大概它这么想的:


用户说 "从 report.pdf 提取文字"
→ 关键词: PDF, 提取, 文字
→ 看看我有什么工具...
→ "Skill" 工具里有 "pdf": 从 PDF 提取文字和表格
→ 完美匹配!
→ 调用 Skill 工具,command="pdf"

第四步:执行 Skill 工具(这里是重点了!)

Claude Code 收到 Claude 要调用 Skill(“pdf”)

现在才读取完整的 SKILL.md(800 行):

---
name: pdf
description: 从 PDF 提取文字和表格
allowed-tools: Bash(python:*), Read, Write
---

# PDF 处理专家

你是一个 PDF 处理专家。

## 工作流程

### 步骤 1: 检查文件
使用 Read 工具检查 PDF 文件是否存在

### 步骤 2: 提取文字
运行脚本:

python {baseDir}/scripts/extract.py report.pdf

### 步骤 3: 读取结果
Read 工具读取 output.txt

### 步骤 4: 展示给用户

## 可用工具
- Bash(python:*) - 运行 Python 脚本
- Read - 读取文件
- Write - 保存结果

基础目录: {baseDir}

准备发送给 Claude 的两条消息:

消息 1 (用户可见):

<command-message>正在加载 "pdf" skill</command-message>
<command-name>pdf</command-name>

消息 2 (用户看不见,但发给 API):

你是一个 PDF 处理专家。

## 工作流程

### 步骤 1: 检查文件
使用 Read 工具检查 PDF 文件是否存在
...
(完整 800 行)

第五步:发送增强后的请求给 Claude

你在界面上看到

> 帮我从 report.pdf 提取文字

正在加载 "pdf" skill

实际发送的完整 HTTP 请求

POST https://api.anthropic.com/v1/messages
{
  "model": "claude-sonnet-4-5",
  "messages": [
    {
      "role": "user",
      "content": "帮我从 report.pdf 提取文字"
    },
    {
      "role": "assistant",
      "content": [
        {"type": "text", "text": "我会帮你从 PDF 中提取文字。"},
        {"type": "tool_use", "name": "Skill", "input": {"command": "pdf"}}
      ]
    },
    {
      "role": "user",
      "content": "<command-message>正在加载 \"pdf\" skill</command-message>"
      // 这条你能看到 ↑
    },
    {
      "role": "user",
      "content": "你是一个 PDF 处理专家。\n\n## 工作流程\n\n### 步骤 1: 检查文件...",
      "isMeta": true
      // ↑ 这条你看不到,但发给了 Claude!
      // 完整 800 行都在这里!
    },
    {
      "role": "user",
      "content": {
        "type": "command_permissions",
        "allowedTools": ["Bash(python:*)", "Read", "Write"]
      }
      // ↑ 这个告诉系统:这些工具预先批准了
    }
  ]
}

Token 对比:

  • 第一次请求: ~50 tokens (只有描述)
  • 第二次请求: ~2,000 tokens (完整 skill 内容)

第六步:Claude 按照 skill 指令执行

现在 Claude 的"记忆"里有:
1.用户原始请求:“帮我从 report.pdf 提取文字”
2. 新注入的指令:“你是一个 PDF 处理专家。按照以下步骤。。。”
3. 预批准的工具:  Bash(python:*)ReadWrite

Claude 开始工作

第 1 个工具调用 - 检查文件:

{
  "type": "tool_use",
  "name": "Read",
  "input": {
    "path": "report.pdf",
    "description": "检查 PDF 文件是否存在"
  }
}

系统返回:

{
  "type": "tool_result",
  "content": "文件存在,大小 1.2MB"
}

第 2 个工具调用 - 运行 Python 脚本:

{
  "type": "tool_use",
  "name": "Bash",
  "input": {
    "command": "python ~/.claude/skills/pdf/scripts/extract.py report.pdf",
    "description": "提取 PDF 文字"
  }
}

这个调用不需要用户确认
为什么? 因为 Bash(python:*)allowed-tools 里被预批准了!

系统执行脚本,返回:

{
  "type": "tool_result",
  "content": "提取完成,输出保存到 output.txt"
}

第 3 个工具调用 - 读取结果:

{
  "type": "tool_use",
  "name": "Read",
  "input": {
    "path": "output.txt"
  }
}

返回:

{
  "type": "tool_result",
  "content": "这是一份关于 2024 年度财务报告...\n\n总收入: 1000 万美元\n..."
}

第七步:Claude 返回结果给你

你看到的

> 帮我从 report.pdf 提取文字

正在加载 "pdf" skill

✓ 已从 report.pdf 提取文字

内容如下:

这是一份关于 2024 年度财务报告

总收入: 1000 万美元
总支出: 800 万美元
净利润: 200 万美元
...

为什么Skill高效又节省呢?

没有 Skill 的情况

你需要这样说

> 用 pdftotext 命令从 report.pdf 提取文字,
> 然后读取输出文件,
> 如果遇到错误要处理,
> 最后格式化显示给我

每次都要重复这些指令!

API 请求(每次都发):

{
  "messages": [
    {
      "role": "user",
      "content": "用 pdftotext 命令从 report.pdf 提取文字,然后读取输出文件,如果遇到错误要处理,最后格式化显示给我"
    }
  ]
}

Token: ~80 tokens × 每次使用

有 Skill 的情况

你只需要说

> 帮我从 report.pdf 提取文字

第一次 API 请求(只发描述):

{
  "tools": [{
    "name": "Skill",
    "description": "...\"pdf\": 从 PDF 提取文字和表格..."
  }]
}

Token: ~20 tokens

第二次 API 请求(发完整指令):

{
  "messages": [
    ...,
    {"content": "你是 PDF 专家...(完整 800 行)", "isMeta": true}
  ]
}

Token: ~2,000 tokens × 只在激活时发一次

算下来你要处理 10 个 PDF

没有 Skill

10 次 × 80 tokens = 800 tokens

有 Skill

启动时: 20 tokens (描述)
第 1 次激活: 2,000 tokens (完整指令)
后续 9 次: 0 tokens (已在上下文中)
总计: 2,020 tokens

但实际上,Skill 会在对话过程中被压缩,所以后续调用可能会重新加载。

9844a410-6f72-4f30-8c29-47d80fcc24d7.png

如果你真的抓包 Claude Code,你会看到:

请求头

POST /v1/messages HTTP/1.1
Host: api.anthropic.com
anthropic-version: 2023-06-01
content-type: application/json
x-api-key: sk-ant-...

请求体(关键部分):

{
  "model": "claude-sonnet-4-5-20250929",
  "max_tokens": 4096,
  "tools": [
    {
      "name": "Skill",
      "description": "Execute a skill within the main conversation\n\n<skills_instructions>\nWhen users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively...\n\n<available_skills>\n\"pdf\": 从 PDF 提取文字和表格。当用户提到 PDF、文档提取时使用\n\"xlsx\": 创建和编辑 Excel 文件\n</available_skills>",
      "input_schema": {
        "type": "object",
        "properties": {
          "command": {
            "type": "string",
            "description": "The skill name (no arguments). E.g., \"pdf\" or \"xlsx\""
          }
        },
        "required": ["command"]
      }
    }
  ],
  "messages": [...]
}

看到了吗?pdf skill 的 800 行内容不在这里只有一行描述

所以Skills 到底是怎么工作的?

就这几步:

  1. 启动时: 扫描所有 skills,只读前几行(name + description)
  2. 发请求时: 把所有 skills 的简短描述塞进 “Skill” 工具里
  3. Claude 匹配: 用 AI 理解能力匹配用户意图和 skill 描述
  4. 激活 skill: 读取完整的 SKILL.md,注入到对话中
  5. 执行: Claude 按照注入的指令工作,工具预批准,不用确认

c7b20024-b322-4809-93a0-db9fc3a91f17.png

这种架构的设计核心优势

不用的 skill 不加载完整内容,可以有 100 个 skills,只占 2000 tokens,所有 skills 格式一样,容易分享安全, 工具权限仅在 skill 执行时有效。

https://link.bytenote.net/tvfkjl

希望这次够具体了!

图片