s07:Skill Loading 技能加载机制详解
本章为 s07。
s07:Skill Loading(技能加载)
两级知识注入:Two-Level Knowledge Injection
痛点:如果把所有技能的详细说明(可能超过 8000 tokens)全部塞进 SYSTEM 提示词,Agent 的上下文窗口会被迅速撑爆,且成本高昂。
方案:将知识分为"轻量级目录"和"重量级内容"两级。
- 第 1 级(始终存在):在
SYSTEM提示词中只注入一个技能目录,仅包含技能名称和一句话描述(约 100 tokens/技能)。让模型知道有哪些技能可用。 - 第 2 级(按需加载):当模型判断需要某个技能时,它调用
load_skill("技能名")工具。系统从本地文件读取完整的SKILL.md内容,并通过tool_result注入到当前对话中。让模型在需要时才获取详细指令。
元数据驱动与注册表模式:Metadata-Driven & Registry Pattern
- 技能被定义为
skills/目录下的子文件夹,每个文件夹内有一个SKILL.md文件。 SKILL.md使用 YAML Frontmatter 声明元数据(如名称、描述),让技能本身是自描述的。- 程序启动时,
_scan_skills()遍历skills/目录,解析所有SKILL.md,构建一个包含所有技能元数据和完整内容的SKILL_REGISTRY(注册表)。这是一个内存中的中央知识库。
动态的系统提示词:Dynamic System Prompt
SYSTEM 提示词不再是固定的字符串,而是由 build_system() 函数动态生成。它在启动时扫描技能目录,将最新的技能列表注入其中。这意味着只需在文件系统中增删技能文件夹,无需修改任何代码。
技能文件解析与注册表构建
import yaml
SKILL_REGISTRY: dict[str, dict] = {}
# 标准的 frontmatter 解析器,输入 skill.md 文件的全部文本,返回 dict 类型的 meta 元数据和 str 类型的正文
def _parse_frontmatter(text: str) -> tuple[dict, str]:
"""解析 SKILL.md 的 YAML Frontmatter。返回 (元数据, 正文)。"""
if not text.startswith("---"):
return {}, text
# 分隔符为 ---,最多进行两次分割
parts = text.split("---", 2)
if len(parts) < 3:
return {}, text
try:
meta = yaml.safe_load(parts[1]) or {}
except yaml.YAMLError:
meta = {}
return meta, parts[2].strip()
# 注册表构建:遍历 `SKILLS_DIR` 下的每个文件夹,查找 `SKILL.md` 文件。找到后,读取内容并解析,
# 最终构建一个 SKILL_REGISTRY 字典。键是技能名,值是包含 name、description、content(完整原始内容)的字典。
# 这个注册表就是 Agent 的"知识库"索引。
def _scan_skills():
"""扫描 skills/ 目录,填充 SKILL_REGISTRY。"""
if not SKILLS_DIR.exists():
return
for d in sorted(SKILLS_DIR.iterdir()):
if not d.is_dir():
continue
manifest = d / "SKILL.md"
if manifest.exists():
raw = manifest.read_text()
meta, body = _parse_frontmatter(raw)
name = meta.get("name", d.name)
desc = meta.get("description", raw.split("\n")[0].lstrip("#").strip())
SKILL_REGISTRY[name] = {"name": name, "description": desc, "content": raw}
_scan_skills()
动态构建系统提示词
# 遍历全局的 SKILL_REGISTRY,生成人类可读、模型也易理解的 Markdown 格式技能清单。
def list_skills() -> str:
"""列出所有技能(名称 + 一行描述)。"""
if not SKILL_REGISTRY:
return "(no skills found)"
return "\n".join(f"- **{s['name']}**: {s['description']}" for s in SKILL_REGISTRY.values())
# 该函数在模块加载时被调用一次
def build_system() -> str:
"""构建包含技能目录的 SYSTEM 提示词。"""
catalog = list_skills()
return (
f"You are a coding agent at {WORKDIR}. "
f"Skills available:\n{catalog}\n"
# 需要 load_skill 加载详细指令
"Use load_skill to get full details when needed."
)
SYSTEM = build_system()
按需加载技能的工具函数
def load_skill(name: str) -> str:
"""按名称加载完整技能内容。通过注册表查找,无路径遍历风险。"""
skill = SKILL_REGISTRY.get(name)
if not skill:
return f"Skill not found: {name}"
return skill["content"]
技能工具集成到 Agent 循环
向 TOOLS 列表中添加一个新的工具定义,告诉 API 这个工具存在,作用是"通过名称加载技能的完整内容",且需要一个 name 参数。
# 在工具定义列表 TOOLS 中新增
TOOLS = [
# ... 其他工具 ...
{
"name": "load_skill",
"description": "Load the full content of a skill by name.",
"input_schema": {
"type": "object",
"properties": {"name": {"type": "string"}},
"required": ["name"]
}
},
]
# 在工具处理函数映射 TOOL_HANDLERS 中注册
TOOL_HANDLERS = {
# ... 其他处理器 ...
"load_skill": load_skill,
}
工作原理(完整时序)
ps:子代理没有 skill 技能,也没有
task工具。
从社区获取 skill 或自己创建 skill,SKILL.md 是核心文件,里面是技能的完整说明。完整流程:
- 启动时:扫描
skills/并构建技能注册表SKILL_REGISTRY。 - 构建 SYSTEM 提示词:第一级注入——把轻量技能目录写进
SYSTEM(如build_system()生成的清单)。 - 用户请求(如"帮我做代码审查"):用户的问题进入主对话。
- Agent 循环开始:模型看到
SYSTEM中的技能目录,知道有code-review这个技能可用。 - 模型决定加载技能:第二级注入——模型调用
load_skill("code-review")。 - 系统执行
load_skill:从注册表取出完整SKILL.md内容,通过tool_result返回给模型。 - 下一轮:模型现在拥有了完整的代码审查知识,会按照
SKILL.md中定义的检查清单和流程来工作。
技能加载后的两种执行方式
技能内容返回后,模型有两种使用方式:
| 方式 | 说明 | 适用场景 |
|---|---|---|
| ① 作为指令注入 | 技能内容本身是指令,模型加载后在后续对话中遵循这些指令行动 | 大多数 skill(如 code-review 检查清单) |
| ② 作为可执行代码 | 通过 exec() 动态加载函数,把函数注册为额外工具,扩展 Agent 能力 |
需要真正运行逻辑的 skill(稍复杂) |
① 作为指令注入
skill_content = SKILL_REGISTRY["code-review"]["content"]
# 这个完整的 SKILL.md 被注入到对话中
history.append({"role": "user", "content": [{
"type": "tool_result",
"tool_use_id": "...",
"content": skill_content # 完整的审查指南
}]})
② 作为可执行的代码(稍微复杂一点)
通过 exec() 动态加载函数,这些函数作为额外的工具注册到 Agent 工具池中……
好的 Skill 设计核心
- 明确元数据(YAML Frontmatter)
---
name: skill-name
description: One-line summary for the catalog
---
- 清晰工作流
## Process
1. Step one
2. Step two
- 具体输出格式
## Output Format
[明确告诉模型应该输出什么格式]
- 示例和边界条件
## Examples
## Edge Cases
## What NOT to do
本模块知识点
- 两级知识注入:SYSTEM 只放轻量技能目录(~100 tokens/技能),
load_skill按需注入完整SKILL.md。 - 注册表模式:
_scan_skills()扫描skills/构建内存SKILL_REGISTRY(元数据 + 完整内容)。 - 动态 SYSTEM:
build_system()启动时扫描技能目录生成提示词,增删技能文件夹无需改代码。 SKILL.md用 YAML Frontmatter 自描述;ps:子代理无 skill、无 task。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 茯茶养生人的博客!









