🚀170行代码实现AI Agent Skills系统

0 阅读9分钟

引言: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 字段说明 :

字段类型必填说明
namestring技能唯一标识,kebab-case
descriptionstring关键字段!决定 LLM 何时使用该技能,应包含触发关键词
argument-hintstring参数提示,如 [branch-name]
disable-model-invocationbool是否仅允许用户手动调用
user-invocablebool是否对用户可见
allowed-toolslist限制该技能可调用的工具

二、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 1Agent 启动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 ListDictOptional
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会自动:

  1. 发现 code-reviewer 技能可用
  2. 判断 当前任务需要代码审查
  3. 激活 技能,加载完整的审查规范和最佳实践
  4. 执行 并输出符合你团队标准的结果

四、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)
  • ❌ 极度创造性的开放式任务
  • ❌ 上下文窗口极小的模型

最佳实践建议:

  1. 从真实痛点出发:不要为造技能而造,先观察团队最常重复的AI对话
  2. 保持技能原子化:一个技能解决一类问题,避免"万能技能"
  3. 版本化管理:将SKILL.md纳入Git,随团队规范迭代
  4. 人机协作:技能是"初稿生成器",关键决策仍需人工确认

结语

不要造一个什么都会的Agent,而是造一堆专业精深的Skills,让Agent按需调用。


    

关注公众号【dev派】,回复 "agent" 获取完整源码和配置文件模板!