Skill 的結構:指令、工具、資源與範例
深入拆解 Skill 的內部結構,從 SKILL.md 設計、漸進式揭露到指令與範例的撰寫技巧,理解如何設計好用、可維護、可組合的 Skill。
Skill 的結構:指令、工具、資源與範例
上一篇我們談了工具與技能的差異,理解了 Skill 是「可重用、可組合、有明確輸入輸出的能力單元」。
但一個 Skill 到底長什麼樣子?它的內部由哪些部分組成?
這一篇,我們要深入拆解 Skill 的結構,談談 SKILL.md 的設計、漸進式揭露的實作、指令與範例的撰寫技巧,以及如何讓 LLM 正確理解並使用一個 Skill。
理解這些結構,你才能設計出真正好用、可維護、可組合的 Skill。
一、一個 Skill 由什麼組成?
一個完整的 Skill,通常包含四個部分:
| 組成 | 說明 | 類比 |
|---|---|---|
| 指令(Instructions) | 做什麼、怎麼做、何時用 | 工作手冊 |
| 工具(Tools) | 需要哪些工具來完成任務 | 工具箱 |
| 資源(Resources) | 需要的參考資料、範本、知識 | 參考文件 |
| 範例(Examples) | 正確使用的示例 | 範例題 |
這四者協同運作,讓 LLM 能:
- 知道這個 Skill 的存在與用途(指令)
- 知道要用哪些工具來完成(工具)
- 知道需要什麼背景知識(資源)
- 知道正確的輸出長什麼樣子(範例)
二、指令(Instructions):Skill 的大腦
指令是 Skill 最核心的部分。
它告訴 LLM:這個 Skill 做什麼、何時該用、怎麼做。
指令的結構
一個好的指令通常包含以下區塊:
# Skill: research_topic
## 描述
一句話說明這個 Skill 做什麼。
## 何時使用
列出應該使用這個 Skill 的情境。
## 何時不該使用
列出不應該使用這個 Skill 的情境。
## 輸入
定義輸入參數。
## 輸出
定義輸出格式。
## 執行流程
逐步說明執行步驟。
## 輸出規範
定義輸出品質標準。
## 邊界與限制
說明這個 Skill 的限制。
為什麼要分這麼多區塊?
因為 LLM 需要結構化的指令才能穩定執行。
如果你把所有內容混在一段文字裡,LLM 可能會遺漏某些重要資訊。
分區塊的好處:
- 易於閱讀:人類和 LLM 都能快速找到需要的資訊。
- 易於維護:修改某個區塊不會影響其他區塊。
- 易於漸進式揭露:可以按需載入特定區塊。
範例:一個完整的指令
# 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,包含指令、工具、資源、範例,並討論如何讓它可重用、可組合、可測試。