Perplexity Agent Skills 设计、优化与维护全流程深度解析

2026-05-11阅读 0热度 0
skill

在AI Agent的架构中,Skill(技能)正成为定义系统行为的关键单元。但若将其等同于传统软件开发,便已误入歧途。本质上,编写Skill是在为模型及其运行环境构建精确的上下文。它遵循一套独特的设计范式与约束条件,生硬套用编程思维只会导致低效甚至失败。

以Perplexity的先进Agent产品为例,其核心能力便构建于一套模块化的Agent Skills之上——将领域知识与操作流程封装为可调用的独立单元。Perplexity内部维护着一个经过严格筛选的Skill库,覆盖通用工具、金融分析、法律咨询、医疗诊断等众多垂直场景,甚至包含大量应对细分需求的微型模块。某些Skill可能日常使用频率不高,但在特定关键时刻却能成为解决问题的决定性因素。因此,Perplexity的Agents团队将Skill的质量标准提升至与生产代码同等的高度。

然而,构建高质量Skill所需的直觉和最佳实践,与常规软件开发大相径庭。团队在评审工程师提交的大量PR时发现,即便是资深开发者,其初版Skill也常需大幅重构,评审意见往往写满整页。根源在于:许多在编程中被奉为圭臬的“好习惯”,在Skill编写语境下反而成了需要规避的“反模式”。

一个鲜明的对比是PEP20——Python之禅。其中至少半数原则,在Skill设计的框架下要么不再适用,要么会引导开发者走向错误方向。随意对比几条,便能看出底层思维模式的根本性差异。

本文分享的指南,源自Perplexity内部工程师在开发与评审Skill过程中沉淀的实际文档。我们将其公开,旨在让这些实践中积累的经验与教训惠及更广泛的开发者群体。无论你是需要构建生产级Skill的工程师,还是希望在特定领域为Computer打造定制化能力的用户,这份指南都将提供切实的参考。

到底什么是Skill?

首先必须澄清,编写Skill并非传统意义上的软件开发。其本质是为模型及其运行环境构建精准的上下文。Skill有其内在的约束与设计逻辑,强行套用编码思维注定难以成功。

具体到Perplexity的实践,一个Skill至少承载着四重含义。

Skill是一个目录

Skill并非一个孤立的SKILL.md文件。在多数场景下,一个Skill是一个包含多文件的目录结构。在以Skill命名的目录下,通常包含:SKILL.md(内含frontmatter元数据与核心指令)、scripts/(存放Agent可直接调用的脚本,避免重复造轮子)、references/(存放体积较大的参考文档,支持按需加载)、assets/(存放模板、数据模式等资源文件),以及config.json(用于首次使用时的用户配置)。

这种“中心-辐条”式结构确保了Skill的高度聚焦与精简,其文件夹层级本身也能衍生出丰富的设计模式。对于特别复杂的Skill,采用多层嵌套的目录结构能有效辅助模型进行导航。试想,如果一个Skill涉及300个主题,归属于20个主要领域——即便对于当前最强大的前沿模型,让其从300个选项中一次性精准选择也极为困难;但如果先让其在20个领域中做出选择,再在该领域下的15个主题中进行筛选,任务难度便显著降低。

一个典型案例是今年美国报税季。Perplexity团队在为Computer构建美国所得税相关能力时,便采用了三层嵌套的目录结构。面对税法这类高度复杂的领域,这种层级划分几乎是刚需。在早期测试中,团队曾尝试将美国《国内税收法典》全部1,945个条款塞入单一文件夹,结果模型的表现甚至比完全不加载该Skill时更差。而按照逻辑重新切分为多层结构后,效果立竿见影。

当然,层级结构并非没有代价。层级越深,对信息架构的精细度要求就越高,否则容易在多层间接寻址中迷失方向。为此,团队专门设计了快速参考索引与定制化搜索工具,旨在让模型能以最少的“弯路”定位到所需信息。这些基础工作做扎实后,回报是显著的:模型在处理税务相关任务时,其表现远超单纯依赖通用工具的水平。

图片图片

Skill是一种格式

Skill同时也是一种格式约定。根目录下的SKILL.md文件必须包含name和description字段,且name必须与Skill所在目录名严格一致(全小写、无空格、可用连字符)。

其中,description字段是路由触发器,这也是一个常见误区。许多人将其写成“这个Skill是干什么的”的内部说明,这并不正确。它的真正作用是告知模型“何时应该加载这个Skill”。因此,地道的写法是“Load when...”(何时加载),而非“This Skill does...”(这个Skill做什么)。这一点至关重要,因为大多数实现都会将description直接注入模型的上下文。

frontmatter中还可包含depends:字段,用于构建Skill间的层级依赖关系;以及metadata:字段,主要用于评审与评估。不同的Agent系统也可自定义frontmatter字段以满足特定需求。当然,也存在另一种思路:将Skill专属的元数据放入单独的JSON或YAML配置文件中。这种做法适合那些希望Skill具备不同运行时行为,又不想让细节污染模型上下文的系统。另一种类似方案是在读取时剥离frontmatter。Computer采用了后一种方案——配置保留在SKILL.md中,但在解析时进行谨慎处理;如果某些字段对模型上下文有用而另一些无用,还可以进行条件性剥离。

Skill是可调用的

Skill是可调用的,Agent在运行时按需加载。关键在于:Skill的内容不会一次性全部塞入上下文。大多数Agent系统默认采用按需、渐进式展开的策略。

以Computer为例,Skill的加载流程大致如下:Computer先调用load_skill(name="..."),将Skill目录复制到隔离的执行沙箱中,然后递归加载depends:标签声明的依赖,最后剥离frontmatter,因此Agent最终看到的只有正文内容与附属文件。

不同的Agent系统可以用不同方式暴露Skill内容。有些系统选择完全不暴露文件层级结构,让模型自行通过文件系统操作去发现;有些则提供完整的文件树地图(可能带有截断或深度限制)。Computer的默认策略是不在调用上下文中展开完整文件层级,但这个行为可以按每个Skill单独配置覆盖。

Skill是渐进式的

Skill是渐进式的。在Computer中,上下文成本分为三层,每一层在不同阶段付出:

首先,Computer会构建一个Skill索引,其中包含每个可用Skill的name和description。这个索引的预算极其紧张——每个Skill大约100个token左右,越短越好。为何如此“抠门”?因为这项成本是每个会话、每个用户都必须承担的,它会被注入到对话最开始的系统提示词中。模型只有拿到这堆Skill的名字和描述,才能决定是否调用load_skill()。因此,能进入这个索引的门槛极高:你的Skill必须确实有用,并且描述必须做到极度精炼,因为所有用户都在为它“付费”。

当Skill真正被加载后,紧接着注入的是完整的SKILL.md正文。理想情况下,正文不应超过5,000个token。即便如此,也要力求字字珠玑——因为一旦加载,在触发上下文压缩之前,整个对话流程都将持续背负它的成本。许多对话线程会同时加载三到五个Skill,这个成本是叠加的。一个内容浮夸、废话连篇的Skill不仅自身效率低下,还会拖累其他Skill乃至整个Agent的能力。说白了:Skill加载了却没干对事,那就是纯粹的上下文浪费。

最后一层是脚本、子Skill、特殊格式说明等。这一层是存放无限制、有条件分支的复杂逻辑的地方。Agent只会在真正需要时才去读取,因此这里的内容门槛可以放低很多。简单总结:索引层的每个token都要斤斤计较;加载层可以稍微宽松;运行时层最为自由——可能塞入两万个token,也可能完全不用。正是运行时层,让模型上下文实现了真正的“渐进展开”。

什么时候才需要Skill?

Agents团队经常被问到:某个场景到底需不需要创建一个Skill?说实话,仅靠第一性原理很难给出确定答案。唯一可靠的方法是先在不带Skill的情况下运行,用几个具有代表性的核心查询(hero query)测试一下,看看Agent的实际表现究竟如何。

什么时候你需要一个Skill

许多任务本就位于训练好的模型能力分布之内。只有当你想以某种特定方式改变模型行为,而这种改变无法通过在prompt里加一句话来实现时,才需要考虑编写Skill。换句话说,当Agent在缺乏特定上下文时会犯错,或者其表现存在不一致、非确定性,而你需要在多次运行中保持高度稳定性时——这才是Skill该出场的时候。

另一种情况是知识本身很稳定但不在训练数据中:例如训练截止后才出现的新事物,或是企业内部专有的工作流程。有时甚至纯粹是“品味”问题。比如Computer中好几个与设计相关的Skill,就是由Perplexity的设计负责人Henry Modisett亲自编写的。这些Skill中的每一个token之所以存在,都源于Henry在UI和PDF设计上独到的审美判断——该用什么字体、不该用什么字体、字体给人的感觉如何。这些细微的品味,模型很难从海量训练数据中自行习得。

什么时候你不需要Skill

我们见过太多Skill里写着一长串git命令,并要求按顺序执行。这完全没有必要——模型本来就会做这件事。这类内容作为文档或许不错,但作为Skill就是垃圾。

也见过Skill在重复系统提示词里已经阐明的内容。这同样没必要。对于绝大多数请求都相关的通用知识,应该放在全局上下文里,而不是塞进按需加载的Skill中。

如果某个东西的变化速度比你维护的速度还快,也别把它做成Skill。例如,调用一个远端的MCP端点,其工具或工具版本频繁变动——千万别把这些细节硬编码进Skill,否则注定会出现版本漂移,模型也会随之犯错。

每一个Skill都是一份“税”

这里有一个非常实用的自检方法:对Skill里的每一句话问一句——“如果去掉这句话,Agent会写错吗?”如果这句话并非必不可少,那它就没有资格留在那里,因为每一次会话、每一个用户都在为它“付费”。在决定是否新增一个Skill之前,请牢记这个“税收”逻辑。

下面这句法语原话或许更能传达其中的精髓,大意是“我之所以把这封信写得这么长,是因为我没空把它写短”。

“« Je n'ai fait celle-ci plus longue que parce que je n'ai pas eu le loisir de la faire plus courte. » —— 帕斯卡,《致外省人书》,1657年

正如帕斯卡所言,每个Skill都需要花时间打磨。写一个简短的Skill远比想象中困难。如果你的Skill写得很轻松,那它很可能太长了,或者根本就不该存在。一个好的Skill,应该是被压缩到不能再短的状态。

如果想着一次性、五分钟就提交一个Skill的PR,结果几乎注定是平庸的。事实上,已有研究指出,让LLM自己为自己编写Skill,平均来看是无效的:“模型自己生成的Skill平均没有任何效果,这说明模型其实无法可靠地将那些它消费时受益的程序性知识自行书写出来。”

怎么写一个Skill

换句话说,你必须将自己的判断和品味注入到每一个编写的Skill中。具体步骤如下。

Step 0:先写Eval

第一步,先把评估用例(eval)写出来。评估用例可以从这几个地方挖掘:从生产环境或团队内部的核心智囊团中采样真实用户查询;之前因缺少该Skill而导致Agent失败的已知案例;以及那些贴近Skill边界、但本应路由到其他Skill的“邻近域混淆”案例。

至少要确保你测试的是:Skill在该加载的时候确实加载了。理想情况下,应从生产环境采样一些案例。已知的失败案例也极具价值:可能你编写这个Skill的初衷就是为了解决某个具体失败,或者是在重构两个职责相邻、容易互相串味的Skill。正面例子和反面例子都需要,并且要从相似的案例开始。反面例子的威力巨大,有时甚至比正面例子更重要。

Step 1:写Description

这是整个Skill中最难写的一行。它是一个路由触发器,不是文档说明。

要写对name和description,你不需要关心Skill内部具体做什么。你只需要关心这个Skill是否在该加载的时候加载、不会产生偏离目标的副作用——这是头号失败模式。每新增一个Skill,都有可能让其他每一个Skill略微变差,因此必须将回归风险压到最低。

再强调一次:差的描述写“这个Skill是干嘛的”或“这个Skill有什么用”。好的描述写“什么时候Agent应该加载它”。举个例子,假设有个Skill是用来盯PR进展的,不要写“这个Skill监控PR”,而要写工程师烦躁起来催进度时会蹦出来的话——比如“babysit”(盯着)、“watch CI”(看CI)、“make sure this lands”(确保它能合上)。

写description时有几个要点:用“Load when...”开头;控制在50词以内;描述用户的意图,最好直接来自真实查询;不要总结工作流程。真实查询能帮你做到80-20覆盖,通常两三个例子就足够了。要做到刚好够用、又一点不多余,这并不容易。

Step 2:写正文

接下来才是写Skill的正文内容。注意,这一步既不是Step 0也不是Step 1。

向LLM描述工作流,与向同事描述、向运行时系统描述,完全是两码事。一个工程师学习新软件工具,可能需要看文档、找有经验的人带、自己上手用一段时间。但对于已经存在至少一年的软件工具,提到它的名字,LLM该知道的都知道了。

写正文时,把那些显而易见的东西略掉。很多工程师写惯了readme.md,习惯把每一个命令一条条列出来。编写Skill时很容易退回那个模式——感觉像在写文档,但如果真这么干,写出来的Skill就是垃圾。所以,不要一条条列出命令。

举个反例,不要写:git log # 找到 commit; git checkout main; git checkout -b ; git cherry-pick ;

而要写:**“把这个commit cherry-pick到一个干净分支上。处理冲突时保留原意。如果合不进去,说明原因。”**

后者比前者好太多了,尤其是在出问题的时候。不要“railroad”(生硬规定每一步)——那样做既脆弱又僵化。要给模型留出灵活空间,让它在多种可行方案中选择。再说一遍:对人来说是好文档的东西,对模型来说常常是烂文档。

接下来的重点应放在“坑”(gotchas)和反面例子上。这部分内容信息密度极高,因为它告诉模型什么不能做。Agent每翻车一次就加一行,这个列表会自然而然地增长。最后,如果有任何条件性的、内容特别厚重的部分,把它从作为枢纽的SKILL.md里挪出去,放进辐条文件——也就是可以渐进加载的附属文件中。下一步会详细展开。

Step 3:用好层级结构

当需要用到脚本、参考文档或特定工具时,就该充分利用Skill的目录层级。scripts/里存放那些Agent每次都会重复“发明”的确定性逻辑,预先写好让它直接调用,而不是让它从头组装。references/里存放体量大、只在特定条件下才需要的文档,例如“如果API返回非200,去读api-errors.md”。assets/里存放Agent可以直接套用、按格式填充的输出模板,比如report-template.md、各种输出模式。config.json用于首次使用时的用户配置,例如询问Slack频道是哪个,并记录下来供下次使用。

凡是从主Skill分支出去的条件性内容,都应拆到子文件夹里。同时要记住前面提到的多层嵌套——对于特别复杂的Skill,需要认真权衡:到底是做成一个庞大的单体,还是拆分成多个Skill,用depends:串联起来更合适?

Step 4:迭代

接下来在分支上进行大量迭代。从主分支开始,先不带Skill跑几轮,建立好hero query集合,然后运行一大批eval。

评审这个Skill的同事会感谢你提交的是一个完整的变更集以及配套的eval集合。一个一个增量改动地进行评审非常痛苦(除了新增“坑”的情况),因此应尽量降低这种评审负担。

很多时候你会反复修改一些小的措辞。description中的小幅措辞调整,对路由的影响往往是巨大的(甚至可能溢出影响到其他Skill),因此这类工作必须在Step 5之前全部完成。

Step 5:发布

发布就完事了。

怎么维护一个Skill

写完一个Skill并非终点,后续的维护同样重要。

让Gotcha飞轮转起来

从这一步开始,“坑”(gotcha)列表往往会快速增长或变化。我们经常看到工程师提交未经eval的PR,甚至修改了description。Skill合并之后还在改description,那肯定是哪里出了问题。你要修改的是决定Skill是否被路由的那行字,这必须配套新的eval来支撑改动。

Skill的维护策略是“只增不删”。时间一长,最有价值的部分就是“坑”这一节。Agent在某件事上翻车了——加一条gotcha;Agent在不该加载的时候加载了这个Skill——收紧description,再加几条反面eval;Agent在该加载的时候没加载——增加关键词,加几条正面eval;系统提示词变了——检查是否有冲突或重复。

发现一个失败案例就加一条gotcha,这件事做起来很容易。它是反面例子,并不算修改显式指引,只是在告诉模型“嘿,这里有个已知的坑”。随着你从80-20的成功率向99.9%甚至99.99%推进,这个gotcha列表会增长得越来越快。看到反面例子,主要往gotcha章节里追加即可——不要动指令,不要动description。

Eval套件

Perplexity内部运行着多套eval来检查不同维度。

第一类是Skill加载和Skill文件读取。检查Skill自身加载行为的精确率、召回率以及禁加载校验:Agent在该路由的时候会路由到你的Skill吗?这类eval用于确保新Skill不会破坏既有的边界划分。

第二类检查渐进加载是否正确。Agent加载了Skill没错,但它有没有去读那些附属文件?比如金融Skill在处理金融查询时,到底有没有去读FORMATTING.md

第三类是域内端到端任务完成度。跑完整个Agent循环,然后用LLM作为裁判,按照一套定义清晰的评分标准给结果打分。

最后非常重要的一点:这些eval必须在不同模型上运行。Computer至少支持三种编排模型家族——GPT、Claude Opus、Claude Sonnet。Skill加载和域内Skill都要跨这些不同的Agent编排器进行验证,以确保行为一致。Sonnet和GPT在处理Skill时的行为差异其实相当大。

最后的总结

写得越多,自然就越熟练。如果你日常做的、可重复的任务还没有用Skill自动化起来,那么现在就可以开始了。

编写Skill本身就是一种自我训练,越写越会写。而且Skill在自动化业务流程上特别管用。如果你能描述清楚自己每周站会前要做什么、每个冲刺(sprint)结束要做什么,或者作为工程师每天、每周乃至每个季度要重复做的事情——这些都应该写成Skill,把时间“买”回来。复盘自动化、PR审查……任何能描述的任务,至少可以让Skill先做个初版,能省下来的时间真的不少。

话说回来,也别走极端——Skill不是写起来轻松的东西,也不是任何场景都需要Skill。少即是多。最后再敲定三个核心要点:

先写eval再写Skill。包含反面例子,以及那些贴近但不该路由过来的Skill的“禁止加载”校验。

description才是最难的部分。用“Load when...”开头,每个词都在抢夺注意力。

Gotcha是高价值内容。一开始就写薄一点,随着Agent翻车再慢慢让它生长出来。

还有一点千万记住:新增一个Skill很容易把其他本来运行良好的Skill搞坏,哪怕你没动它一行代码——这就是所谓的“超距作用”,需要时刻警惕。

无论是编写Skill还是维护Skill,把手边能用的工具都用起来。如果想深入了解,Agent Skills领域有很多优秀案例,Perplexity的内部仓库和公开生态里也有大量值得参考的Skill设计。

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策