动机:为什么需要 Skills
到目前为止,agent 的所有行为都由 system prompt 定义。但不可能把所有知识都塞进一个 prompt 里——那样会超出 context window,而且大部分信息在大部分时间都用不上。
Skill 的思路是按需加载:
- 定义一组独立的技能文件(SKILL.md),每个描述一种专业能力
- System prompt 里只放技能目录(名称和简要描述)
- 当 agent 遇到需要某个技能的任务时,主动用
read_file加载对应的 SKILL.md
Anthropic 把这叫做 Routing pattern:根据输入分类,走不同的处理路径。
Ground Truth:真实的 Skills 系统
Skills & Routing——跨代码库对比
技能加载和输入路由在不同代码库中的实现
1class SkillsLoader:2 def __init__(self, workspace: Path):3 self.workspace_skills = workspace / "skills"4 self.builtin_skills = BUILTIN_SKILLS_DIR56 def list_skills(self, filter_unavailable=True):7 skills = []8 # 工作区技能 (最高优先级)9 if self.workspace_skills.exists():10 for skill_dir in self.workspace_skills.iterdir():11 skill_file = skill_dir / "SKILL.md"12 if skill_file.exists():13 skills.append({14 "name": skill_dir.name,15 "path": str(skill_file),16 "source": "workspace",17 })18 # 内置技能 (不覆盖同名工作区技能)19 if self.builtin_skills.exists():20 for skill_dir in self.builtin_skills.iterdir():21 ...22 return skills2324 def build_skills_summary(self) -> str:25 """生成技能目录, 注入 system prompt"""26 all_skills = self.list_skills(filter_unavailable=False)27 lines = ["<skills>"]28 for s in all_skills:29 available = self._check_requirements(s)30 lines.append(f' <skill available="{available}">')31 lines.append(f' <name>{s["name"]}</name>')32 lines.append(f' <description>...</description>')33 lines.append(f' <location>{s["path"]}</location>')34 lines.append(f' </skill>')35 lines.append("</skills>")36 return "\n".join(lines)3738 def get_always_skills(self) -> list[str]:39 """获取标记为 always=true 的技能"""40 return [s["name"] for s in self.list_skills()41 if self._get_skill_meta(s["name"]).get("always")]
1# Routing Pattern2# 来源: Anthropic Building Effective Agents34# 根据输入分类, 走不同处理路径5# 这是 5 种 workflow pattern 之一67def route(user_input: str) -> str:8 """根据输入类型选择处理路径"""9 classification = llm.classify(user_input)1011 if classification == "code_review":12 return code_review_agent(user_input)13 elif classification == "debugging":14 return debug_agent(user_input)15 elif classification == "documentation":16 return docs_agent(user_input)17 else:18 return general_agent(user_input)1920# 5 种 pattern 映射:21# Prompt Chaining -> s03 (TodoWrite)22# Routing -> s11 (Skill Loading)23# Parallelization -> s10 (Sub-agents)24# Orchestrator-Worker -> s10 (Sub-agents)25# Evaluator-Optimizer -> s09 (Plan Mode)
关键观察:
- nanobot 的 Skills 系统有两层:工作区技能(用户自定义)和内置技能(agent 自带)
- 技能目录以 XML 格式注入 system prompt——agent 知道有哪些技能可用
- 每个技能可以声明依赖(
requires.bins、requires.env),不满足条件的标记为available=false always=true的技能在每次会话都自动加载,其余按需加载
构建:SkillsLoader
class SkillsLoader:
"""按需加载 agent 技能"""
def __init__(self, workspace: Path):
self.skills_dir = workspace / "skills"
def list_skills(self) -> list[dict]:
"""列出所有可用技能"""
skills = []
if not self.skills_dir.exists():
return skills
for skill_dir in self.skills_dir.iterdir():
if skill_dir.is_dir():
skill_file = skill_dir / "SKILL.md"
if skill_file.exists():
meta = self._parse_frontmatter(skill_file)
skills.append({
"name": skill_dir.name,
"description": meta.get("description", skill_dir.name),
"path": str(skill_file),
})
return skills
def load_skill(self, name: str) -> str | None:
"""加载指定技能的内容"""
skill_file = self.skills_dir / name / "SKILL.md"
if skill_file.exists():
return skill_file.read_text(encoding="utf-8")
return None
def build_summary_for_prompt(self) -> str:
"""生成技能摘要, 注入 system prompt"""
skills = self.list_skills()
if not skills:
return ""
lines = ["可用技能 (用 read_file 加载详情):"]
for s in skills:
lines.append(f"- {s['name']}: {s['description']} ({s['path']})")
return "\n".join(lines)
技能文件的格式(skills/code-review/SKILL.md):
---
description: "代码审查——检查代码质量、安全性和最佳实践"
---
# Code Review 技能
## 审查流程
1. 读取需要审查的文件
2. 检查以下维度: ...
3. 输出结构化的审查报告
变更内容
| 组件 | 之前 (s10) | 之后 (s11) |
|---|---|---|
| 能力来源 | 固定 system prompt | SkillsLoader 动态加载 |
| 技能文件 | 无 | SKILL.md 按需加载 |
本课代码: agents/s11_skills_routing.py — 369 行 (新增 25 行)
试一试
cd public/code
mkdir -p skills/code-review && echo "# Code Review skill\n\n## 审查流程\n1. 检查代码风格\n2. 检查安全漏洞\n3. 输出报告" > skills/code-review/SKILL.md
python agents/s11_skills_routing.py "Review my code"
可以尝试的提示:
- 创建
skills/code-review/SKILL.md后让 agent 做代码审查 - 不创建任何 skill,观察 agent 的 system prompt 有何不同
- 创建多个 skill 目录,看 agent 如何选择
距离生产
依赖检查与 always-active 技能
nanobot 的 SkillsLoader 不只是列出技能——它还检查每个技能的依赖条件。技能可以在 frontmatter 里声明 requires.bins(需要哪些命令行工具)和 requires.env(需要哪些环境变量)。不满足条件的技能标记为 available=false,agent 知道它存在但无法使用。此外,标记 always=true 的技能会在每次会话自动加载到 system prompt,不需要 agent 主动读取——这用于那些”总是需要”的基础技能。
其他差距
- 无技能版本管理——生产支持技能的版本迭代和 A/B 测试
- 无显式分类器——我们完全靠模型自己判断用哪个技能,生产可能有预分类步骤
第一性原理思考
Skills 本质上是”专家系统”的轻量版——把领域知识编码为 prompt 片段。但与传统专家系统不同,这里的”路由”是模型自己做的,不是硬编码的 if/else 规则。这既是优势(灵活,能处理模糊的任务描述)也是风险(模型可能选错 skill,或者在该用 skill 时没有用)。nanobot 的 always=true 机制是对这个风险的一种缓解——关键技能不依赖模型的判断,强制加载。这暗示了一个设计权衡:越重要的知识,越不应该依赖模型的”自觉”去加载。