首页
看点啥
插画图片
首页 看点啥 设计 Skill 系统,这 3 个坑我替你踩过了

设计 Skill 系统,这 3 个坑我替你踩过了

2026-08-22 0

设计 Skill 系统,这 3 个坑我替你踩过了需要先看清适用场景和关键步骤,避免只记结论却忽略实际限制。

前言

Skill 是什么?说白了就是一个技能包,核心是一个 SKILL.md 文件。Agent 干活的时候,会根据任务需要按需加载对应的 Skill,比如要做 UI 设计、代码审查,就加载相应的那一个。

听起来是不是挺简单?你可能已经打开 AI 编程工具,准备直接丢一句"帮我实现 Skill 机制"过去。

等一下 ,先别急。 Skill 听着简单,但你真的想清楚怎么在 Agent 系统里实现它了吗?先看这几个问题你能不能答上来:

  1. SKILL.md 里除了 name 和 description,还有什么头部元信息?
  2. Agent 要用某个 Skill 的时候,如何定位以及如何调用 Skill 的?
  3. Skill 去重是否有考虑过?

如果这几个问题你还没想明白,那接下来我就一个一个拆,把整套 Skill 机制的设计讲清楚。

一、认识 SKILL.md:头部元信息

请先查收 Claude Code 官方给的标准Skill头字段:code.claude.com/docs/zh-CN/…

这里我挑几个典型的讲:

字段作用
name展现名称
description给模型看,决定何时用
allowed-tools工具白名单
disallowed-tools工具黑名单
model限定模型
metadata自定义键值
agent指定执行用的 subagent
context执行时是否开辟独立子上下文
  1. 其中 namedescription 决定了 Agent 什么时候调用它。

  2. allowed-tools 可能很多人会理解为只允许使用什么工具,实际上是赋予 Agent 执行这些工具的权限,无需你再盯着点同意。

  3. disallowed-tools 则是字面意思,硬性禁止某些工具/命令,即使模型想调也会被系统拦截

    这里有个坑:此次禁用的工具,需要在下一个回合(turn)恢复,否则这些工具会在这个会话里一直被禁用,影响后续操作。。

(turn 是什么,后面文章会专门讲,这里先按这个理解:turn 就是一次会话回合。)

[第 1 回合] 你发消息 → 模型调用某 Skill → Skill 进入 active            → disallowed-tools 里的工具被从可用工具池里移除            → 这一回合里,模型根本"看不到" Write / Edit,想调都调不了[第 2 回合] 你发下一条消息 → 限制清除 → Write / Edit 恢复可用
  1. disallowed-toolsallowed-tools 一样,都是临时作用域。只在调用 Skill 的那个回合预批准

  2. model 则是可以指定这个 Skill 只在特定模型下启用,别的模型加载不到。典型用法有两种:某个 Skill 依赖长上下文或复杂推理,小模型扛不住,就限定它只用大模型,免得跑崩;反过来,简单的 Skill 也可以限定用小模型,省钱。

  3. contextagent 则是配合用的:context 决定这个 Skill 是在主对话里跑,还是开辟一个独立子上下文单独跑agent 决定用哪个子 Agent 来执行。典型场景是:某个 Skill 过程很脏——读几十个文件、跑一堆命令——但你只关心最终结果。这时设 context: fork,让它跑在独立上下文里、不污染主对话,再指定一个子 Agent 去干这票重活。

  4. metadata 则是用来塞任意自定义键值的地方,模型一般不读它。通常是给团队的管理、审计、成本核算用的——比如记 ownertagscost-center,Skill 管理平台扫目录时可以按这些字段分类统计。它不影响 Agent 怎么跑,只影响你怎么管。

Skill案例

简单场景,同时也是大多数Skill的头部元信息

---name:frontend-ui-engineeringdescription:构建生产级品质的用户界面。在构建或修改面向用户的界面时使用。在创建组件、实现布局、管理状态,或需要输出看起来达到生产级品质而非“AI生成感”时使用。---

复杂一点的:

---name:code-reviewdescription:当用户要求审查代码、检查PR、或查找潜在bug时使用。allowed-tools:-Read-Grep-Bash(gitdiff:*)model:claude-sonnet-4-5disable-model-invocation:falselicense:MITversion:1.2.0metadata:scope:projectagents: [backend-bot, reviewer-bot]---

(示例里的 licensedisable-model-invocationversion 属于官方标准里的其他字段,基础版可以先不处理。)

结论:如果你实现的是基础 Skill 系统,只处理 namedescription 就够了;后续要扩展,再基于上面这些字段往上加。

二、深入了解 Skill 与 Agent 的交互

1. Agent 如何 调用 Skill

Agent是怎么调用 Skill的 ? 目前有两种主流的做法:

  1. 单独为Skill设计一个工具,Agent 通过 skill_name 加载 Skill
SKILL_TOOL = {    "name": "skill",    "description": "按 name 加载一个已注册的 Skill,返回它的完整指令。",    "parameters": {        "skill_name": {            "type": "string",            "description": "要加载的 Skill 名称",            "required": True,        },    },}defexecute_skill_tool(skill_name: str) ->str:    skill = find_skill_by_name(skill_name)   # 在注册表里按 name 找ifnot skill:        returnf"未找到 Skill: {skill_name}"    body = load_body(skill["path"])          # 正文    apply_permissions(skill["meta"])         # 应用 allowed/disallowed-tools# 返回三件套,正文作为 tool result 注入上下文return {        "activation": f"{skill_name}",  # 激活标记"base_dir": skill["base_dir"],        # 根目录,正文里的相对路径靠它"body": body,                          # 正文指令    }              

注意这个返回值不只是 Skill.md 文件里面的内容,而是三样东西:

  1. activation(激活标记){skill_name}。这是给系统看的——表示"这个 Skill 已被调用",用来做去重和状态追踪(下文作解释),也方便前端展示调用事件。
  2. base_dir(根目录) :Skill 所在的目录。
  3. body(正文) :SKILL.md 正文内容。
  1. 通过 ReadFile 工具根据路径读取 SKILL.md 正文
READ_FILE_TOOL = {    "name": "read_file",    "description": "按路径读取文件内容。",    "parameters": {        "path": {            "type": "string",            "description": "文件路径",            "required": True,        },    },}defexecute_read_file_tool(path: str) ->str:    return Path(path).read_text(encoding="utf-8")   

顺带补充一个小知识:Skill 的组成不只有 SKILL.md 这一个文件。除了 SKILL.md,还可以带知识文档、可执行脚本、静态资源等:

{skill_name}/├── SKILL.md        # 必填:入口(YAML frontmatter + Markdown 指令)├── scripts/        # 可选:可执行脚本(Python / Bash)├── references/     # 可选:长文档、规范、示例└── assets/         # 可选:模板、图标、字体等静态资源

所以调用Skill的本质其实是工具调用,使用 读工具 读SKILL.md 或者其他知识文件,使用 Bash 工具执行脚本。

不过在Codex中我倒是发现其内部使用 PowerShell 的 cat 命令获取SKILL.md。

Claude Code 就偏向使用 SKILL_TOOL 完成Skill的加载

2. Agent 如何 定位 Skill

第一步系统首先扫一遍指定目录,找到所有 skill文件夹下的SKILL.md,只读头部的 name 和 description,收进注册表中

defregister_skills(skill_dir: str) ->list[dict]:    skills = []    for path in Path(skill_dir).rglob("SKILL.md"):        meta = parse_frontmatter(path)  # 只读头部 name 和 description        skills.append({            "name": meta["name"],            "description": meta["description"],            "path": str(path),        })    return skills

接着把这些信息注入系统提示词。两种工具对应的注入内容不一样:

  1. 用 SKILL_TOOL 的方式:只注入 name 和 description
  2. 用 ReadFile 的方式:除了 name 和 description,还要把 SKILL.md 的路径也注入,让模型知道去哪读

Skill 在上下文里的显示大概长这样:

<available_skills><skill><name>pdfname><description>Comprehensive PDF manipulation toolkit for extracting text and tables, merging/splitting documents, and handling forms.description><path>/absolute/path/to/pdf/SKILL.mdpath>skill>available_skills>

是给 ReadFile 方式定位文件用的;如果是 Skill 工具方式,路径留在注册表里,不暴露给模型。)

注意,这里只放了 name 和 description,没放正文。这是整个 Skill 系统里最关键的一个设计:注册要轻。 如果这一步就把正文全塞进去,后面的"匹配"和"加载"就没意义了,上下文也会被无关内容占满。

三、Skill 的激活标记:去重与状态追踪

去重:别把同一个 Skill 重复加载

一次任务里,模型可能反复想用同一个 Skill。比如用户连续问两轮"再帮我审查一下代码",模型可能两次都想调 code-review

如果没有去重,code-review 的正文会被注入两次,白白浪费 token,上下文里还多了份重复内容。

有激活标记 + 去重,系统就能判断:

一句话总结就是:去重 = 防止同一个 Skill 的正文被反复塞进上下文。

状态追踪:记住"现在哪些 Skill 是激活的"

系统需要维护一个"当前激活中的 Skill 列表",因为好几件事都靠它:

  1. 权限:Skill 激活期间,它的 allowed/disallowed-tools 生效;一旦退出激活,就要把权限收回来。系统得知道"现在该收谁的权限"。
  2. 前端展示:界面上显示"当前正在执行 code-review",也是从这个列表读的。
  3. 生命周期:记录 Skill 什么时候进 active、什么时候退出。

一句话总结就是:状态追踪 = 系统维护一张"谁正在激活"的表,用来管权限生效和恢复。


放到你的 Skill 系统实现里,大概就是:

active_skills = set()          # 当前激活的 Skill 集合defactivate(skill_name):    if skill_name in active_skills:        return"已激活,跳过"# 去重    active_skills.add(skill_name)   # 状态追踪    apply_permissions(skill)defdeactivate(skill_name):    active_skills.discard(skill_name)    restore_permissions()

active_skills 这个集合,同时干了"去重"和"状态追踪"两件事——判断重复靠它,知道该恢复谁也靠它。

喜欢(0)

上一篇

模型“开源”了,就能直接拿来做 Agent 吗?

模型“开源”了,就能直接拿来做 Agent 吗?

下一篇

漫画免费阅读app哪个好-免费漫画阅读app推荐下载

漫画免费阅读app哪个好-免费漫画阅读app推荐下载
猜你喜欢