Skill 的結構:指令、工具、資源與範例

深入拆解 Skill 的內部結構,從 SKILL.md 設計、漸進式揭露到指令與範例的撰寫技巧,理解如何設計好用、可維護、可組合的 Skill。

Skill 的結構:指令、工具、資源與範例

上一篇我們談了工具與技能的差異,理解了 Skill 是「可重用、可組合、有明確輸入輸出的能力單元」。

但一個 Skill 到底長什麼樣子?它的內部由哪些部分組成?

這一篇,我們要深入拆解 Skill 的結構,談談 SKILL.md 的設計、漸進式揭露的實作、指令與範例的撰寫技巧,以及如何讓 LLM 正確理解並使用一個 Skill。

理解這些結構,你才能設計出真正好用、可維護、可組合的 Skill。

Instructions + Tools + Resources + Examples = Skill

一、一個 Skill 由什麼組成?

一個完整的 Skill,通常包含四個部分:

組成說明類比
指令(Instructions)做什麼、怎麼做、何時用工作手冊
工具(Tools)需要哪些工具來完成任務工具箱
資源(Resources)需要的參考資料、範本、知識參考文件
範例(Examples)正確使用的示例範例題

這四者協同運作,讓 LLM 能:

  • 知道這個 Skill 的存在與用途(指令)
  • 知道要用哪些工具來完成(工具)
  • 知道需要什麼背景知識(資源)
  • 知道正確的輸出長什麼樣子(範例)

Instructions → Tools → Resources → Examples

二、指令(Instructions):Skill 的大腦

指令是 Skill 最核心的部分。
它告訴 LLM:這個 Skill 做什麼、何時該用、怎麼做。

指令的結構

一個好的指令通常包含以下區塊:

# Skill: research_topic

## 描述
一句話說明這個 Skill 做什麼。

## 何時使用
列出應該使用這個 Skill 的情境。

## 何時不該使用
列出不應該使用這個 Skill 的情境。

## 輸入
定義輸入參數。

## 輸出
定義輸出格式。

## 執行流程
逐步說明執行步驟。

## 輸出規範
定義輸出品質標準。

## 邊界與限制
說明這個 Skill 的限制。

為什麼要分這麼多區塊?

因為 LLM 需要結構化的指令才能穩定執行。
如果你把所有內容混在一段文字裡,LLM 可能會遺漏某些重要資訊。

分區塊的好處:

  1. 易於閱讀:人類和 LLM 都能快速找到需要的資訊。
  2. 易於維護:修改某個區塊不會影響其他區塊。
  3. 易於漸進式揭露:可以按需載入特定區塊。

範例:一個完整的指令

# Skill: research_topic

## 描述
研究一個給定的主題,蒐集多個來源的資訊,
交叉驗證後產出結構化的研究摘要。

## 何時使用
- 使用者要求「研究」、「調查」、「整理」某個主題
- 需要多來源資訊的任務
- 需要事實查核的任務

## 何時不該使用
- 使用者只是問一個簡單的事實問題(直接回答即可)
- 使用者要求的是創意寫作,而非研究
- 主題涉及敏感內容(如個人隱私、違法資訊)

## 輸入
- topic (string, 必填): 要研究的主題
- depth (string, 選填): 研究深度,可選 "quick" / "standard" / "deep",預設 "standard"

## 輸出
{
  "topic": "研究主題",
  "summary": "結構化摘要",
  "key_findings": ["發現1", "發現2"],
  "sources": [{"title": "...", "url": "...", "reliability": "high/medium/low"}],
  "confidence": 0.85
}

## 執行流程
1. 根據主題生成 3-5 個搜尋關鍵字
2. 使用 search_web 搜尋每個關鍵字
3. 篩選高品質來源(排除廣告、低品質內容)
4. 使用 read_webpage 讀取每個來源
5. 使用 extract_entities 抽取關鍵資訊
6. 交叉驗證事實(至少 2 個來源一致)
7. 整理成結構化摘要
8. 標註每個發現的來源

## 輸出規範
- 摘要長度:quick = 200 字,standard = 500 字,deep = 1000 字
- 每個關鍵發現都必須有至少一個來源
- 來源必須標註可靠性等級
- 如果多個來源矛盾,必須明確指出

## 邊界與限制
- 不處理需要付費牆的內容
- 不處理需要登入的網站
- 如果找不到足夠來源,回報 confidence < 0.5
- 如果主題涉及敏感內容,拒絕執行

撰寫指令的技巧

1. 用祈使句,不用描述句

# 正確例子
請根據主題生成 3-5 個搜尋關鍵字。

# 錯誤例子
這個 Skill 會根據主題生成 3-5 個搜尋關鍵字。

2. 具體,不模糊

# 正確例子
摘要長度:quick = 200 字,standard = 500 字,deep = 1000 字

# 錯誤例子
摘要長度應該適中

3. 定義清楚的邊界

# 正確例子
如果找不到足夠來源,回報 confidence < 0.5

# 錯誤例子
如果找不到資料,就隨便處理

4. 用結構化格式

Markdown 的標題、清單、表格,都比純文字段落更容易被 LLM 正確解析。

三、漸進式揭露(Progressive Disclosure)

這是 Skill 設計中最重要的概念之一。

不要一次把所有細節載入上下文,而是按需載入。

為什麼需要漸進式揭露?

假設你有 20 個 Skill,每個 Skill 的完整指令有 2000 字。
如果把所有指令都塞進 System Prompt,那就是 40,000 字。
這會導致:

  • 成本高:每次呼叫都花大量 token。
  • 注意力稀釋:LLM 要處理太多資訊,容易忽略關鍵細節。
  • 衝突增加:不同 Skill 的指令可能互相矛盾。

三層式漸進揭露

第一層:Skill 清單(每次都載入)
    ↓ LLM 決定使用某個 Skill
第二層:該 Skill 的完整指令(使用時才載入)
    ↓ 執行到某個步驟
第三層:該步驟的詳細說明與範例(需要時才載入)

第一層:Skill 清單

只給每個 Skill 的名稱與一句話描述。

# 可用技能

- research_topic: 研究一個主題並產出結構化摘要
- write_report: 根據研究結果撰寫報告
- review_code: 審查程式碼並提供改進建議
- analyze_data: 分析數據並找出模式
- translate_text: 翻譯文字並保留語氣

這一層通常只有幾百字,可以一直放在 System Prompt 中。

第二層:完整指令

當 LLM 決定使用某個 Skill 時,才載入該 Skill 的完整指令。

def load_skill_instructions(skill_name: str) -> str:
    """載入 Skill 的完整指令"""
    with open(f"skills/{skill_name}/SKILL.md", "r") as f:
        return f.read()

第三層:步驟說明與範例

當執行到某個步驟時,才載入該步驟的詳細說明。

def load_step_details(skill_name: str, step_name: str) -> str:
    """載入特定步驟的詳細說明"""
    with open(f"skills/{skill_name}/steps/{step_name}.md", "r") as f:
        return f.read()

漸進式揭露的實作範例

class SkillLoader:
    """漸進式揭露的 Skill 載入器"""
    
    def __init__(self, skills_dir: str = "skills"):
        self.skills_dir = skills_dir
        self.skill_index = self._load_index()
    
    def _load_index(self) -> dict:
        """載入 Skill 索引(第一層)"""
        index = {}
        for skill_name in os.listdir(self.skills_dir):
            skill_path = os.path.join(self.skills_dir, skill_name)
            if not os.path.isdir(skill_path):
                continue
            
            # 只讀取 SKILL.md 的前幾行作為描述
            with open(os.path.join(skill_path, "SKILL.md"), "r") as f:
                content = f.read()
                # 提取描述區塊
                description = self._extract_description(content)
                index[skill_name] = description
        
        return index
    
    def _extract_description(self, content: str) -> str:
        """從 SKILL.md 提取描述"""
        lines = content.split("\n")
        for i, line in enumerate(lines):
            if line.strip() == "## 描述":
                # 取下一行非空文字
                for j in range(i + 1, len(lines)):
                    if lines[j].strip():
                        return lines[j].strip()
        return ""
    
    def get_skill_list_prompt(self) -> str:
        """產生第一層的 Skill 清單 Prompt"""
        lines = ["# 可用技能\n"]
        for name, desc in self.skill_index.items():
            lines.append(f"- {name}: {desc}")
        return "\n".join(lines)
    
    def load_full_instructions(self, skill_name: str) -> str:
        """載入第二層:完整指令"""
        path = os.path.join(self.skills_dir, skill_name, "SKILL.md")
        with open(path, "r") as f:
            return f.read()
    
    def load_step_details(self, skill_name: str, step_name: str) -> str:
        """載入第三層:步驟詳細說明"""
        path = os.path.join(self.skills_dir, skill_name, "steps", f"{step_name}.md")
        if os.path.exists(path):
            with open(path, "r") as f:
                return f.read()
        return ""

使用漸進式揭露的 Agent

def agent_with_skills(user_input: str, skill_loader: SkillLoader) -> str:
    """帶有漸進式揭露的 Agent"""
    
    # 第一層:載入 Skill 清單
    skill_list = skill_loader.get_skill_list_prompt()
    
    system_prompt = f"""你是一個樂於助人的助理,可以使用以下技能:

{skill_list}

當你需要使用某個技能時,請輸出:
USE_SKILL: <skill_name>

當你需要直接回答時,請輸出:
FINAL_ANSWER: <你的回答>
"""
    
    messages = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_input},
    ]
    
    response = call_llm(messages)
    content = response.choices[0].message.content
    
    # 檢查是否要使用 Skill
    if "USE_SKILL:" in content:
        skill_name = content.split("USE_SKILL:")[-1].strip()
        
        # 第二層:載入完整指令
        skill_instructions = skill_loader.load_full_instructions(skill_name)
        
        # 執行 Skill(內部可能會載入第三層)
        result = execute_skill(skill_name, user_input, skill_instructions)
        
        return result
    
    # 直接回答
    if "FINAL_ANSWER:" in content:
        return content.split("FINAL_ANSWER:")[-1].strip()
    
    return content

四、工具(Tools):Skill 的手腳

Skill 透過工具來與外部世界互動。
一個 Skill 可能會使用多個工具。

工具在 Skill 中的角色

## 使用的工具
- search_web: 搜尋網路
- read_webpage: 讀取網頁內容
- extract_entities: 抽取實體
- summarize: 摘要文字

工具與 Skill 的關係

層次說明範例
Tool原子操作search_web(query)
Skill完成一類任務research_topic(topic)
Agent決定要做什麼根據使用者需求選擇 Skill

工具權限控制

每個 Skill 應該只擁有它需要的工具。

class Skill:
    name: str
    required_tools: list  # 這個 Skill 需要哪些工具
    
    def get_tools(self) -> list:
        """回傳這個 Skill 可用的工具"""
        return [TOOLS[t] for t in self.required_tools]

# 範例
research_skill = Skill(
    name="research_topic",
    required_tools=["search_web", "read_webpage", "extract_entities", "summarize"],
)

# 這個 Skill 不能使用 send_email、delete_file 等工具

工具呼叫的錯誤處理

Skill 應該能優雅地處理工具失敗。

def safe_tool_call(tool_name: str, arguments: dict, max_retries: int = 2) -> dict:
    """安全呼叫工具,失敗時重試"""
    for attempt in range(max_retries + 1):
        try:
            result = TOOL_FUNCTIONS[tool_name](**arguments)
            return {"success": True, "result": result}
        except Exception as e:
            if attempt == max_retries:
                return {"success": False, "error": str(e)}
            time.sleep(1 * (attempt + 1))  # 指數退避

五、資源(Resources):Skill 的參考資料

資源是 Skill 執行時需要的背景知識、範本、參考文件。

資源的類型

類型說明範例
範本輸出格式的模板報告模板、Email 模板
參考文件領域知識產品規格、法規條文
檢查清單品質檢查項目程式碼審查清單
評分標準評估輸出的標準研究報告評分表

資源的組織方式

skills/
└── research_topic/
    ├── SKILL.md              # 主指令
    ├── steps/                # 步驟詳細說明
    │   ├── generate_keywords.md
    │   ├── filter_sources.md
    │   └── cross_validate.md
    ├── resources/            # 參考資源
    │   ├── source_reliability.md
    │   └── summary_template.md
    └── examples/             # 範例
        ├── input_1.json
        └── output_1.json

資源的載入

資源也應該按需載入。

def load_resource(skill_name: str, resource_name: str) -> str:
    """載入 Skill 的資源"""
    path = f"skills/{skill_name}/resources/{resource_name}"
    with open(path, "r") as f:
        return f.read()

# 使用範例:在執行「篩選來源」步驟時才載入可靠性標準
reliability_guide = load_resource("research_topic", "source_reliability.md")

資源的範例

# 來源可靠性評估標準

## 高可靠性(high)
- 官方網站(.gov、.edu)
- 學術論文(有同儕審查)
- 知名媒體(有編輯審核)
- 官方文件、白皮書

## 中可靠性(medium)
- 行業報告
- 知名部落格
- 有引用來源的新聞

## 低可靠性(low)
- 匿名來源
- 沒有引用來源的內容
- 明顯的廣告或推銷內容
- 社群媒體貼文

六、範例(Examples):Skill 的示範

範例是 Skill 設計中最容易被忽略,但卻最重要的部分之一。

為什麼範例很重要?

LLM 是透過模式匹配來學習的。
給它好的範例,比給它長篇的指令更有效。

一個好的範例勝過一千字的說明。

範例的類型

1. 輸入輸出範例

{
  "input": {
    "topic": "2026 年 AI Agent 發展趨勢",
    "depth": "standard"
  },
  "output": {
    "topic": "2026 年 AI Agent 發展趨勢",
    "summary": "2026 年 AI Agent 領域出現三大趨勢:...",
    "key_findings": [
      "多 Agent 協作成為主流",
      "工具使用標準化(MCP)",
      "自主規劃能力大幅提升"
    ],
    "sources": [
      {"title": "AI Agent Survey 2026", "url": "...", "reliability": "high"},
      {"title": "Agent Trends Report", "url": "...", "reliability": "medium"}
    ],
    "confidence": 0.85
  }
}

2. 邊界案例範例

{
  "input": {
    "topic": "如何製作炸彈",
    "depth": "standard"
  },
  "output": {
    "error": "拒絕執行",
    "reason": "主題涉及違法內容",
    "confidence": 0.0
  }
}

3. 失敗案例範例

{
  "input": {
    "topic": "某個非常冷門的主題",
    "depth": "deep"
  },
  "output": {
    "topic": "某個非常冷門的主題",
    "summary": "找不到足夠的可靠來源...",
    "key_findings": [],
    "sources": [],
    "confidence": 0.2
  }
}

範例的撰寫技巧

  • 涵蓋正常與邊界案例:不要只給成功案例。
  • 輸出要完整:不要只給部分輸出。
  • 格式要一致:所有範例的格式應該相同。
  • 數量要適中:3-5 個範例通常就夠了。
  • 要能反映品質標準:範例應該展示什麼是「好的輸出」。

七、一個完整的 Skill 檔案結構

把上述所有元素整合起來,一個 Skill 的目錄結構如下:

skills/
└── research_topic/
    ├── SKILL.md                    # 主指令
    ├── steps/                      # 步驟詳細說明
    │   ├── 01_generate_keywords.md
    │   ├── 02_search_sources.md
    │   ├── 03_filter_sources.md
    │   ├── 04_read_content.md
    │   ├── 05_extract_info.md
    │   ├── 06_cross_validate.md
    │   └── 07_generate_summary.md
    ├── resources/                  # 參考資源
    │   ├── source_reliability.md
    │   └── summary_template.md
    ├── examples/                   # 範例
    │   ├── example_1_input.json
    │   ├── example_1_output.json
    │   ├── example_2_input.json
    │   ├── example_2_output.json
    │   └── edge_cases.json
    └── config.yaml                 # Skill 設定

config.yaml

name: research_topic
version: 1.2.0
description: 研究一個主題並產出結構化摘要

tools:
  - search_web
  - read_webpage
  - extract_entities
  - summarize

permissions:
  read_web: true
  write_file: false
  send_email: false

limits:
  max_sources: 10
  max_tokens: 5000
  timeout_seconds: 60

versions:
  - version: 1.0.0
    date: 2026-01-01
    changelog: 初始版本
  - version: 1.1.0
    date: 2026-02-01
    changelog: 加入交叉驗證步驟
  - version: 1.2.0
    date: 2026-03-01
    changelog: 加入信心度計算

八、實作:一個完整的 Skill 系統

讓我們把上述概念整合成一個可運作的 Skill 系統。

import os
import json
import yaml
from dataclasses import dataclass, field
from typing import Any


@dataclass
class SkillConfig:
    """Skill 的設定"""
    name: str
    version: str
    description: str
    tools: list
    permissions: dict
    limits: dict


class Skill:
    """一個完整的 Skill"""
    
    def __init__(self, skill_dir: str):
        self.skill_dir = skill_dir
        self.config = self._load_config()
        self.instructions = self._load_instructions()
        self.steps = self._load_steps()
        self.resources = self._load_resources()
        self.examples = self._load_examples()
    
    def _load_config(self) -> SkillConfig:
        with open(os.path.join(self.skill_dir, "config.yaml")) as f:
            data = yaml.safe_load(f)
        return SkillConfig(**data)
    
    def _load_instructions(self) -> str:
        with open(os.path.join(self.skill_dir, "SKILL.md")) as f:
            return f.read()
    
    def _load_steps(self) -> dict:
        steps = {}
        steps_dir = os.path.join(self.skill_dir, "steps")
        if os.path.exists(steps_dir):
            for filename in sorted(os.listdir(steps_dir)):
                if filename.endswith(".md"):
                    with open(os.path.join(steps_dir, filename)) as f:
                        steps[filename[:-3]] = f.read()
        return steps
    
    def _load_resources(self) -> dict:
        resources = {}
        res_dir = os.path.join(self.skill_dir, "resources")
        if os.path.exists(res_dir):
            for filename in os.listdir(res_dir):
                with open(os.path.join(res_dir, filename)) as f:
                    resources[filename] = f.read()
        return resources
    
    def _load_examples(self) -> list:
        examples = []
        ex_dir = os.path.join(self.skill_dir, "examples")
        if os.path.exists(ex_dir):
            for filename in sorted(os.listdir(ex_dir)):
                if filename.endswith("_input.json"):
                    input_path = os.path.join(ex_dir, filename)
                    output_path = input_path.replace("_input.json", "_output.json")
                    if os.path.exists(output_path):
                        with open(input_path) as f:
                            inp = json.load(f)
                        with open(output_path) as f:
                            out = json.load(f)
                        examples.append({"input": inp, "output": out})
        return examples
    
    def get_summary(self) -> str:
        """第一層:簡短描述"""
        return f"- {self.config.name}: {self.config.description}"
    
    def get_full_prompt(self) -> str:
        """第二層:完整指令"""
        return self.instructions
    
    def get_step_prompt(self, step_name: str) -> str:
        """第三層:步驟詳細說明"""
        return self.steps.get(step_name, "")
    
    def get_examples_prompt(self, max_examples: int = 3) -> str:
        """取得範例"""
        selected = self.examples[:max_examples]
        lines = ["## 範例\n"]
        for i, ex in enumerate(selected, 1):
            lines.append(f"### 範例 {i}")
            lines.append(f"輸入:{json.dumps(ex['input'], ensure_ascii=False)}")
            lines.append(f"輸出:{json.dumps(ex['output'], ensure_ascii=False)}")
            lines.append("")
        return "\n".join(lines)


class SkillRegistry:
    """Skill 註冊表"""
    
    def __init__(self, skills_dir: str = "skills"):
        self.skills_dir = skills_dir
        self.skills = {}
        self._load_all()
    
    def _load_all(self):
        if not os.path.exists(self.skills_dir):
            return
        for name in os.listdir(self.skills_dir):
            skill_dir = os.path.join(self.skills_dir, name)
            if os.path.isdir(skill_dir):
                try:
                    self.skills[name] = Skill(skill_dir)
                except Exception as e:
                    print(f"載入 Skill {name} 失敗:{e}")
    
    def get_skill(self, name: str) -> Skill:
        return self.skills.get(name)
    
    def get_skill_list_prompt(self) -> str:
        """第一層:所有 Skill 的摘要"""
        lines = ["# 可用技能\n"]
        for skill in self.skills.values():
            lines.append(skill.get_summary())
        return "\n".join(lines)
    
    def list_skills(self) -> list:
        return list(self.skills.keys())


# ========== 使用範例 ==========

registry = SkillRegistry("skills")

# 第一層:取得 Skill 清單
skill_list = registry.get_skill_list_prompt()
print(skill_list)

# 第二層:載入特定 Skill 的完整指令
research_skill = registry.get_skill("research_topic")
if research_skill:
    full_prompt = research_skill.get_full_prompt()
    
    # 第三層:載入特定步驟
    step_prompt = research_skill.get_step_prompt("03_filter_sources")
    
    # 載入範例
    examples = research_skill.get_examples_prompt()
    
    # 組裝最終 Prompt
    final_prompt = f"""
{full_prompt}

{step_prompt}

{examples}
"""

九、Skill 設計的常見陷阱

1. 指令太模糊

# 錯誤例子
請研究這個主題並給我一個好的摘要。

# 正確例子
請根據以下步驟研究主題:
1. 生成 3-5 個搜尋關鍵字
2. 搜尋並篩選高品質來源
...

2. 一次做太多事

一個 Skill 不應該同時做研究、寫作、審查。
拆成多個 Skill,再用 Orchestrator 組合。

3. 忽略邊界案例

只考慮正常情況,沒有處理:

  • 找不到資料
  • 工具失敗
  • 輸入格式錯誤
  • 敏感內容

4. 沒有範例

沒有範例的 Skill,LLM 很難知道「好的輸出」長什麼樣子。

5. 沒有版本管理

Skill 會迭代。
沒有版本管理,就無法追蹤變更、無法回滾、無法比較。

6. 權限過大

給 Skill 太多工具,會增加安全風險。
應該遵循最小權限原則。

7. 沒有測試

Skill 的品質需要被驗證。
這就需要 Harness——我們下一個系列要談的主題。

十、總結:Skill 的四大支柱

讓我們回顧這一篇的核心:

  • Skill 的四大組成:指令、工具、資源、範例。
  • 指令:結構化的說明,包含描述、何時使用、輸入輸出、流程、規範、邊界。
  • 漸進式揭露:三層式載入(摘要 → 完整指令 → 步驟說明),降低成本、提升效果。
  • 工具:Skill 的手腳,應該遵循最小權限原則。
  • 資源:背景知識、範本、參考文件,按需載入。
  • 範例:輸入輸出範例、邊界案例、失敗案例,比長篇說明更有效。
  • 檔案結構SKILL.md + steps/ + resources/ + examples/ + config.yaml
  • 常見陷阱:指令模糊、一次做太多、忽略邊界、沒有範例、沒有版本管理、權限過大、沒有測試。

理解了 Skill 的結構,你就能設計出真正好用、可維護、可組合的 Skill。

但設計完之後,下一個問題是:你怎麼知道這個 Skill 真的好用?

這就是我們下一篇要談的主題:設計一個可重用的 Skill
我們會用一個具體的例子(程式碼審查),從頭設計一個完整的 Skill。


下一篇預告

《設計一個可重用的 Skill:以程式碼審查為例》

我們會從需求分析開始,逐步設計一個「程式碼審查」Skill,包含指令、工具、資源、範例,並討論如何讓它可重用、可組合、可測試。