引言:AI的"Skills焦虑"
你有没有发现,现在的AI就像一个博览群书但从未实习过的大学生——它懂很多道理,但真让它干活时,却总是差点意思。
写代码?它能给你讲设计模式,但看不懂你公司的祖传代码规范。 做审查?它知道PEP8,但不知道你团队为什么一定要"黑色格式化"。 搭API?它懂RESTful,但不理解你们为什么坚持某些"反直觉"的约定。
传统的解决方案是Prompt工程,但几千字的Prompt既费Token又难维护。微调?成本高昂且难以应对多变场景。
最近,一种名为 "Skills(技能)" 的新范式正在兴起。Claude、OpenAI、微软、腾讯纷纷跟进。而今天要介绍的 nanoSkills 项目,用仅170行代码就实现了这一标准的核心机制,堪称最简化的生产级实现。
一、什么是Skills?为什么比Prompt更高级?
想象你在教一个实习生:
- Prompt工程 = 每次任务都重新口述一遍操作手册
- RAG(检索增强) = 给他一堆文档,让他自己翻
- Skills = 直接给他一本结构化的工作手册,需要时翻对应章节
Skills的核心思想是**"渐进式披露"**——只在需要时展示所需内容。
以nanoSkills为例,它设计了三级披露机制:
| 级别 | 内容 | Token消耗 | 触发时机 |
|---|---|---|---|
| Level 1: 发现 | 扫描所有SKILL.md文件 | 极小 | 启动时 |
| Level 2: 列表 | 显示技能名称+描述(~50 tokens/技能) | 低 | 规划阶段 |
| Level 3: 激活 | 加载完整指令(~500-2000 tokens) | 按需 | 执行时 |
这种设计的精妙之处在于:AI不会一次性吞下所有知识,而是像人类专家一样,先判断需要哪个技能,再深入细节。
SKILL.md 文件格式规范
每个技能仅需一个 Markdown 文件,采用 YAML Frontmatter + Markdown 正文 的标准格式:
---
name: code-reviewer
description: 审查代码质量和最佳实践
---
# 代码审查指南
## 审查重点
1. 代码风格和一致性
2. 潜在的 bug 和边界情况
3. 性能优化机会
4. 安全漏洞检查
## 输出格式
- 🔴 严重问题
- 🟡 建议改进
- 🟢 良好实践
## 审查流程
1. 首先检查代码是否符合 PEP8 规范
2. 识别未处理的异常和边界情况
3. 检查是否有 SQL 注入等安全风险
4. 最后给出重构建议
YAML Frontmatter 字段说明 :
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 是 | 技能唯一标识,kebab-case |
description | string | 是 | 关键字段!决定 LLM 何时使用该技能,应包含触发关键词 |
argument-hint | string | 否 | 参数提示,如 [branch-name] |
disable-model-invocation | bool | 否 | 是否仅允许用户手动调用 |
user-invocable | bool | 否 | 是否对用户可见 |
allowed-tools | list | 否 | 限制该技能可调用的工具 |
二、nanoSkills:核心代码架构解析
2.1 三级渐进式披露机制
这是 nanoSkills 最核心的逻辑,代码通过三个层级管理上下文加载:
# 伪代码示意
def discover_skills(skills_dir: str) -> List[Skill]:
"""
Level 1: 发现阶段 - 扫描所有 SKILL.md 文件
仅提取 YAML frontmatter(name + description)
每个技能约 50 tokens
"""
skills = []
for skill_path in Path(skills_dir).glob("*/SKILL.md"):
metadata = parse_frontmatter(skill_path) # 只读前 10 行
skills.append(Skill(
name=metadata['name'],
description=metadata['description'],
full_path=skill_path
))
return skills
def build_activate_tool(skills: List[Skill]) -> Tool:
"""
Level 2: 构建激活工具
将技能列表转换为 LLM 可调用的工具描述
"""
return Tool(
name="activate_skill",
description="根据任务需求激活特定技能",
parameters={
"skill_name": {
"enum": [s.name for s in skills],
"description": "要激活的技能名称"
}
}
)
def activate_skill(skill_name: str, skills: List[Skill]) -> str:
"""
Level 3: 激活阶段 - 按需加载完整指令
仅在 LLM 决定调用时才读取完整 SKILL.md 内容(500-2000 tokens)
"""
skill = find_skill_by_name(skill_name, skills)
full_content = skill.full_path.read_text() # 完整读取
return extract_instructions(full_content) # 返回正文部分
三级披露的数据对比 :
| 层级 | 加载时机 | 内容 | Token 消耗 | 作用 |
|---|---|---|---|---|
| Level 1 | Agent 启动 | YAML Frontmatter (name, description) | ~50 tokens/技能 | 让 LLM 知道"我会什么" |
| Level 2 | 任务匹配 | 技能名称列表 | ~100 tokens | 路由决策 |
| Level 3 | 调用时 | 完整 SKILL.md 正文 + 脚本 | 500-2000 tokens | 执行具体任务 |
三、主程序代码详解 (agent.py)
基于项目描述,主程序 agent.py 的核心逻辑可分为以下几个模块:
3.1 技能发现模块
import os
import yaml
from pathlib import Path
from typing import List, Dict, Optional
from dataclasses import dataclass
@dataclass
class Skill:
"""技能元数据容器"""
name: str
description: str
full_path: Path
instructions: Optional[str] = None# 延迟加载
def parse_frontmatter(content: str) -> Dict:
"""解析 YAML Frontmatter"""
if not content.startswith('---'):
return {}
parts = content.split('---', 2)
if len(parts) < 3:
return {}
try:
return yaml.safe_load(parts[1]) or {}
except yaml.YAMLError:
return {}
def discover_skills(skills_dir: str) -> List[Skill]:
"""
扫描技能目录,执行 Level 1 披露
仅读取 Frontmatter,不加载完整内容
"""
skills = []
skills_path = Path(skills_dir)
if not skills_path.exists():
raise FileNotFoundError(f"Skills directory not found: {skills_dir}")
for skill_dir in skills_path.iterdir():
if not skill_dir.is_dir():
continue
skill_file = skill_dir / "SKILL.md"
if not skill_file.exists():
continue
# 关键:只读取前 500 字节,确保只拿到 Frontmatter
content = skill_file.read_text(encoding='utf-8')
metadata = parse_frontmatter(content)
if 'name' in metadata and 'description' in metadata:
skills.append(Skill(
name=metadata['name'],
description=metadata['description'],
full_path=skill_file
))
return skills
3.2 工具构建与激活逻辑
from openai import OpenAI
import json
def build_activate_tool(skills: List[Skill]) -> Dict:
"""
构建激活技能的工具描述(OpenAI Function Calling 格式)
这是 Level 2 披露的关键
"""
return {
"type": "function",
"function": {
"name": "activate_skill",
"description": "激活特定技能以获取详细指令。当任务涉及以下领域时调用:"
+ ", ".join([f"{s.name}({s.description})" for s in skills]),
"parameters": {
"type": "object",
"properties": {
"skill_name": {
"type": "string",
"enum": [s.name for s in skills],
"description": "要激活的技能名称"
},
"reason": {
"type": "string",
"description": "为什么需要这个技能"
}
},
"required": ["skill_name", "reason"]
}
}
}
def activate_skill(skill_name: str, skills: List[Skill]) -> str:
"""
Level 3 披露:实际加载完整技能内容
仅在 LLM 调用此函数时执行
"""
skill = next((s for s in skills if s.name == skill_name), None)
if not skill:
return f"Error: Skill '{skill_name}' not found"
# 现在才读取完整文件内容
content = skill.full_path.read_text(encoding='utf-8')
# 提取正文(去掉 Frontmatter)
parts = content.split('---', 2)
if len(parts) >= 3:
instructions = parts[2].strip()
else:
instructions = content
# 缓存避免重复读取
skill.instructions = instructions
return f"[Skill '{skill_name}' activated]\n\n{instructions}"
3.3 主 Agent 循环
class NanoAgent:
def __init__(self, skills_dir: str = "./skills-real"):
self.client = OpenAI()
self.skills = discover_skills(skills_dir)
self.active_skill: Optional[Skill] = None
self.conversation_history = []
def run(self, user_input: str):
# 构建系统提示(仅包含技能元数据 - Level 1)
system_prompt = self._build_system_prompt()
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_input}
]
# 构建可用工具列表
tools = [build_activate_tool(self.skills)]
while True:
response = self.client.chat.completions.create(
model=os.getenv('OPENAI_MODEL', 'gpt-4o-mini'),
messages=messages,
tools=tools,
tool_choice="auto"
)
message = response.choices[0].message
# 处理工具调用
if message.tool_calls:
messages.append(message)
for tool_call in message.tool_calls:
if tool_call.function.name == "activate_skill":
args = json.loads(tool_call.function.arguments)
result = activate_skill(args['skill_name'], self.skills)
# 将技能指令加入上下文(Level 3 披露)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
# 标记当前激活的技能
self.active_skill = next(
s for s in self.skills if s.name == args['skill_name']
)
else:
# 普通回复,任务完成
return message.content
def _build_system_prompt(self) -> str:
"""构建包含技能元数据的系统提示"""
skills_info = "\n".join([
f"- {s.name}: {s.description}"
for s in self.skills
])
return f"""你是一个专业 AI 助手,拥有以下技能可用:
可用技能:
{skills_info}
工作方式:
1. 分析用户请求,判断需要使用哪个技能
2. 如果任务需要特定领域知识,调用 activate_skill 工具激活对应技能
3. 激活后你将获得详细指令,按照指令执行任务
记住:只有当任务明确需要某个技能时才激活,避免不必要的工具调用。"""
# CLI 入口
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description='nanoSkills Agent')
parser.add_argument('query', help='用户查询')
parser.add_argument('--skills-dir', default='./skills-real',
help='技能目录路径')
args = parser.parse_args()
agent = NanoAgent(args.skills_dir)
result = agent.run(args.query)
print(result)
三、实战:一分钟创建一个自定义技能
nanoSkills的技能定义极其简单,只需一个Markdown文件:
mkdir -p skills-real/my-skill
cat > skills-real/my-skill/SKILL.md << 'EOF'
---
name: my-skill
description: 简短描述供AI使用(这是给AI看的"技能标签")
---
这里是完整指令内容。
你可以写:
- 详细的操作步骤
- 代码规范要求
- 审查检查清单
- 输出格式模板
EOF
执行时:
python agent.py "你的任务描述"
比如使用内置的代码审查技能:
python agent.py "审查这个Python函数的改进点"
AI会自动:
- 发现
code-reviewer技能可用 - 判断 当前任务需要代码审查
- 激活 技能,加载完整的审查规范和最佳实践
- 执行 并输出符合你团队标准的结果
四、Skills vs MCP
MCP的问题:前置Schema膨胀
MCP需要在对话开始时就加载所有工具的Schema定义,这通常消耗 10k-25k tokens。对于复杂系统,这还没开始干活,上下文就已经被占满。
Skills的优势:按需加载
Skills采用"渐进式披露",初始只加载技能列表(名称+描述),每个技能约50 tokens。只有当AI决定使用某个技能时,才加载完整的2000 tokens指令。
用个比喻:
- MCP = 出差前背下整本《员工手册》
- Skills = 带个目录,需要时再查具体章节
当然,MCP在工具标准化方面有优势,但对于"知识密集型"而非"工具密集型"的任务,Skills明显更高效。
五、内置技能库解析
nanoSkills提供了三个真实生产级技能,极具参考价值:
1. git-expert(Git工作流专家)
- 规范提交信息格式(Conventional Commits)
- 分支管理策略
- 代码合并最佳实践
2. code-reviewer(代码审查专家)
- Python最佳实践(PEP8、类型提示、错误处理)
- 性能优化建议
- 安全漏洞检查
3. api-designer(API设计专家)
- RESTful规范
- 版本控制策略
- 错误码设计标准
这些技能文件都是纯Markdown,你可以直接复制修改,打造自己团队的专属技能库。
六、适用场景与最佳实践
适合用Skills的场景:
- ✅ 有明确规范的重复性工作(代码审查、文档生成)
- ✅ 需要领域专业知识的任务(安全审计、合规检查)
- ✅ 团队协作需要一致性输出的场景
不适合的场景:
- ❌ 需要实时数据查询(应该用MCP+API)
- ❌ 极度创造性的开放式任务
- ❌ 上下文窗口极小的模型
最佳实践建议:
- 从真实痛点出发:不要为造技能而造,先观察团队最常重复的AI对话
- 保持技能原子化:一个技能解决一类问题,避免"万能技能"
- 版本化管理:将SKILL.md纳入Git,随团队规范迭代
- 人机协作:技能是"初稿生成器",关键决策仍需人工确认
结语
不要造一个什么都会的Agent,而是造一堆专业精深的Skills,让Agent按需调用。
关注公众号【dev派】,回复 "agent" 获取完整源码和配置文件模板!