ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI Agent Skills 深度拆解:从提示词工程到确定性任务执行的进化

AI Agent Skills 深度拆解:从提示词工程到确定性任务执行的进化

我最早接触 Skills 这个词,是因为项目的 issue 区里有人吐槽:"把提示词封装一下就叫 Skill,这跟之前的 Prompt 工程有什么本质区别?"当时我也抱着同样的怀疑,直到自己动手把一个高频重复的数据处理流程真正做成了 agent skills 之后,才发现这东西不是给提示词换个名字那么简单——它改变了 Agent 处理任务的确定性。这篇内容我不打算复述官方文档,而是从第一性原理出发,把 Skills 的构造、开发过程、安装分发、框架编排位置和安全隐患讲清楚,适合正在搞 agent 开发、被 Skills 概念绕晕、或者已经在用但想优化命中率的人。

1. Skills 到底是什么:一个被频繁误读的概念

1.1 从"模型会"到"模型能"的最后一公里

大模型本身"会"很多东西,但"会"和"能"之间隔着一层执行能力。你让模型写一段 Python 代码,它能写得很像样;但让它"清洗这个 CSV、去重、算一下各分类占比、最后按固定格式输出周报",模型每次都可能在步骤顺序、字段映射、异常处理上发挥得不一样。区别在哪?区别在于缺了一套固定的操作流程。

Skills 解决的就是这个问题。它把"完成某类任务所需的全部知识"打包成一个结构化的单元:模型在什么情况下激活它、按什么步骤操作、调用哪些脚本、依赖哪些资源、遇到边界情况怎么处理。这个单元不是单纯的知识灌输,而是带脚手架的流程约束。我的理解是,Skill = 细化的操作指令 + 可执行脚本 + 触发条件 + 结果校验方式,四者缺一不可。

1.2 Skills、Tools、Agent 三者边界

很多文章把 Skills 和 Tools 混着讲,实际粒度完全不同。Tools 是 Agent 能调用的最小功能单元,比如一个"执行 Python 代码"的工具、一个"访问某 API"的工具;Skill 则是面向某个任务场景的完整方案,它内部会编排多个工具调用和脚本执行。

打个比方:Tools 是螺丝刀、扳手、电钻,Agent 是工人,而 Skill 是一份"安装宜家柜子"的说明书加工具清单。工人有工具箱不代表会组装柜子,说明书本身也不执行任何操作,但它指导工人按顺序使用哪些工具、零件怎么匹配。Agent 负责理解说明书后进行推理,Skill 负责提供说明书和必要的脚本资源。至于 Harness,我理解它是整个 Agent 的调度壳,负责循环、上下文管理、工具调用回路的控制,Skill 则像插在 Harness 上的功能模块,两个层面互相补充而不是竞争关系。

1.3 一个 Skill 的典型生命周期

从我实际开发的经验来看,一个 Skill 从诞生到退役大致经过这么几个阶段:

  • 需求识别:发现某类任务重复出现,且每次都要手写流程
  • 原型验证:先手工写脚本 + 手动跑通,确认流程确定可行
  • 封装成型:整理成 SKILL.md + scripts 结构
  • 安装测试:放入 Agent 的 skills 目录,模拟真实对话验证激活率
  • 迭代优化:根据失败案例修正描述和指令
  • 分发复用:上传到团队仓库或个人维护的 Skills 市场

这套流程看起来很简单,但每一步都有坑。后面我逐个章节拆开讲。

2. Skill 文件拆解:看懂一个 Skill 的内部构造

2.1 SKILL.md 的结构与心智模型

官方 Skill 机制虽然各家实现略有差异,但核心骨架基本一致:一个以 Skill 命名的目录,目录里有一个 SKILL.md 作为入口,配合脚本资源和静态文件。SKILL.md 最关键的头部是一段 YAML frontmatter,我用一个实际示例来说明:

--- name: csv-clean-summarize description: 将用户提供的CSV文件进行数据清洗(去重、去空值、格式归一),并按指定维度做统计分析,最终生成Markdown格式摘要报告。当用户要求处理CSV或表格数据、整理报表时使用。 allowed-tools: bash, python metadata: version: 0.1.0 author: your-name ---

frontmatter 里的每一项都对模型的决策有直接作用。name是 Skill 的唯一标识;description是模型决定"是否激活这个 Skill"的核心依据;allowed-tools限制了 Skill 内脚本运行时可以调用的工具范围;metadata是给人和分发系统用的版本信息。

很多人忽略 description 的重要性,这是我踩过的最大的坑。模型选择 Skill 的方式本质上是一个检索匹配过程:它会拿用户当前任务与所有已安装的 description 做语义匹配。写得太宽泛,比如"处理数据",模型可能在任何涉及数据的场景都尝试调用;写得太窄,比如只写了"清洗股票行情CSV",那用户换个贸易数据文件它就识别不出来。我现在的写法是"触发场景 + 数据形态 + 输出格式 + 使用条件"四要素齐备。

2.2 脚本、资源与依赖的组织方式

SKILL.md 只是入口说明,真正的逻辑能力在 scripts 目录。结构通常长这样:

csv-clean-summarize/ ├── SKILL.md ├── scripts/ │ ├── clean_csv.py │ ├── summarize.py │ └── requirements.txt └── assets/ └── sample_template.csv

脚本与说明分离有一个很明显的好处:模型在决策时只需要读取 SKILL.md 的简短说明,不需要把几百行 Python 源码塞进上下文。只有当 Skill 被激活执行时,Agent 才通过 allowed-tools 里的执行工具去实际调用脚本。这种按需加载的设计,对 token 消耗和响应速度的改善是立竿见影的。

依赖管理我建议单独维护 requirements.txt,而不要假设目标环境里已经装了所有库。因为 Skill 会被不同的 Agent 运行环境加载,今天在这个机器上跑,明天可能换到另一个容器里,依赖写清楚能让使用者少折腾一小时环境问题。

2.3 一个从零到可运行的 Skill 骨架

我们继续用 CSV 清洗这个例子,完整的 SKILL.md 正文我通常写成这样:

# CSV 数据处理任务 ## 目标 - 使用 clean_csv.py 清洗输入 CSV 文件 - 使用 summarize.py 生成统计摘要 ## 步骤 1. 检查输入文件是否存在,不存在则明确告知用户 2. 执行 python scripts/clean_csv.py --input <file> --output /tmp/cleaned.csv 3. 检查脚本输出,若有错误记录则按错误提示修正后重试 4. 执行 python scripts/summarize.py --input /tmp/cleaned.csv --group-by 用户填写的分组字段 5. 将 summarize.py 输出的 Markdown 报告返回给用户 ## 注意事项 - 如果输入文件编码不是 UTF-8,先转码再处理 - 如果 CSV 完全没有数据,不要执行统计步骤,直接说明文件为空

指令必须写成模型能够稳定复述并执行的顺序化语言,而不是写成给人类看的技术文档。关键在于"所有可能出错的分支都要有对应的处理指令"。比如脚本执行失败怎么办、输出为空怎么办、用户没给全参数怎么办,这些分支如果不在 SKILL.md 里写清楚,模型就会用通用推理方式自由发挥,那 Skill 的确定性就丢失了。

3. 开发一个真实 Skill 的完整过程

3.1 选型:什么样的任务适合做成 Skill

不是所有任务都适合封装成 Skill。我给自己定了几条筛选标准:

  • 任务过去一个月内出现了至少三次
  • 任务的执行步骤相对固定,不需要太多主观判断
  • 可以拆出至少一段独立的脚本逻辑
  • 错误处理规则可以被明确枚举

满足这些条件,封装成 Skill 的收益才足够大。反过来,如果任务几乎每次都不同、高度依赖灵感和上下文,硬要做成 Skill 反而会束缚模型。比如"帮我构思一个产品名"这种任务就不适合,它的输出开放度太高,做进去只会破坏灵活性。

3.2 先手工跑通,再封装成 Skill

我强烈建议顺序是:先写脚本、手工测试、确认输出稳定,再回头写 SKILL.md 指令。很多人反过来,先写了一大段描述性指令,脚本草草写两行,然后整个 Skill 跑起来到处报错,最后很难分辨到底是指令写得不对还是脚本本身有 bug。

还是拿 CSV 清洗来举例。我先写一个 clean_csv.py 脚本,用 pandas 读取输入文件,删除完全重复的行、丢弃关键字段为空的行、把日期列统一成 YYYY-MM-DD 格式,输出到指定路径:

#!/usr/bin/env python3 import argparse import pandas as pd def main(): parser = argparse.ArgumentParser() parser.add_argument("--input", required=True) parser.add_argument("--output", required=True) args = parser.parse_args() df = pd.read_csv(args.input) before = len(df) df = df.drop_duplicates() df = df.dropna(subset=["date", "amount"]) df["date"] = pd.to_datetime(df["date"]).dt.strftime("%Y-%m-%d") df.to_csv(args.output, index=False) print(f"[INFO] {before} -> {len(df)} rows after cleaning") if __name__ == "__main__": main()

手工跑通几轮,确认空文件、缺列、编码异常这些边界情况都有明确输出,然后再把它写进 SKILL.md。这样调试时如果出错,就能快速定位是"指令理解错了"还是"脚本本身 bug",排查半径小很多。

3.3 测试与命中率调优

Skill 封装完成后的测试,我一般会做两轮。第一轮是单测脚本,直接在命令行跑,验证脚本在各种边界输入下的行为;第二轮是在 Agent 对话里做模拟任务测试,观察模型是否会主动选中这个 Skill,以及选中后是否正确按 SKILL.md 的步骤执行。

我习惯准备三个测试用例:一个任务表述与 description 高度匹配的用例,一个语义相关但用词不同的用例,一个完全不相关但可能出现误触发风险的用例。比如对 CSV 清洗 Skill:

  • "帮我洗一下这份订单数据"(应触发)
  • "把这个表格整理成报告"(应触发,因为语义包含表格数据)
  • "帮我把这段文字翻译成英文"(不应触发)

如果第三个用例误触发了,说明 description 太宽,需要加排除条件。我会在 description 末尾加一句"仅适用于表格或 CSV 文件数据,不处理纯文本",再重新跑测试。这一步急不得,我见过太多人花两小时写 Skill,却不肯再花二十分钟做命中率调优,结果装上去之后模型完全拿它当摆设。

4. 安装、分发与市场生态

4.1 官方市场与第三方来源的现状

几乎每个主流 Agent 平台都在补 Skills 生态,有的叫 marketplace,有的直接叫 skills 目录。官方市场的好处是经过了基础审核,安装路径统一,更新也比较及时;但官方市场的数量永远赶不上社区的需求,所以大量 Skill 还是通过 GitHub 仓库、个人博客、第三方集合站来分发。

我自己安装 Skill 的习惯是:能走官方市场就走官方市场;社区来源的先拉代码审查一遍再放进环境。关于"Skills 下载平台有哪些"这个问题,我现在的答案只有一个标准:即 GitHub 上活跃维护的集合仓库,以及官方内置的市场。遇到那种需要注册、签到、付费下载的所谓平台,我一概不信,这类渠道的 Skill 质量参差不齐,还容易把依赖打包得乱七八糟。

4.2 手动安装与版本管理

不同 Agent 的安装位置不太一样,但大逻辑是共通的:要么在用户级目录统一存放(如~/.claude/skills),要么放在项目目录下的.claude/skills中跟随项目一起走。用户级的好处是所有项目都能用,项目级的好处是权限收敛、不会污染其他项目。我个人的建议是新项目优先用项目级,等验证稳定了再提升到用户级。

版本管理方面,我会为每个 Skill 单独建 git 仓库,打 tag 标识版本,在主仓库中用 git submodule 或依赖清单引用。这样改坏了可以快速回滚,升级时也清楚对比 diff。不要天真地以为 Skill 很小就随手拷贝,等它在五个项目里各自拷贝了一份,你就会发现每次改 bug 都是一场灾难。

安装完成之后的验证步骤不可省略:启动 Agent 后直接问一句"你现在有哪些 skill 可用",看目标 Skill 是否出现在清单里。有些 Agent 需要重启进程才会重新扫描 skills 目录,如果你装完看不到新 Skill,先检查目录权限,再检查 YAML frontmatter 是否有解析错误。

4.3 Skill 选型的实用标准

面对一堆 Skill 集合,什么样的值得装?我总结了一套五条过滤标准:

判断维度合格标准
源码透明度所有脚本可以在安装前完整审阅,没有混淆或二进制黑盒
依赖克制尽量少依赖重型框架,requirements 明确且版本锁定
文档完整度SKILL.md 写清了步骤、边界情况和输出格式
维护活跃度最近六个月内还有提交或 issue 响应
权限边界脚本内不包含 sudo、不读取敏感环境变量、无外发网络请求

如果一个 Skill 连前两条都不满足,就算功能再吸引人我也会放弃。装一个来源不明的 Skill 等于把一个第三方脚本放进了你的 Agent 执行环境,还给了它调用 bash 和 python 的权限,这个信任成本是巨大的。

5. Agent 框架中 Skills 的编排位置:以 Rust 生态为例

5.1 框架层面的 Skill 加载机制

近期社区对 Rust 语言写 AI Agent 的热情明显在涨,我也用 Rust 重构过一个内部 Agent 工具。Rust 生态里典型的做法是不把 Skill 逻辑编译进二进制,而是做成运行时动态加载的外部资源目录。核心思路是用一个 trait 定义 Skill 接口:

pub trait Skill: Send + Sync { fn name(&self) -> &str; fn description(&self) -> &str; fn execute(&self, context: &mut AgentContext, args: serde_json::Value) -> Result<serde_json::Value, AgentError>; }

然后通过一个注册表在启动时扫描指定目录,把 SKILL.md 的name和description解析出来,注入模型系统提示词。执行时再根据用户任务的语义选择对应的 trait 对象调用。这种运行期动态注册的方式,好处是新增或更新 Skill 不需要重新编译整个 Agent,跟脚本语言的灵活性站在了同一条起跑线上。

5.2 多 Agent 协作时的 Skill 共享与隔离

热词里"多 agent"出现频率很高,这里说一个容易被忽略的边界。多个 Agent 可以共享同一个 Skill 目录,但一定不要共享同一个执行环境。比如"A 负责数据分析,B 负责生成报告",两个 Agent 可能都会用 CSV 清洗 Skill,但 B 应该只读取产出的清洗结果,而不应该直接获得清洗脚本所在环境的 bash 权限。

更稳妥的实践是:公共 Skill 放在统一的只读目录,按 Agent 角色拆分可执行权限。Rust 里做权限收敛很舒服,因为编译期就能把每个 Agent 能用哪些 Skill 的配置写死,一个 Agent 永远无法加载未被授权的 Skill 模块。这个"静态编译期约束 + 运行时动态加载"的组合,是我目前觉得综合体验最好的方案。

5.3 Skills 与 Memory、Token、上下文预算的关系

Skill 的文件内容不可能也不应该全部常驻在上下文中。我的经验是,模型系统提示词里只放所有 Skill 的简短 description 列表(比如每个一行),完整 SKILL.md 按需加载。这样即使装了 50 个 Skill,上下文也只被消耗了几个 KB,而不是把几十个脚本全部塞进去。

Memory 则负责保存任务执行过程中的中间结论。比如清洗脚本的输出路径、统计结果的关键数字,这些应该写进 Agent 的短期记忆,供后续任务直接引用,而不是让模型每次重新推理一遍。Skill 负责"怎么做",Memory 负责"上次做到哪了",两者配合才能让 Agent 在多轮复杂任务中不迷路。

6. 安全和边界:Skills 带来的新攻击面

6.1 不可信 Skill 的供应链风险

Skills 的普及带来一个新的攻击面:技能生态供应链。开发者在 Skill 里写一段"清理临时文件"的脚本,可能在正常逻辑之外悄悄读取~/.ssh下的密钥,或者通过外部请求把环境变量传走。这些问题不是靠 Agent 平台层能完全防住的,因为最终执行脚本时拿到的是 Agent 进程的权限。

我在前面选型标准里反复强调源码透明,就是因为这个。一个 Skill 安装到本地后,应该有能力和意愿把所有脚本从头到尾审一遍。要求不高,至少要看清楚每个 shell 命令、每个网络请求、每处文件读写路径。如果你看不太懂代码,那就不要安装需要高权限的 Skill,或者把它跑在专门的容器环境里。

6.2 权限收敛与沙箱执行

我现在的安全习惯是三层收敛:

  • 第一层:不允许 Skill 内出现 sudo,进程投递时检测并拒绝
  • 第二层:Skill 目录挂在只读挂载点,脚本想改自身就会被拒绝
  • 第三层:涉及网络请求的 Skill 强制代理到审计网关,所有外发流量留日志

用 Rust 生态做 Agent 的话,还可以借用编译期静态分析工具扫描脚本中的高风险系统调用,在加载阶段直接拦截,而不是等执行到那一步再报错。实测下来,这套组合拳能挡住大部分粗糙的攻击脚本,至少它们没办法在眼皮底下偷数据。

6.3 对灰色 Skill 的识别原则

现在市面上出现了一些号称"自动挖洞""一键生成报告"之类的 Skill。这类技能本身未必违法,自动化安全检查也是正当需求,但你要清楚边界:扫描和检测行为只能在你有授权的目标上执行,Skill 包装的命令如果含暴力枚举、边界外的主动探测,等同把风险引入你自己的运行环境。

我的处理原则很简单:涉及攻击面操作的 Skill 一律在隔离容器或一次性虚拟机里运行,容器内不给宿主卷挂载、不给真实凭据、网络走白名单代理。同时认真阅读 Skill 的脚本内容,弄清楚它到底执行了什么,而不是被名称和宣传语带着走。安全不该靠信任,只能靠验证。

最后再分享一个小技巧:我在每次调试 SKILL.md 时,会把模型实际生成的执行计划打印出来。如果模型激活了错误的 Skill 或者步骤顺序和你预期的不一致,不要急着改脚本,先回去检查描述文本——大概率是"什么条件下使用"这一段写得不够精确。把触发条件写细、写准,比堆砌更多脚本指令更能提升整个 Agent 系统的稳定度。Skills 这个东西,封装容易,调好难,花在描述上的功夫永远不会白费。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进