案例研读 — Perplexity

Perplexity 内部 skill 工程方法研读:上下文工程视角、四组件模型、三层成本、eval-first 流程、gotchas 飞轮

  • 原文: Perplexity 内部文章 Designing, refining, and maintaining agent skills at Perplexity,基于其 agent 产品 Computer 的实战经验。
  • 核心主张: 写 skill 不是写软件,而是为模型和它的执行环境构建上下文。
  • 本页: 按原文章节顺序逐节研读,关键概念配图,文末单独一节给出我们的总结。

Opening:写 skill 是上下文工程,不是软件工程

文章开篇就立起的基础主张:写 skill 不是在写普通软件,而是在为模型和它的执行环境构建上下文。Skill 有自己的约束条件和自己的设计原则,大多数代码 review 的反射在这里都不适用。

把这条主张落地的方式,是把 PEP 20 做并列反转:

PEP 20Skill 工程版本
Simple is better than complexComplexity is the feature
Explicit is better than implicitActivation is implicit pattern matching
Sparse is better than denseContext is expensive; maximum signal per token
Special cases aren’t special enoughGotchas ARE the special cases
If it’s easy to explain, it’s a good ideaIf it’s easy to explain, the model already knows it; delete it

紧跟着的观察:工程师按写代码的反射写 skill,提交的 PR 读起来像内部文档,reviewer 的大部分精力花在把它砍短。

Skill 是什么?

文章把 skill 拆为四个正交的性质。

Skill 是一个目录

不是一个文件。典型布局:

  • SKILL.md:frontmatter + 指令
  • scripts/:可执行代码,让 agent 直接调用而不是重新构造
  • references/:条件性加载的重型文档
  • assets/:模板、schema、数据
  • config.json:首次运行的用户配置(例如团队特有的 Slack 频道映射)

目录的内部组织本身就是一个 skill 工程问题。文章给出的案例:

1,945 节税法 skill:扁平 vs 层级 Perplexity Computer 重构前后对比 扁平 — 单一目录 1,945 所有 IRC 条款,全在一个文件夹 无内部索引、无搜索工具 结果 表现比"完全不加载这个 skill"还差 层级 — 三级结构 L1 — 约 20 个大类 (categories) L2 — 每类约 15 个 topic cluster L3 — 具体条款内容 结果 每深一层,路由可靠性明显提升

Perplexity 为 Computer 做了一份覆盖 Internal Revenue Code 全部 1,945 节的税法 skill。第一版把所有条款扔进单一目录,表现比根本不加载这个 skill 还差。修复方案是三级层次结构:约 20 个大类,每类下约 15 个 topic cluster,cluster 下是具体条款,配套自定义搜索工具和快速参考指南。每深一层,路由可靠性都有明显提升。对密集参考材料而言,进入这份材料的索引本身就是 skill 工程问题。

Skill 是一种格式

Frontmatter 必含 name(小写,可用连字符)和 description。文章的核心提法:description 是路由触发器,不是文档,措辞应当是 Load when ... 而不是 This skill does ...。可选字段包括 depends:(层级依赖)和 metadata: 块。送入模型前是否剥掉 frontmatter 由实现决定,Computer 选择剥掉。

Skill 是可调用的

Computer 的流程:agent 调用 load_skill(name="..."),skill 目录被复制到隔离的 sandbox,递归自动加载 depends: 声明的依赖,剥掉 frontmatter 后再送入模型。

调用过程跨越三层成本:

三层上下文成本 (Three-tier context cost) 宽度对应付费频率;单次成本随宽度收窄而上升 Index 层 所有可见 skill 的 name + description ~100 tok / skill 每个 session、每个用户、永远在付 Load 层 完整 SKILL.md body ~5,000 tok 每次 load_skill 付一次,留到 compaction Runtime 层 scripts / references / assets / sub-skills 无上限 仅当显式打开文件时 Computer 报告的稳态:每个 thread 加载 3–5 个 skill

  • Index 层:约 100 tokens / skill。所有非隐藏 skill 的 name + description 在对话开始时注入 system prompt。每个 session、每个用户、永远在付。
  • Load 层:约 5,000 tokens。完整的 SKILL.md body。load_skill 调用后注入,持续到下一个 compaction 边界。多个 skill 累加;报告的稳态是每个 thread 加载 3–5 个 skill。
  • Runtime 层:无上限。scripts/、特殊情况文件、子 skill、格式化指南。仅当 agent 主动打开具体文件时才付费。

这三层是所有下游设计取舍的物理基础:description 的每个字节在每次调用都付费;body 的每个字节在加载后的整个 thread 里挂着;只有 runtime 层是真正按需的。

Skill 是渐进式的

昂贵内容的加载推迟到真正需要的那一刻。这是为什么三层模型在几十个 skill 的目录里在经济上仍然成立。

什么时候需要做 skill?

该做 skill 的场景

文章给出的判据:任务需要的行为改变超出了单条 prompt 指令能覆盖的范围;agent 在缺少专门上下文时会失败或表现不稳定;知识是稳定的,但在训练数据里缺席(截止日之后的内容、企业内部 workflow、品味和判断这类内容)。

文章给出的例子:Perplexity 的 Henry Modisett 撰写的 design skill,规定字体选择、视觉美学、设计原则。这是基于品味与判断的知识,模型从训练数据里推不出来,属于“必须做成 skill”的典型例子。

不该做 skill 的场景

三种情况:

  1. 模型本来就会。常见命令序列是经典反例。文中的对照:
    • 不好的写法:把 git log; git checkout main; git checkout -b <clean>; git cherry-pick <commit> 完整命令序列写进 SKILL.md
    • 好的写法:“把 commit cherry-pick 到一个干净 branch 上,解决冲突时保留原意”
  2. 属于普适指令。应该放进 global system prompt,不该做成有条件加载的 skill。
  3. 内容变化快于维护节奏。频繁变动的远端 endpoint 是文章给的例子。一份过期的 skill 比“没有这个 skill”更糟,它会自信地路由进去然后给出错误信息。

核心原则:Every skill is a tax

文章给出的核心纪律。问法是:skill 里任意一句话,没有它 agent 会不会答错?如果不会,这句话就不该存在。

简短被刻画为劳动密集的工艺,借用了帕斯卡那句“这封信写得这么长,因为没时间写得更短”的旧典作为背书。

另引一项研究结论:让 LLM 自己写 skill 平均没有正向收益(“no benefit on average”)。模型可以可靠地消费程序性知识,但写不可靠。

如何构建一个 skill

文章给出六步。

Step 0:先写 eval。 来源:真实生产 query;已知失败案例(推动你做这个 skill 的那个 case);以及边界邻近的、应该路由到别的 skill 的 query。负样本权重高于正样本。

Step 1:Description(最难的一步)。 先把路由触发拿对,再写任何 workflow。文章给的四条清单:

  1. Load when ... 开头
  2. 目标 50 个英文单词以内
  3. 描述用户意图,措辞来自 2–3 条真实生产 query
  4. 不要总结 workflow,那是 body 的事

文中的例子:一个 PR Monitoring skill 的 description 用的是真实用户语言:“babysit”、“watch CI”、“make sure this lands”,而不是功能化的 “monitor pull request status”。前者匹配用户真实的措辞,后者只匹配作者脑补的措辞。

Step 2:写 body。 复述开篇的原则:把一个 workflow 传达给 LLM,和把它传达给同事、甚至传达给运行时系统,是完全不同性质的事。跳过模型已会的内容;不要给一串规定动作的命令序列;火力集中在 gotchas 和负样本;条件性、重型的内容下沉到附属文件。

Step 3:用上层次结构。 把内容分布到 scripts/(“给它代码去组合,不是重新构造”)、references/(触发式加载,例如“API 返回非 200 时去读 api-errors.md”)、assets/(输出模板)、config.json(首次运行配置)。

Step 4:在 branch 上迭代。 Description 里的小词替换都会带来明显的路由偏移。提交时是带完整 eval set 的一个完整 changeset,不做反复来回 review。

Step 5:上线。

如何维护一个 skill

Gotchas 飞轮

长期来看,gotchas 是 skill 里价值密度最高的部分。维护的常态是只增不改:

Gotchas 飞轮 生产观察追加到 SKILL.md,SKILL.md 又喂给下一轮生产 生产观察 (production observations) 生产中 agent 出错 追加一条 gotcha 加载了错误的 skill 收紧 description, 加一条负 eval 该加载的 skill 未加载 补关键词, 加一条正 eval System prompt 变更 审查重复 / 抢夺 SKILL.md 只增不改的累积器 喂给下一轮 稳态:SKILL.md 缓慢增长,累积的是负向知识

  • 生产中 agent 出错,追加一条 gotcha
  • 加载了错误的 skill,收紧 description,加一条负 eval
  • 该加载的 skill 没加载,补关键词,加一条正 eval
  • System prompt 变更,审查与现有 skill 的重复或抢夺

稳态下 SKILL.md 增长缓慢且不对称,累积的是负向知识

Eval 套件:四类

Perplexity 报告同时跑:

  1. Skill 加载精度与召回,含“禁止加载”的负样本检查
  2. 渐进加载 eval:body 指示 agent 去读 FORMATTING.md 这类附属文件时,是否真的读了
  3. 端到端域内完成,由 LLM judge 按 rubric 评分
  4. 多模型 eval:GPT、Claude Opus、Claude Sonnet 各跑一遍。文中给出的具体观察:“Sonnet 和 GPT 在 skill 这件事上行为差异不小”

Action at a distance(远程副作用)

加一个新 skill 会拖垮已有 skill,因为新的 description 会争抢同样的路由注意力。这是 eval 套件必须目录级(catalog-wide)而非 skill 级(skill-local)的最有力理由:真正在乎的回归是“加了 skill X 之后,skill Y 会不会被误路由”。


我们的总结

读完这篇文章值得带走的六条。这是我们的提炼,不是原文的措辞:

  1. 写 skill 是为模型和它的执行环境做上下文工程,不是软件工程。PEP 20 的反转、“Every skill is a tax”的纪律、不要写规定动作命令序列的反模式,都是从这一条主张延伸出来的。
  2. Skill 目录与其说是知识库,不如说是路由系统。成本在 index 上付,信号在 description 上发,失败在 gotchas 里累积,回归在 skill 与 skill 之间冒出来。
  3. Description 是最难写的部分。一个英文单词的替换都可能改变路由结果。
  4. Gotchas 飞轮决定 skill 的长期价值。稳态是缓慢累积负向知识,不是前期设计。
  5. Eval 必须先写、必须覆盖负样本、必须跨模型、必须目录级。
  6. 不是所有看上去该做的事都该做 skill。模型已知、变化过快、普适指令,都该排除。

原文链接

Designing, refining, and maintaining agent skills at Perplexity — Perplexity Research

这页有帮助吗?