Phase 4 / Scale & Orchestration / s11

Skills & Routing

技能加载与输入分类——让 agent 动态获取专业能力

动机:为什么需要 Skills

到目前为止,agent 的所有行为都由 system prompt 定义。但不可能把所有知识都塞进一个 prompt 里——那样会超出 context window,而且大部分信息在大部分时间都用不上。

Skill 的思路是按需加载

Anthropic 把这叫做 Routing pattern:根据输入分类,走不同的处理路径。

Ground Truth:真实的 Skills 系统

Skills & Routing——跨代码库对比

技能加载和输入路由在不同代码库中的实现

pythonagent/skills.py
1class SkillsLoader:
2 def __init__(self, workspace: Path):
3 self.workspace_skills = workspace / "skills"
4 self.builtin_skills = BUILTIN_SKILLS_DIR
5
6 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 skills
23
24 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)
37
38 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")]
pythonBuilding Effective Agents (Routing pattern)
1# Routing Pattern
2# 来源: Anthropic Building Effective Agents
3
4# 根据输入分类, 走不同处理路径
5# 这是 5 种 workflow pattern 之一
6
7def route(user_input: str) -> str:
8 """根据输入类型选择处理路径"""
9 classification = llm.classify(user_input)
10
11 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)
19
20# 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)

关键观察:

构建: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 promptSkillsLoader 动态加载
技能文件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"

可以尝试的提示:

  1. 创建 skills/code-review/SKILL.md 后让 agent 做代码审查
  2. 不创建任何 skill,观察 agent 的 system prompt 有何不同
  3. 创建多个 skill 目录,看 agent 如何选择

距离生产

依赖检查与 always-active 技能

nanobot 的 SkillsLoader 不只是列出技能——它还检查每个技能的依赖条件。技能可以在 frontmatter 里声明 requires.bins(需要哪些命令行工具)和 requires.env(需要哪些环境变量)。不满足条件的技能标记为 available=false,agent 知道它存在但无法使用。此外,标记 always=true 的技能会在每次会话自动加载到 system prompt,不需要 agent 主动读取——这用于那些”总是需要”的基础技能。

其他差距

第一性原理思考

Skills 本质上是”专家系统”的轻量版——把领域知识编码为 prompt 片段。但与传统专家系统不同,这里的”路由”是模型自己做的,不是硬编码的 if/else 规则。这既是优势(灵活,能处理模糊的任务描述)也是风险(模型可能选错 skill,或者在该用 skill 时没有用)。nanobot 的 always=true 机制是对这个风险的一种缓解——关键技能不依赖模型的判断,强制加载。这暗示了一个设计权衡:越重要的知识,越不应该依赖模型的”自觉”去加载。