拆解 Agent Skill 运行机制:从“语义路由”到“渐进式披露”

📅 2026/7/31 3:41:15 👁️ 阅读次数 📝 编程学习
拆解 Agent Skill 运行机制:从“语义路由”到“渐进式披露”

你以为 Skill 只是一个文件夹?其实它是 LLM 与外部世界之间的“惰性接口”。

一、Skill 究竟是什么?

在开始之前,我们先统一认知:Skill 不是函数,不是插件,它是一个磁盘上的文件夹,其标准结构如下:

my-skill/ ├── SKILL.md # 核心文件(必须) ├── references/ # 参考文档(可选) ├── scripts/ # 可执行脚本(可选) └── assets/ # 静态资源(可选)

其中,SKILL.md是最关键的文件,它包含两部分:

  • YAML 头(由---包裹):存放namedescription等元数据。
  • Markdown 正文:存放详细的指令、工作流、引用说明等。

这种文件化的形态,决定了 Skill 的所有加载行为都是按需、惰性的 —— 这也是其核心优势所在。


二、核心设计哲学:渐进式披露(Progressive Disclosure)

Agent 面临的矛盾很现实:

  • 我们希望 Agent 拥有成百上千个 Skill,以应对各种复杂任务。
  • 但 LLM 的上下文窗口有限(即便 128K、1M 也经不起大量文本的堆砌)。

渐进式披露正是解决这一矛盾的关键思想:Skill 的内容不是一次性全部塞给 LLM,而是分层次、按需加载,仅在需要时才进入上下文。

具体来说,Skill 被划分为三个信息层级:

层级内容加载时机Token 成本
L1 索引层name+descriptionAgent 启动时~100 Token/个
L2 指令层SKILL.md完整正文LLM 决定调用该 Skill 后几千 Token
L3 资源层references/scripts/LLM 按指令执行具体操作时按需读取,脚本代码本身不进上下文

下面我们通过一个具体案例,来看这四个阶段是如何串联起来的。


三、案例 Skill:pdf-financial-analyzer

我们有一个用于分析财报 PDF、提取关键财务指标并生成健康度评分的 Skill,目录结构如下:

pdf-financial-analyzer/ ├── SKILL.md ├── references/ │ ├── gaap-standards.md # 美国通用会计准则参考 │ └── industry-benchmarks.md # 各行业财务基准值 ├── scripts/ │ └── extract_ratios.py # 提取并计算财务比率的脚本 └── assets/ └── report_template.json # 最终报告输出的 JSON 模板

现在,用户提问:“分析这份财报 PDF,提取关键财务比率并给出健康度评分。”

接下来,我们跟随 Agent 的视角,走一遍完整调用链。


四、完整调用链(4 个阶段)

在看具体阶段前,我们先通过一张全景流程图,感受一下 L1、L2、L3 这三层信息是如何在不同时机被加载的:

阶段3:按需执行(L3加载)

阶段2:指令注入(L2加载)

阶段1:语义路由

description 匹配成功

Read references/gaap-standards.md

Bash scripts/extract_ratios.py

阶段0:启动索引(L1加载)

Agent 启动

Runtime 遍历磁盘 Skill 目录

只读 SKILL.md 的 YAML 头
(name + description)

拼接进系统提示词
🔵 L1 元数据层加载完成

用户提问:
“分析这份财报PDF”

LLM 语义匹配

LLM 返回 tool_use 请求
type: Skill, command: pdf-financial-analyzer

Agent Runtime 拦截请求

校验权限 + 读取磁盘
完整 SKILL.md 正文

追加为新的对话消息
🟢 L2 指令层加载完成

LLM 按 SKILL.md 指令推理

需要具体资源?

Runtime 读取文件内容

Runtime 拉起子进程运行

仅返回文本内容给 LLM

仅返回执行日志/结果给 LLM
🟡 L3 资源层加载完成

LLM 整合结果并回答用户

阶段 0:启动扫描 —— 只读“身份证”

Agent 启动时,Runtime(运行时)会遍历所有 Skill 目录(例如~/.agent/skills/*),只解析每个SKILL.md的 YAML 头,提取namedescription

对于我们的案例,YAML 头可能是这样的:

---name:pdf-financial-analyzerdescription:解析财报PDF,自动提取三大报表数据,计算流动比率、速动比率、毛利率等关键指标,并与行业基准对比生成企业健康度评分。当用户提及“分析财报”、“PDF财务数据”、“财务比率”、“企业健康度”时使用。---

随后,Runtime 将所有 Skill 的name + description拼接成系统提示词,例如:

你有以下 Skill 可用: - pdf-financial-analyzer:解析财报PDF,自动提取三大报表数据... - xlsx:处理 Excel 电子表格 - pptx:创建 PowerPoint 演示文稿 ...

关键点

  • 这一步成本极低,每个 Skill 仅消耗 ~100 Token,几十个 Skill 也不过几千 Token。
  • Markdown 正文、scripts/、references/ 等全部按兵不动

阶段 1:用户提问,LLM 语义路由

用户说:“分析这份财报 PDF,提取关键财务比率并给出健康度评分。”

此时,LLM 接收到的上下文 =系统提示(含 Skill 列表) + 当前对话。LLM 会在 Transformer 的前向传播中,将用户的问题与各个 Skill 的description进行语义匹配

因为pdf-financial-analyzer的 description 明确提到了“财报PDF”、“财务比率”、“健康度”,LLM 判断该 Skill 适合当前任务,于是返回一个标准工具调用

{"type":"tool_use","name":"Skill","input":{"command":"pdf-financial-analyzer","args":"分析这份财报 PDF,提取关键财务比率并给出健康度评分"}}

重要澄清

  • 决定使用哪个 Skill不是关键词匹配或规则引擎,而是LLM 自己基于语义理解做出的判断
  • 因此,description写得好不好,直接决定 Skill 能否被正确触发。

阶段 2:Agent Runtime 拦截,注入完整指令

LLM 返回tool_use后,Agent Runtime接管控制权,执行以下操作:

  1. 校验:检查pdf-financial-analyzer是否真实存在于磁盘。
  2. 权限检查:确认调用方配置中允许使用Skill工具。
  3. 读取文件:从磁盘读取SKILL.md完整 Markdown 正文
  4. 注入上下文:将完整的 Markdown 正文作为一条新的对话消息追加到 LLM 的上下文中(注意:不是修改系统提示词)。

此时,LLM 手里拿到了类似这样的指令(节选):

## Workflow 1. 调用 `scripts/extract_ratios.py --pdf <path>` 解析 PDF 中的三张表。 2. 按 `references/gaap-standards.md` 校验科目名称的合规性。 3. 计算核心指标: - 流动比率 = 流动资产 / 流动负债 - 速动比率 = (流动资产 - 存货) / 流动负债 - 毛利率 = (营收 - 成本) / 营收 4. 去 `references/industry-benchmarks.md` 查询同行业基准值。 5. 按 `assets/report_template.json` 格式输出结果。 ...

关键点

  • 这一步是L2 指令层的加载,只在 Skill 被真正调用时才发生。
  • 注入后的SKILL.md正文会一直留在对话上下文中,供后续推理使用。

阶段 3:LLM 按指令干活,按需碰附属文件

现在 LLM 有了完整指令,开始逐步执行:

  • 执行脚本:调用 Bash 工具运行scripts/extract_ratios.py --pdf quarterly_report.pdf,Runtime 负责拉起子进程,只将 stdout/stderr 回传给 LLM,脚本源代码本身不进上下文
  • 查阅标准:当遇到不常见的财报科目时,LLM 会Read references/gaap-standards.md进行核对。
  • 对比基准:计算完比率后,Read references/industry-benchmarks.md获取同行业平均水平。
  • 加载模板:将assets/report_template.json作为输出结构模板,确保输出格式统一。

关键点

  • 这是L3 资源层的按需加载,只有被SKILL.md正文引用到的文件才会被读取。
  • scripts/ 下的代码完全不会进入 LLM 上下文,这彻底绕过了 Token 限制,也保护了代码隐私。

阶段 4:结果回传,Skill 指令留在上下文

脚本输出的财务比率、基准对比结论、健康度评分等,都通过 Runtime 回传给 LLM。LLM 整合这些信息,最终给用户一个完整的分析报告。

而注入的SKILL.md正文依然留在上下文中,如果用户追问:“把存货周转率也加上”,LLM 仍然记得指令中的扩展点,可以无缝继续。

如果对话过长触发截断策略,或者用户明确切换任务,Skill 的正文才可能被压缩或移除。


五、核心角色:Agent Runtime

从上文可以看出,在整个调用链中,LLM 只负责“想”和“说”(语义判断 + 返回 tool_use),而Agent Runtime 负责“做”。为了更清晰地展示两者之间的交互边界,我们通过时序图来看一次完整的调用过程:

💾 磁盘文件系统🦾 Agent Runtime(躯体)🧠 LLM(大脑)👤 用户💾 磁盘文件系统🦾 Agent Runtime(躯体)🧠 LLM(大脑)👤 用户阶段0:启动时(极低成本)阶段1:提问与决策阶段2:拦截与注入(L2)阶段3:按需执行(L3)遍历 skills/,解析 YAML 头返回 name + description注入系统提示(仅 Skill 列表)“分析这份财报PDF”语义比对 description返回 tool_use (Skill, command=pdf-financial-analyzer)读取完整 SKILL.md 正文返回 Markdown 指令追加为新的对话消息(注入操作手册)按手册要求,执行 scripts/extract_ratios.py拉起子进程运行(代码不进上下文)返回 stdout/stderr 日志回传执行结果(仅文本)输出最终财务分析报告与健康度结论

Agent Runtime 的具体职责包括:

  • 启动时扫描并构建索引
  • 拦截工具调用并路由
  • 从磁盘读取文件并注入上下文
  • 执行子进程并捕获输出
  • 管理会话状态和上下文生命周期

正是 Runtime 的存在,使得 Skill 能像“插件”一样挂载,又不会像传统函数那样提前占用上下文。


六、为什么 YAML 头如此重要?

YAML 头是 Skill 的“机器可读摘要”,它的作用不可替代:

  1. 极低成本建索引:启动时只读这几十个字节,不读正文,省 Token。
  2. 标准化解析:YAML 格式让 Runtime 可以轻松提取字段,无需 NLP。
  3. 语义路由依据:LLM 完全依靠description来判断何时调用该 Skill。写得好,触发精准;写得差,形同虚设。

因此,写好description是一门学问,建议包含:触发场景、适用任务类型、关键词等。


七、总结与思考

核心机制一句话概括

Skill 的运行机制 = 启动时扫索引(L1) + 调用时注指令(L2) + 执行时取资源(L3) + 代码永不进上下文。

这一设计带来的优势

优势说明
海量技能可拥有数百个 Skill,启动成本仅线性增长(每个 ~100 Token)。
上下文干净只有被调用的 Skill 指令才会进入上下文,避免无关信息干扰。
代码隐私脚本源码留在磁盘,不给 LLM 看,保护知识产权。
灵活扩展新增 Skill 只需放一个文件夹,无需修改 Agent 核心代码。

一点延伸思考

这种“目录即接口”的设计,其实与微服务架构中的“服务发现”有异曲同工之妙。未来,Skill 或许会成为 Agent 生态中的“标准化容器”,让跨 Agent 的技能共享变得更加容易。

本文案例代码均为示意,实际 Skill 可根据需要封装任意复杂度的工具链。