深度解析【Perplexity】 公开内部手册:Agent Skills 是什么?如何设计、优化和维护?(附完整结构模板)

作者:技术博主 | 更新时间:2026-05-11 | 阅读时长:约 20 分钟
来源:Perplexity Research《Designing, Refining, and Maintaining Agent Skills at Perplexity》
标签Agent Agent Skills MCP Perplexity LLM 智能体 工具调用 Claude Code


🔥 一句话定位:Perplexity 把内部构建 Agent Skills 的玩法彻底公开了。如果你正在做 Agent,这份手册比大多数教程都实战得多——它不讲原理,直接讲:description 怎么写才能被正确路由,gotcha 列表怎么维护,progressive loading 怎么设计,以及怎么跑 eval。本篇带你逐条拆解。


在这里插入图片描述

目录


一、什么是 Agent Skill?和 MCP Tool 有什么区别

在开始之前,需要先把概念说清楚,因为很多人把 Agent Skill 和 MCP Tool 混淆了。

MCP Tool(工具调用):
  模型调用一个函数/API,得到结果
  例:search(query) -> 返回搜索结果
  粒度:细,单个功能
  状态:无状态

Agent Skill:
  模型加载一套"指令集 + 文档 + 脚本"的组合
  告诉模型:在这类任务里,应该怎么思考、用什么工具、注意什么
  粒度:粗,完整的能力域
  状态:有层级、有依赖关系

两者的关系:
  Skill 经常封装多个 MCP Tool 的调用逻辑
  Skill 是"如何做事的说明书"
  Tool 是"做事的手"

Perplexity 的定义很直接:Skill 是可被调用的(invocable)。Agent 在运行时动态加载 Skill,大多数情况下 Skill 并不常驻 context——它们根据具体需要被逐步展开(progressively disclosed)。

这一点极其重要,后文会反复强调。


二、Skill 的物理结构:hub-spoke 模式

在这里插入图片描述

一个 Skill 在文件系统上是一个目录,结构如下:

my-skill/               <- 目录名 = Skill 名(必须完全匹配)
├── SKILL.md            <- 核心:hub 文件,模型激活后先读这个
├── scripts/
│   └── helper.py       <- 可选:可执行脚本
└── references/
    ├── FORMATTING.md   <- 辅助文档(spoke)
    ├── edge-cases.md   <- 辅助文档(spoke)
    └── finance.md      <- 辅助文档(spoke)

hub-spoke 设计的本质:

SKILL.md(hub):
  - 模型激活 Skill 后,优先完整读取
  - 包含:核心指令、常见场景、gotcha 列表、辅助文件索引
  - 不应该太长(过长会塞满 context)

references/*.md(spoke):
  - 按需加载,只有任务需要时才读入
  - 每个文件专注单一主题(格式规范 / 特殊案例 / 工具文档)
  - 文件越小越好,减少不必要的 context 消耗

类比:
  SKILL.md 是一本书的目录 + 摘要
  references/* 是各章节正文
  模型根据任务需要,只翻到它需要的那一章

目录命名规则:

Skill 名称必须与所在目录名完全对应,必须全部小写,不能有空格,可以使用连字符。

✅ 正确命名:
  my-skill/
  us-tax-advisor/
  code-review-python/

❌ 错误命名:
  My Skill/      # 有大写有空格
  mySkill/       # 驼峰
  my_skill/      # 下划线(部分实现不支持)

三、Frontmatter 是 Skill 的"大脑":四个关键字段

每个 SKILL.md 的开头都是 YAML frontmatter。这几行代码决定了整个 Skill 如何被系统发现和调用:

---
name: us-tax-advisor
description: >
  Load when the user asks about U.S. income tax, IRS rules,
  deductions, filing deadlines, or tax forms (1040, W-2, etc.).
  Also load for questions about tax optimization strategies
  or state tax calculations.
depends:
  - base-financial-tools
metadata:
  version: "2.1"
  owner: "tax-team"
  eval_suite: "tax-v2-suite"
---

# U.S. Tax Advisor Skill

## Core Instructions
...

四个字段逐一解析:

name:唯一标识符

必须与目录名完全一致,是 Skill 在系统内的"身份证"。

description:路由触发器(最重要!)

下一节单独深讲,这是最容易踩坑的地方。

depends:依赖关系

depends: 字段允许创建层级化的 Skill 依赖,系统会在加载当前 Skill 前先加载依赖项。

# 示例:高级税务 Skill 依赖基础金融工具 Skill
depends:
  - base-financial-tools
  - us-legal-terminology

这使得 Skill 可以形成树状依赖图

  • 专用 Skill 站在通用 Skill 的肩膀上
  • 避免重复在每个 Skill 里定义相同的基础内容
  • 修改基础 Skill 自动传播到所有依赖它的子 Skill

metadata:供 eval 和 review 使用

metadata: 字段用于 review 和 evaluation,不会注入模型的 context,也可以在辅助 JSON/YAML 文件中维护,适合需要在运行时控制不同行为而不污染模型 context 的情况。


四、最高频踩坑:description 不是注释,是路由触发器

Perplexity 的文档在这里用了非常强烈的措辞:

“这是一个常见的失败点(common failure point)”

description 不是关于 Skill 是什么的内部文档,而是告诉模型何时加载该 Skill 的指令。所以你会频繁看到 “Load when…”,而不是 “This Skill does…”。这很重要,因为大多数实现会将 description 直接注入模型的 context。

这个区别在实践中产生巨大差异:

# ❌ 错误写法:描述功能(内部文档视角)
description: >
  This skill handles U.S. income tax calculations, including
  federal and state tax rules, deductions, and IRS compliance.

# ✅ 正确写法:路由触发器(模型决策视角)
description: >
  Load when the user asks about U.S. income tax, IRS rules,
  tax forms (1040, W-2, Schedule C), deductions, filing deadlines,
  or state tax calculations. Also load for tax optimization and
  audit-related questions.

两种写法的本质区别:

错误写法:告诉模型"这个工具能做什么"
           -> 模型可能在任何和税相关的话题时都尝试加载
           -> 也可能在应该加载时因为措辞不匹配而漏掉

正确写法:告诉模型"在什么情况下激活我"
           -> 精确的触发条件
           -> 明确的关键词映射
           -> 边界条件清晰(also load for / NOT for)

Description 的写作要点:

1. 以 "Load when" 开头(明确告知这是触发条件)
2. 枚举关键词和短语(覆盖用户可能使用的各种表达)
3. 包含边界条件(什么时候应该加载,什么时候不应该)
4. 控制长度(所有 Skill 的 description 会同时存在 context 中,
   每个 description 大约 50-100 tokens 的预算)

示例模板:
---
description: >
  Load when [主要触发场景].
  Also load for [扩展场景].
  NOT for [明确排除的场景,避免误触发].
---

五、Progressive Loading:上下文窗口管理的核心机制

在这里插入图片描述

这是 Agent Skill 设计哲学的核心。

会话开始时,Agent 扫描 skills 目录,只读取每个 SKILL.md 的 YAML frontmatter,正文不解析。只有当 Agent 判断某个 Skill 与当前任务相关,才读取完整的 SKILL.md 正文。

三个加载层级(Three Loading Tiers):

Tier 1:Frontmatter(会话启动时常驻 context)
  所有 Skill 的 name + description
  极小,约 50-100 tokens/Skill
  模型用这些来决策"是否需要激活某个 Skill"

Tier 2:SKILL.md 正文(激活 Skill 后加载)
  核心指令、核心场景、gotcha 列表
  按需加载,只有决定使用该 Skill 时才读入

Tier 3:references/*.md(任务需要时按需加载)
  条件性内容、专项文档
  任务执行中根据具体情况决定是否读入
  例:只有在生成财务报告时,才加载 FORMATTING.md

Progressive Loading 的实践意义:

没有 Progressive Loading:
  加载 50 个 Skill,每个 SKILL.md 平均 2000 tokens
  -> 光 Skill 文档就占用 100K tokens context
  -> 这些 context 大多数时候是无效噪音

有 Progressive Loading:
  Tier 1:50 个 Skill 的 description,约 5K tokens
  Tier 2:激活 1-3 个 Skill,约 3-6K tokens
  Tier 3:按需加载辅助文档,约 1-3K tokens

  总计:~10K tokens 活跃 context,且全部相关

对 SKILL.md 写作的直接影响:

如果 SKILL.md 里有任何部分是条件性的或内容极重,应该将其移出 SKILL.md(hub),放入辅助文件(spoke)。将条件性或分支逻辑放入辅助文件,以便按需加载。

# SKILL.md 里应该放什么:
✅ 核心指令(所有场景都需要的)
✅ 关键概念定义
✅ 最常见的 2-3 个用例
✅ Gotcha 列表(反例集合)
✅ 辅助文件索引(告诉模型有哪些文件,何时读)

# 不应该放在 SKILL.md 里:
❌ 完整的法规原文(-> references/regulations.md)
❌ 大量代码示例(-> references/examples.md)
❌ 条件分支逻辑(-> references/special-cases.md)
❌ 不常用的边缘情况处理

六、Gotcha 列表:从 80% 到 99.9% 成功率的关键

在这里插入图片描述

这是 Perplexity 手册里最有工程价值的一节。

Gotcha 是极高信号含量的内容,因为它们往往引导模型了解"什么事不该做"。你可以在 Agent 每次失败时追加一条,Gotcha 列表会有机生长。

Gotcha 的本质是:负例知识库。

指令(正例):
  "回答税务问题时,总是引用最新的 IRS 规则"

Gotcha(负例):
  "不要混淆联邦税率和州税率
   错误案例:用户问加州收入税,
   Agent 错误地使用了联邦税率表"

  "在 2024 年之前,个人 IRA 供款上限是 6500 美元
   不要使用 2023 年之前的旧数字(6000 美元)"

Gotcha 的增长策略:

从 80-20 到 99.9% 甚至 99.99% 的成功率,很容易不断增长 gotcha 列表。每次看到负例,就追加到 gotcha 部分。不要增加更长的指令或修改 description,追加 gotcha 即可。

Skill 生命周期:

第一周(80% 成功率):
  SKILL.md 核心指令 + 3-5 条 gotcha

第一个月(90% 成功率):
  + 10-20 条 gotcha(来自真实失败案例)

三个月后(99%+ 成功率):
  + 50-100 条 gotcha(覆盖大量边缘情况)
  通常不需要重写核心指令
  只是不断增加负例

关键认知:不要用 Gotcha 替代正向指令

❌ 错误做法:
  核心指令写得很简单
  把所有情况都用 gotcha 来约束

✅ 正确做法:
  核心指令覆盖 80% 的常规场景
  Gotcha 覆盖异常情况、历史 Bug、特殊限制

  比例参考:
    核心指令:500-1000 tokens
    Gotcha 列表:200-500 tokens
    辅助文件:按需,任意大小

七、Eval 体系:Perplexity 怎么测试 Skill 质量

Perplexity 运行多种 eval 套件来检验不同方面的质量。Skill 加载评测(Skill loading evals)检查 Skill 加载本身的精准率、召回率和禁止项——Agent 会在应该路由时加载 Skill 吗?这些 eval 还确保新 Skill 不会破坏已有 Skill 的边界。另外还有检查正确渐进加载的 eval——Agent 可能加载了 Skill,但它是否读取了辅助文件?以及端到端任务完成 eval。

把这套 eval 体系翻译成可复用的框架:

三层 Eval 体系:

Layer 1:Skill 路由 Eval
  测试点:
    Precision(精准率):触发了不应该触发的 Skill?
    Recall(召回率):应该触发的 Skill 漏触发了?
    Forbidden checks:是否加载了被明确排除的 Skill?

  测试数据:
    正例集:应该触发此 Skill 的查询
    负例集:不应该触发此 Skill 的查询
    边界案例:模糊地带的查询

  示例 eval case:
    Input: "如何计算 W-2 表格上的联邦税"
    Expected: tax-advisor Skill 被加载
    Forbidden: legal-advisor Skill 不应被加载

Layer 2:Progressive Loading Eval
  测试点:
    Skill 激活后,是否正确读取了辅助文件?
    Finance 查询是否触发了 FORMATTING.md 的加载?
    条件分支是否被正确路由到对应的辅助文件?

Layer 3:端到端任务完成 Eval
  测试点:
    最终回答的正确率
    输出格式是否符合要求
    是否触发了 Gotcha 中记录的已知失败模式

实际测试代码示例:

from typing import Callable

class SkillEvalSuite:
    """简单的 Skill 路由评测框架"""

    def __init__(self, agent_fn: Callable):
        self.agent = agent_fn
        self.results = []

    def add_case(self, query: str, expected_skills: list,
                  forbidden_skills: list = None, label: str = ""):
        self.results.append({
            "query": query,
            "expected": expected_skills,
            "forbidden": forbidden_skills or [],
            "label": label,
        })

    def run(self) -> dict:
        tp = fp = fn = 0
        failures = []

        for case in self.results:
            loaded_skills = self.agent.get_loaded_skills(case["query"])

            # 召回率计算
            for skill in case["expected"]:
                if skill in loaded_skills:
                    tp += 1
                else:
                    fn += 1
                    failures.append(f"MISS: {case['label']} -> {skill}")

            # 精准率计算
            for skill in case["forbidden"]:
                if skill in loaded_skills:
                    fp += 1
                    failures.append(f"FALSE POSITIVE: {case['label']} -> {skill}")

        precision = tp / (tp + fp) if (tp + fp) > 0 else 1.0
        recall    = tp / (tp + fn) if (tp + fn) > 0 else 1.0

        return {
            "precision": precision,
            "recall":    recall,
            "f1":        2 * precision * recall / (precision + recall + 1e-9),
            "failures":  failures,
        }

# 使用示例
suite = SkillEvalSuite(agent)

suite.add_case(
    query="我需要申报 2024 年的联邦所得税",
    expected_skills=["us-tax-advisor"],
    forbidden_skills=["legal-advisor", "investment-advisor"],
    label="basic-tax-query",
)
suite.add_case(
    query="推荐一下理财产品",
    expected_skills=["investment-advisor"],
    forbidden_skills=["us-tax-advisor"],
    label="investment-query",
)

results = suite.run()
print(f"Precision: {results['precision']:.2%}")
print(f"Recall:    {results['recall']:.2%}")
print(f"F1:        {results['f1']:.2%}")
for f in results["failures"]:
    print(f"  {f}")

八、构建 Skill 的实战流程:从 0 到上线

结合 Perplexity 手册和工程经验,整理出一套可复用的流程:

Step 1:明确边界(最重要的一步)

在写任何文件之前,先回答这三个问题:

1. 这个 Skill 解决什么具体问题?
   (不是"税务问题",而是"用户询问 U.S. 联邦所得税计算")

2. 这个 Skill 的边界在哪里?
   (它不负责什么?哪些话题应该由其他 Skill 处理?)

3. 触发条件是什么?
   (用户会用哪些词语或短语触发这个 Skill?)

只有这三个问题都清晰了,才开始写文件。

Step 2:写 SKILL.md(从薄到厚)

---
name: skill-name
description: >
  Load when [具体触发条件].
  Also load for [扩展场景].
depends: []
---

# Skill Name

## Overview
一句话概述这个 Skill 的职责。

## Core Instructions
- [指令1]
- [指令2]
- [指令3]

## Common Scenarios
### Scenario 1: [最常见场景]
[具体处理方式]

### Scenario 2: [次常见场景]
[具体处理方式]

## Gotchas
<!-- 从这里开始,随着失败案例增长 -->
- [已知失败模式1]
- [已知失败模式2]

## Reference Files
- `references/FORMATTING.md`: Load when generating formatted reports
- `references/edge-cases.md`: Load for unusual or borderline scenarios

Step 3:跑初始 Eval(建立基线)

# 在有任何真实用户数据之前,手动构建初始测试集
# 至少覆盖:
#   10个 正例(应触发)
#   10个 负例(不应触发)
#   5个  边界案例

baseline = suite.run()
print(f"初始基线 F1: {baseline['f1']:.2%}")
# 目标:初始就达到 F1 > 80%

Step 4:上线监控 + 追加 Gotcha

生产环境中的维护循环:

发现失败 -> 记录具体案例
           -> 追加到 SKILL.md 的 Gotcha 部分
           -> 重跑 eval,确认修复
           -> 检查是否影响其他 Skill(行动的距离效应)

Step 5:定期审计(防止 Skill 退化)

每季度检查:
  □ Precision/Recall 是否有下降趋势?
  □ 新增的 Skill 是否破坏了已有 Skill 的边界?
  □ Gotcha 列表是否过长?(过长说明核心指令需要重构)
  □ 辅助文件是否过大?(过大需要进一步拆分)

九、最容易忽视的警告:行动的距离效应

Perplexity 手册特别提到一个非直觉的警告:

很容易因为添加新 Skill 而破坏已有的 Skill,即便你没有改动它(注意行动的距离效应)。

这是因为所有 Skill 的 description 共享同一个 context,它们之间存在竞争关系:

示例:添加新 Skill 破坏旧 Skill

已有 Skill:
  us-tax-advisor:
    description: "Load when user asks about tax..."

新增 Skill:
  financial-planning:
    description: "Load when user asks about finances, 
                  tax strategies, investments..."
                  ↑ 这里包含了 "tax" 相关词汇!

后果:
  用户问 "如何优化我的税务负担?"
  本来应该路由到 us-tax-advisor
  现在 financial-planning 也可能被触发
  两个 Skill 同时加载,产生冲突或浪费 context

解决方案:
  新 Skill 的 description 要明确排除与已有 Skill 重叠的触发词
  添加新 Skill 后,对所有相关旧 Skill 重跑 eval

实践原则:

每次新增或修改任何 Skill 后:
  1. 先跑新 Skill 的 eval(确认它按预期工作)
  2. 再跑所有相关 Skill 的 eval(确认没有破坏已有边界)
  3. 特别关注 description 有词汇重叠的 Skill 组

十、完整 Skill 结构模板

这是根据 Perplexity 手册整理的可直接复用的模板:

目录结构

your-skill/
├── SKILL.md              <- 必须,hub 文件
├── scripts/
│   └── helper.py         <- 可选,可执行脚本
└── references/
    ├── FORMATTING.md     <- 格式规范(按需加载)
    ├── edge-cases.md     <- 边界案例(按需加载)
    └── domain-guide.md   <- 领域知识(按需加载)

SKILL.md 完整模板

---
name: your-skill-name
description: >
  Load when [主要触发场景:用 "Load when" 开头,枚举关键词].
  Also load for [扩展场景].
  NOT for [明确排除,避免与其他 Skill 冲突].
depends:
  - base-skill-name    # 可选,有依赖时填写
metadata:
  version: "1.0"
  owner: "your-team"
  eval_suite: "your-skill-eval-v1"
---

# Your Skill Name

## Overview
[一句话职责说明,不超过两行]

## Core Instructions
[核心操作指令,覆盖 80% 常规场景]

1. [指令1]
2. [指令2]
3. [指令3]

## Common Scenarios

### [场景1:最常见]
**触发词**:[用户可能使用的词语]
**处理方式**:[具体步骤]

### [场景2:次常见]
**触发词**:[用户可能使用的词语]
**处理方式**:[具体步骤]

## Gotchas
<!-- 每次发现失败案例时追加,不要修改核心指令 -->

- **[已知错误1]**:[描述模型容易犯的错误,提供正确做法]
- **[已知错误2]**:[描述模型容易犯的错误,提供正确做法]

## Reference Files
<!-- 告诉模型有哪些辅助文件,以及何时加载 -->

- `references/FORMATTING.md`:生成格式化报告时加载
- `references/edge-cases.md`:遇到边界案例时加载
- `references/domain-guide.md`:需要深入领域知识时加载

快速检查清单

发布前必须确认:

□ name 与目录名完全一致(全小写,连字符)
□ description 以 "Load when" 开头,而不是 "This Skill does"
□ description 包含用户常用的触发关键词
□ description 明确排除了与相邻 Skill 重叠的场景
□ SKILL.md 正文不超过 2000 tokens(超出部分移入 references/)
□ 条件性内容已移入 references/ 辅助文件
□ 至少有 5 条 Gotcha(来自真实测试)
□ 已构建初始 eval 集(10 正例 + 10 负例)
□ F1 > 80%(初始基线)
□ 已对所有相关 Skill 重跑 eval(确认没有副作用)

核心洞见:Agent Skill 开发的思维转变

Perplexity 这份手册最深刻的价值,是它揭示了一个思维方式的转变:

传统软件开发的思维:
  我写代码,代码按规则执行
  bug = 代码逻辑错误
  修复 = 修改代码

Agent Skill 开发的思维:
  我写指令,模型根据指令推理
  bug = 模型的推理偏差
  修复 = 给模型更好的信息(Gotcha + 边界条件)

关键认知:
  不要试图用更长的指令覆盖所有情况
  而是要构建"正例 + 反例"的完整知识体
  让模型从真实的成功和失败案例中学习

  就像培训一个新员工:
    不是给他一本厚厚的规则手册
    而是不断告诉他:"这种情况要这样做,上次那个错误不要再犯"

这也是为什么 Gotcha 列表比核心指令更有价值——它记录的是真实发生的失败,而不是设计者想象中可能发生的问题。


💬 你在构建 Agent 时用的是 MCP 工具调用还是 Skill 这种结构化指令集? 欢迎评论区分享经验!

原文地址:https://research.perplexity.ai/articles/designing-refining-and-maintaining-agent-skills-at-perplexity

🙏 如果这篇帮到你,一键三连(点赞 + 收藏 + 关注)!


参考资料

  1. Perplexity Research《Designing, Refining, and Maintaining Agent Skills at Perplexity》:https://research.perplexity.ai/articles/designing-refining-and-maintaining-agent-skills-at-perplexity
  2. Agent Skills 规范文档:https://agentskills.io/specification
  3. Microsoft Agent Skills Progressive Disclosure Pattern:https://deepwiki.com/microsoft/agent-skills/5.3-progressive-disclosure-pattern
  4. DEV Community《Building Agent Skills from Scratch》:https://dev.to/onlyoneaman/building-agent-skills-from-scratch-lbl

本文为原创技术解析,基于 Perplexity 官方文章。最后更新:2026-05-11

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐