面试知识库
中 进阶

Agent Skill机制与渐进式加载#

一句话答案#

Skill 是一个目录:SKILL.md(name + description + 指令正文)加可选的脚本和参考文件。上下文里常驻的只有每份 skill 的 name 和 description,模型判断当前任务用得上时才读正文,正文里再引用附属文件按需读。这样几十份打法常驻的成本只有几十行描述;代价是模型可能漏读,需要用触发率评测来发现。

核心要点

1. Skill 长什么样#

skills/
└── pdf-processing/          # 目录名必须和 name 一致
    ├── SKILL.md             # 必需:frontmatter + 指令正文
    ├── references/
    │   └── forms.md         # 可选:细则、口径表,正文里点名时才读
    ├── scripts/
    │   └── fill_form.py     # 可选:确定性步骤交给脚本
    └── assets/              # 可选:模板、数据文件
text
---
name: pdf-processing
description: 提取 PDF 文本和表格、填写 PDF 表单、合并多个 PDF。用户提到 PDF、表单或文档抽取时使用。
---

# PDF 处理

1. 填表前先用 scripts/fill_form.py --list 读出字段名,禁止根据文件名猜字段。
2. 合并前确认页序,输出文件名沿用用户给的命名。
3. 各类表单的填写口径见 references/forms.md。
markdown
  • name:唯一标识,也是调用时的参数。
  • description:写清「做什么」和「什么时候用」。它是唯一的触发面,正文写得再好,description 不准就永远不会被读到。
  • 正文:一类任务的完整打法,写步骤、边界和反例。
  • 附属文件:大段细则、模板、可执行脚本。脚本的好处是确定性逻辑不用模型每次重新生成。

开放标准现状:Anthropic 在 2025 年 12 月把 Agent Skills 作为开放标准发布(规范和参考校验库在 agentskills.io),OpenAI Codex 等工具也采用了同一个 SKILL.md 格式,不同产品的存放目录和调用方式有差异(例如 Claude Code 用 .claude/skills/ 和 /name,Codex 用 .agents/skills/)。规范里的 frontmatter 字段:

字段必需约束(按 agentskills.io 规范)
name是1–64 字符,只能用小写字母、数字、连字符;不能以连字符开头或结尾,不能有连续连字符;必须和所在目录名一致
description是1–1024 字符,写清做什么和什么时候用
license否许可证名或附带的许可证文件名
compatibility否最多 500 字符,写环境要求(目标产品、系统依赖、网络等)
metadata否字符串键值对,放规范之外的自定义属性
allowed-tools否空格分隔的预授权工具列表,标注为实验性,各实现支持程度不同

各产品可以在规范之外扩展字段。例如 Claude Code 额外支持 when_to_use、disable-model-invocation(禁止模型自动加载,只能用户 /name 调用)、user-invocable、disallowed-tools、context: fork(在子 Agent 里跑)、paths、hooks、model 等;这些字段上传到只认规范字段的平台(如 claude.ai、Skills API)会报错。

2. 渐进式披露:三层加载#

层内容何时进上下文成本
L1 目录所有 skill 的 name + description每轮常驻(通常拼在 system prompt 之后)每份一两行
L2 正文命中那份的 SKILL.md 正文模型判断需要时,调用读取工具只在用到的回合付
L3 附属reference、模板、脚本输出正文里点名且当前步骤需要时按需

规范给的量级参考:L1 每份约 100 token;L2 正文建议少于 5000 token、SKILL.md 控制在 500 行以内,更长的细则拆进 references;附属文件只引用一层,不要层层嵌套引用。Claude Code 里 skill 正文被调用后会留在上下文里跨轮保留,自动压缩时可能只保留每份的前一部分(具体上限以官方文档为准)。

核心收益是把「每轮都付」变成「用到才付」。system prompt 里只留每轮都成立的规则(安全边界、终止条件、红线),按场景才用得上的打法搬进 skill。和 Agent记忆与上下文工程 里讲的渐进式披露思想相同,做法有区别:这里只把指令正文延后加载,工具表每轮保持不变;按意图动态增减工具会让 prompt 前缀变化、前缀缓存失效(见 [Agent Harness与Hook设计](/topics/ai-agent/Agent Harness与Hook设计))。skill 是把「正文按需加载」规范成文件格式的一种做法。

一个实现细节:L1 目录要保持稳定。目录块内容变了(比如运行中改了 SKILL.md、目录按修改时间排序),会让 system prompt 变化,前缀缓存从那里开始失效。

3. 三种加载路径和取舍#

路径谁决定加载优点问题
模型自主模型读 description 后调 Skill(name)无需硬判据,通用可能漏读;多一次往返
控制面预注入代码按意图分类结果直接塞正文不会漏;省往返判据常常也来自 LLM 输出,不硬;多轮会话里同一份正文可能重复注入
用户显式选输入框 /skill-name意图最明确依赖用户知道有这份 skill

Claude Code 用 frontmatter 控制前两种路径的开关:disable-model-invocation: true 只允许用户显式调用,user-invocable: false 只允许模型调用。

省往返的做法:在 prompt 里要求模型把 Skill(...) 和本轮第一个业务工具(比如检索)放在同一次回复里并行发出,而不是先读 skill 再等下一轮。不过模型未必照做,需要在评测里单独统计。

一条实用分界线:事实接地(查知识库、理解图片)可以由机制预先做;打法的选择交给模型。

4. 和 Tool / MCP / System Prompt / 子 Agent 的边界#

机制本质加载时机适合放什么
System Prompt常驻指令每轮角色、红线、终止规则、每轮都成立的约束
Skill按需加载的指令和资料命中时某类任务的做法、口径、模板
Tool / Function Calling可执行的动作schema 常驻,调用时执行查询、写入、计算,见 [Function Calling与工具编排](/topics/ai-agent/Function Calling与工具编排)
MCP工具和资源的接入协议连接 server 时注册跨进程、跨团队复用的工具集,见 MCP协议原理
子 Agent独立上下文里的另一个循环派发时需要隔离上下文、结果只回摘要的子任务,见 多Agent协作架构

一句话分:Tool 让 Agent 能做某件事,Skill 告诉它这类事怎么做好。 本文的设计原则是 Skill 不增加权限:读 skill 的工具是只读的,skill 正文也不能替代写操作的确认流程。例外是 allowed-tools 字段:在 Claude Code 里,skill 被调用后,列出的工具在当前这一轮免确认执行,用户发下一条消息时授权清除;它不限制其他工具的可用性,会话里配置的 deny / ask 规则仍然优先。用这个字段就等于做了一次授权,要按授权来审。

5. 触发准确率怎么评估#

模型自主加载后,「有没有读对」没有确定性判据可以写单测,只能真跑模型。

  1. 标注集:每份 skill 若干正例 query(应该读它),加一组负例(不该读任何 skill,比如点名具体型号的直搜)。暂时测不了的 skill 显式登记为未覆盖,并在报告里写明原因。
  2. 只跑到第一批工具调用:打法是在第一批里决定的,再往下跑是浪费。在「模型推理后、工具执行前」的切点挂探针,拿到 tool_call 就中断。
  3. 分四种结果记:
结果含义修什么
PASS正例读对了—
MISS正例没读任何 skilldescription 或分流规则没说清什么时候必须读
WRONG_SKILL读错了份两份 description 边界重叠
FALSE_FIRE负例读了 skilldescription 写得太宽
def judge(case, first_batch_calls):
    loaded = {c.args["skill"] for c in first_batch_calls if c.name == "Skill"}
    if case.expected is None:                     # 负例
        return "FALSE_FIRE" if loaded else "PASS"
    if case.expected in loaded:
        return "PASS"
    return "WRONG_SKILL" if loaded else "MISS"
python
  1. 负例必须有:只看正例触发率,把 description 写宽就能刷高分;负例误触发不涨,才说明提升不是靠多读换来的。
  2. 看 MISS 的原因:常见情况是 description 已经覆盖了这个场景,但模型觉得「我已经知道该搜什么」就跳过了。这时要改的是分流规则的措辞(收紧「可以不读」的例外),不是正文。

6. 版本与维护#

  • 随代码走版本控制:SKILL.md 和标注集都进仓库。改 description 等同于改触发行为,必须重跑触发率评测。
  • 数量有成本:每加一份 skill,每轮常驻多一行描述。可以在测试里维护一份期望的 skill 清单,加一份就要改一次测试,让这笔开销被看见。
  • 加载器配置会静默失败:例如目录结构是 skills/<name>/SKILL.md 而加载器只扫一层,结果加载 0 份、只打一行 info 日志。启动时断言加载数量。
  • 用户自定义 skill:放独立命名空间(如 my/ 前缀)避免覆盖内置;显式选用的正文标注为「仅供参考」,不能新增工具、不能扩权、不能跳过确认。
  • 第三方 skill 带脚本时就是在跑外部代码:安装前要审查,执行放沙箱,和 [Agent安全与Prompt Injection防御](/topics/ai-governance/Agent安全与Prompt Injection防御) 里对待外部工具的原则一致。
  • 格式校验:规范提供 skills-ref validate ./my-skill 检查 frontmatter 和命名,可以放进 CI。

面试回答(2分钟版)

Skill 我理解成「按需加载的打法包」。一份 skill 是一个目录,核心是 SKILL.md,frontmatter 里有 name 和 description,正文写某类任务怎么做,旁边可以放参考文档和脚本。关键在渐进式披露:上下文里常驻的只有每份 skill 的 name 和 description,模型判断这轮用得上,才调工具读正文,正文里提到的附属文件再按需读。这样 system prompt 里只留每轮都成立的红线和终止规则,场景类的打法都搬出去,每轮只多付几行描述。它和 Tool 的区别是:Tool 让 Agent 能做一件事,Skill 告诉它这类事怎么做好;MCP 是工具接入协议;子 Agent 是另开一个上下文跑循环。代价是模型可能漏读,description 是唯一触发面,所以要做触发率评测:正例、负例各一组,真跑模型,只看第一批工具调用里有没有读对那份,按命中、漏读、读错、误触发四类记。负例一定要有,不然把 description 写宽就能刷高触发率。另外 Agent Skills 已经是开放标准,Claude Code、Codex 等都认同一个 SKILL.md 格式,name 和 description 是必填字段。结合项目时可以讲:skill 按什么维度切分、加载路径选了模型自主还是预注入、触发率用什么标注集验证。

追问与易错

追问方向:

  • “Skill 和直接写进 system prompt 有什么区别?” → 成本模型不同:写进 prompt 每轮都付 token、每轮模型都要读一遍;skill 只常驻一行描述,正文在用到的那轮才进上下文。代价是多一次模型判断,判错等于这份打法没生效。
  • “为什么不用意图分类结果确定性地预注入?” → 分类结果本身常是 LLM 输出,判据并不硬;预注入的正文写进会话状态后,多轮对话里同一份可能重复出现;读 skill 的往返可以通过和业务工具同轮发出来省。
  • “模型漏读 skill 怎么发现?” → 线上看不出来,只表现为答案质量差。只能靠离线触发率评测:正例 query 的第一批 tool_call 里没有那份 skill 就记 MISS。
  • “触发率评测为什么只跑到第一次模型调用?” → 分流要求在第一批就把 skill 读进来,后面的步骤和触发无关,继续跑只是花钱;在推理后、工具执行前中断,每条只花一次模型调用。
  • “中断探针为什么用 BaseException 而不是 Exception?” → 如果 hook 执行器最外层是 except Exception 吞异常(为了不让治理代码拖垮主链路),普通异常会被吞掉,整轮照跑照收费。
  • “Skill 会不会影响前缀缓存?” → 目录块本身静态、放在稳定前缀之后就没问题;但运行中修改 SKILL.md 会让目录或正文变化,从变化处开始缓存失效,改完应新开会话。
  • “用户自定义 skill 会不会变成提权口子?” → 走同一个只读读取工具,不新增工具、不改 harness;显式选用的正文标注仅供参考,明确不能扩权、不能代替写操作的确认流程。
  • “什么内容适合放 skill 的脚本里?” → 确定性、每次都一样的步骤(格式转换、校验、计算),让模型调用脚本而不是每次重新生成代码,结果更稳定也省 token。

易错点:

  • ❌ “正文写好就行” → description 才决定会不会被读到,写清「什么时候用」比正文措辞更要紧。
  • ❌ “Skill 是另一种工具” → Skill 是指令和资料,模型读了之后再去调工具或运行附带的脚本,它本身不是一个可调用的动作;按本文的设计原则也不带来新权限(Claude Code 的 allowed-tools 字段例外,见第 4 节)。
  • ❌ “只看正例触发率” → 没有负例,误触发涨了也看不见。