手把手实现 Agent Skills 机制:让 AI Agent 真正复用工程经验

📖 摘要:2026 年 GitHub Trending 上 agent-skills 类仓库合计近 30 万 star,Skills 已成为 AI Agent 工程化的关键一层。但多数文章只做"仓库推荐",没人讲清它到底是什么、怎么自己实现。本文从工程角度拆解 Skill 的定义、两阶段加载机制与路由原理,并手撸一个可运行的 Python Skills 加载器,帮你把零散的"工程经验"沉淀成 Agent 可复用的能力模块。读完你将拥有一个即插即用的 Skills 框架雏形。

🏷️ 关键词:AI Agent,Agent Skills,大模型工程化,技能加载,Prompt 工程

目录

一、背景与痛点

1.1 Agent 的"失忆"困局

我们给 AI Agent 接上工具、配上模型,跑通第一个 Demo 时都很兴奋。但一旦进入真实业务,立刻撞上一堵墙:经验无法沉淀

举个例子:你在 user_service 项目里花了一下午,摸清了"如何用内部 SDK 拉取用户信息并做权限校验"。第二天新开一个会话,Agent 又从零开始翻文档、试错、踩你昨天刚踩过的坑。每一次新对话,都是一次"失忆重启"。

传统做法是把这些知识写进 System Prompt,或者塞进一份超长的 CLAUDE.md / AGENTS.md。但提示词越长,token 越贵、上下文越容易互相干扰,模型反而越容易"迷路"。我们需要一种"按需调用、即插即用"的能力模块机制——这就是 Skills 要解决的问题。

1.2 Skills 为什么突然火了

2026 年 8 月 GitHub Trending 周榜前 15 名里,至少 9 个直接涉及 Agent / Skills / 记忆 / 上下文。其中两个 Skills 仓库格外亮眼:

  • mattpocock/skills:工程师实战技能集合,208k+ star;
  • addyosmani/agent-skills:生产级 AI 编码技能库,83.8k+ star,覆盖 Code Review、测试、部署等标准化 SOP。

开源社区的注意力,正在从"单体大模型"转向"Agent 工程系统"。Skills 就是这套系统里的能力模块层——它让 Agent 像人一样,把"我会做什么、怎么做"固化成可复用、可分享的文件。

1.3 Skills 不是什么(常见混淆)

讲实现前,必须先划清边界,否则很容易和下面几个概念搞混:

概念它解决什么和 Skills 的区别
Prompt一次性指令写死在对话里,不复用、不发现
Tool / Function Call让模型调一个具体函数(如查天气)Tool 是"动作接口",Skill 是"动作背后的方法论+流程"
Plugin平台级功能扩展通常绑定特定产品,Skill 是普通 Markdown 文件,跨框架
MCP Server标准化的工具连接协议MCP 解决"连什么",Skill 解决"连上之后怎么干"

一句话:Tool 是手,Skill 是脑中的"操作手册"。 Skill 可以调用 Tool,但本身是一段结构化的经验指令。

二、核心原理

2.1 关键概念:一个 Skill 的最小结构

一个 Skill 本质上就是一个带元数据的 Markdown 文件,约定俗成叫 SKILL.md。最小结构长这样:

---
name: user-permission-check
description: 当用户要求校验某账号权限、判断角色、或拉取 user_service 用户信息时使用。
---

# 用户权限校验技能

## 步骤
1. 调用 user_service 的 GET /internal/v1/profile 拿到原始档案。
2. 用 permission-sdk 的 evaluate(uid, resource) 做策略判定。
3. 返回 { role, allowed, reason },allowed=false 时必须带 reason。

## 注意
- uid 缺失直接报错,不要猜测默认值。
- 示例数据,仅作演示,实际接口以内部文档为准。

关键是顶部那段 YAML frontmatter:

  • name:技能唯一标识,路由时用它定位。
  • description:这是最重要的字段——它决定 Agent 在无数技能里能不能"想得起"这个技能。它必须是"触发场景"描述,而不是功能罗列。

正文是给 Agent 看的操作手册,可以放步骤、代码、反例、注意事项。

2.2 工作机制:两阶段加载

Skills 能省 token,靠的是一个核心设计:两阶段加载(two-phase loading)

  • 阶段一(元数据扫描):Agent 启动时,只读取每个 SKILL.mdname + description,汇总成一份极短的"技能清单"注入系统提示。这一段通常只有几十到几百字。
  • 阶段二(按需加载):只有当 LLM 判断当前任务需要某个技能时,才去读取该 SKILL.md 的完整正文,塞进上下文。

💡 为什么不能直接全加载?一个有 30 个技能的库,全文可能上万字。全塞进上下文既烧钱又干扰推理。两阶段加载让"知道有哪些能力"和"展开具体能力"解耦。

2.3 Agent 如何"发现→加载→执行"

整个闭环可以用一张流程串起来:

启动 ──► 扫描 skills/ 目录 ──► 注入 [name + description] 清单
                                        │
用户任务 ──► LLM 判断该用哪个 Skill ──► 命中 name
                                        │
                              ──► 读取该 SKILL.md 全文 ──► 按手册执行 ──► 返回结果

"LLM 判断该用哪个 Skill"这一步叫路由(routing)。生产环境里通常用 LLM 做语义匹配,但也必须保留一个确定性的兜底(比如关键词匹配),防止模型抽风调错技能。

三、实战落地

3.1 环境准备

我们只需要 Python 3.10+ 和一个 YAML 解析库。代码保证可运行:

# 创建项目
mkdir -p my-agent/skills && cd my-agent
python -m venv .venv && source .venv/bin/activate   # Windows 用 .venv\Scripts\activate
pip install pyyaml

目录约定:

my-agent/
├── skill_loader.py      # 我们马上写的加载器
└── skills/
    └── user-permission-check/
        └── SKILL.md     # 一个技能

3.2 定义 SKILL.md 规范

skills/user-permission-check/SKILL.md 写入 2.1 节的示例内容(已给出)。再建一个 skills/order-refund/SKILL.md 作路由对照:

---
name: order-refund
description: 当用户要处理退款、冲销、或查询 order_service 某笔订单的退款状态时使用。
---

# 订单退款技能
1. 先查 order_service GET /orders/{id} 确认状态。
2. 状态为 SHIPPED 不允许自动退款,转人工。
3. 调用 refund-sdk.apply(order_id, reason) 发起冲销。

3.3 实现 Skill 加载器(Python)

3.3.1 扫描元数据阶段
# skill_loader.py
import os
import yaml
from dataclasses import dataclass
from typing import Optional

SKILL_DIR = "./skills"


@dataclass
class Skill:
    name: str
    description: str
    path: str
    body: str = ""  # 阶段二才填充

    @classmethod
    def from_file(cls, path: str) -> "Skill":
        text = open(path, "r", encoding="utf-8").read()
        fm, body = _split_frontmatter(text)
        meta = yaml.safe_load(fm) if fm.strip() else {}
        return cls(
            name=meta.get("name", os.path.basename(os.path.dirname(path))),
            description=meta.get("description", ""),
            path=path,
            body=body.strip(),
        )


def _split_frontmatter(text: str):
    """只认 '---' 包裹的 YAML 头,避免误切正文里的分隔线。"""
    if text.startswith("---"):
        parts = text.split("---", 2)
        if len(parts) == 3:
            return parts[1], parts[2]
    return "", text


class SkillRegistry:
    """两阶段加载:先扫元数据,按需再读正文。"""

    def __init__(self, skill_dir: str = SKILL_DIR):
        self.skill_dir = skill_dir
        self._index: dict[str, Skill] = {}

    def scan(self) -> "SkillRegistry":
        """阶段一:只把 name + description 装进索引(极省 token)。"""
        self._index.clear()
        for root, _, files in os.walk(self.skill_dir):
            if "SKILL.md" in files:
                skill = Skill.from_file(os.path.join(root, "SKILL.md"))
                self._index[skill.name] = skill
        return self

    def describe_all(self) -> str:
        """生成注入系统提示的精简清单。"""
        lines = ["可用技能(Skills):"]
        for s in self._index.values():
            lines.append(f"- {s.name}: {s.description}")
        return "\n".join(lines)

    def load(self, name: str) -> Optional[Skill]:
        """阶段二:按需加载完整正文。"""
        return self._index.get(name)

    def __len__(self) -> int:
        return len(self._index)

3.3.2 按需加载正文阶段

注意 scan() 只读了 frontmatter,body 此时是空的。load(name) 返回的是同一个对象,但由于 from_file 已经把 body 填好了,这里其实阶段一就顺带拿到了正文——这没问题,因为正文不会describe_all() 的清单,仍只在被路由命中后才进入上下文。如果你想严格"阶段二才读磁盘",可以把 body 留空、load() 里再 open 一次即可,逻辑等价。

3.4 实现 Skill 路由器

路由负责"用户说一句话,该调哪个 Skill"。生产用 LLM 语义匹配,这里先给一个确定性兜底 + LLM 钩子的实现:

import re


def route_deterministic(task: str, registry: SkillRegistry) -> Optional[str]:
    """兜底路由:用 description 里的关键词做简单命中,防止模型抽风。"""
    task_lower = task.lower()
    for name, skill in registry._index.items():
        # 取 description 里的名词片段做弱匹配(示例逻辑,仅作演示)
        if any(k in task_lower for k in ["退款", "冲销", "订单"]):
            if name == "order-refund":
                return name
        if any(k in task_lower for k in ["权限", "角色", "用户"]):
            if name == "user-permission-check":
                return name
    return None


def route_by_llm(task: str, registry: SkillRegistry, llm_call) -> Optional[str]:
    """生产路由:把清单 + 任务交给 LLM,让它返回 name。"""
    prompt = (
        registry.describe_all()
        + f"\n\n用户任务:{task}\n只回复最匹配的技能 name,没有就回 NONE。"
    )
    name = llm_call(prompt).strip()
    return name if name in registry._index else route_deterministic(task, registry)

💡 工程实践:永远保留确定性兜底。LLM 路由偶尔会返回不存在的 name 或乱编技能,用 name in registry._index 校验 + 兜底,是生产稳定的关键。

3.5 运行与验证

if __name__ == "__main__":
    reg = SkillRegistry("./skills").scan()
    print(f"已扫描 {len(reg)} 个技能:")
    print(reg.describe_all())  # 阶段一:只输出 name+description

    task = "帮我查下 uid=10086 有没有访问报表的权限"
    name = route_deterministic(task, reg)      # 或 route_by_llm(...)
    print(f"\n路由命中:{name}")

    skill = reg.load(name)                      # 阶段二:按需取正文
    if skill:
        print(f"\n===== {skill.name} 正文 =====\n{skill.body}")

预期输出(节选):

已扫描 2 个技能:
可用技能(Skills):
- user-permission-check: 当用户要求校验某账号权限、判断角色、或拉取 user_service 用户信息时使用。
- order-refund: 当用户要处理退款、冲销、或查询 order_service 某笔订单的退款状态时使用。

路由命中:user-permission-check

===== user-permission-check 正文 =====
# 用户权限校验技能
## 步骤
1. 调用 user_service 的 GET /internal/v1/profile ...

阶段一只进了清单,正文直到"路由命中"后才出现——两阶段加载验证通过。

四、踩坑与优化

4.1 description 写多写少都是坑

⚠️ description 是 Skills 机制的命门。 写太短(“处理用户相关”)Agent 想不起来;写太长(把整个手册抄进去)既浪费清单 token,又让"按需加载"失去意义。

对策:description 只写触发场景,用"当用户……时使用"的句式,控制在 1–2 句话。正文才放步骤细节。

4.2 技能职责重叠导致路由冲突

⚠️ 两个 Skill 的 description 都写"处理订单",路由会随机命中,行为不可复现。

对策:技能之间用明确边界切分。比如"退款"和"查物流"必须拆成两个 Skill,description 互不覆盖触发词。定期用真实任务做路由回归测试。

4.3 安全与共享

💡 三件必须做的事

  1. Skill 正文里绝不写密钥/Token,敏感信息走环境变量或 MCP Server。
  2. 执行高风险动作(删数据、发请求)前加 human-in-the-loop 确认点。
  3. 技能是普通 Markdown,天然可 git 管理、可跨框架(Claude Code / Codex / Cursor 都认 SKILL.md),团队共建时统一放 skills/ 目录即可共享。

五、总结

2026 年 Agent 工程化的关键词是"分层收敛":MCP 管工具连接、A2A 管 Agent 间协作、Skills 管经验沉淀。本文讲的就是其中最容易被忽视、却最贴近开发者日常的一层——把"我会怎么做"固化成可复用、可路由、可按需加载的 SKILL.md

我们手撸的 SkillRegistry 已经具备两阶段加载与确定性兜底路由,你只要往 skills/ 里不断加 SKILL.md,Agent 的能力就会像搭积木一样长出来,且每次新会话都"记得"昨天的经验。

后续方向:把 Skill 路由接上真实 LLM、给 Skill 加版本号与依赖声明、再和 MCP Server 组合——一个最小可用的 Agent 工程系统就成型了。


如果本文对你有帮助,欢迎点赞、收藏、关注~ 你打算先用 Skill 沉淀哪类工程经验?评论区聊聊你的方案。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐