skill 脚本
如何在你的技能中运行命令并捆绑可执行脚本。
技能可以指示代理运行 shell 命令,并将可复用脚本捆绑在 scripts/ 目录中。本指南涵盖一次性命令、自带依赖的自包含脚本,以及如何为代理使用设计脚本接口。
1. 一次性命令
当现有包已经能满足你的需求时,你可以直接在 SKILL.md 指令中引用它,而无需 scripts/ 目录。许多生态系统提供了在运行时自动解析依赖的工具,下面仅列举常用的三种,更多类别参见官网。
uvx ruff@0.8.0 check .uvx black@24.10.0 .- 不捆绑在 Python 中——需要单独安装。
- 速度快。积极缓存,重复运行几乎即时。
- pipx 在隔离环境中运行 Python 包。可通过操作系统包管理器获取(
apt install pipx,brew install pipx)。
pipx run 'black==24.10.0' .pipx run 'ruff==0.8.0' check .- 不捆绑在 Python 中——需要单独安装。
- 是
uvx的成熟替代品。虽然uvx已成为标准推荐,但pipx仍是一个可靠的选择,在操作系统包管理器中的可用性更广。
- npx 运行 npm 包,按需下载。它随 npm(随 Node.js 提供)一起提供。
npx eslint@9 --fix .npx create-vite@6 my-app- 捆绑在 Node.js 中——无需额外安装。
- 下载包、运行它,并缓存以供将来使用。
- 使用
npx package@version锁定版本以确保可重现性。
在技能中使用一次性命令的技巧:
- 锁定版本(例如
npx eslint@9.0.0),使命令在不同时间行为一致。 - 在
SKILL.md中说明前提条件(例如“需要 Node.js 18+”),而不是假定代理环境已具备。对于运行时级别的要求,使用compatibility前置元数据字段。 - 将复杂命令移至脚本中。 当你调用带有几个标志的工具时,一次性命令效果很好。当命令变得复杂到难以在第一次尝试时正确执行时,
scripts/中经过测试的脚本更加可靠。
2. 从 SKILL.md 引用脚本
使用从技能目录根目录开始的相对路径来引用捆绑文件。代理会自动解析这些路径——无需绝对路径。
在 SKILL.md 中列出可用脚本,以便代理知道它们的存在:
## 可用脚本
- **`scripts/validate.sh`** —— 验证配置文件- **`scripts/process.py`** —— 处理输入数据然后指示代理运行它们:
## 工作流
1. 运行验证脚本: ```bash bash scripts/validate.sh "$INPUT_FILE" ```
2. 处理结果: ```bash python3 scripts/process.py --input results.json ```相同的相对路径约定也适用于 references/*.md 等支持文件——脚本执行路径(在代码块中)是相对于技能目录根目录的,因为代理从那里运行命令。
3. 自包含脚本
当你需要可复用逻辑时,在 scripts/ 中捆绑一个脚本,并在脚本内联声明其依赖项。代理可以用单个命令运行该脚本——无需单独的清单文件或安装步骤。
多种语言支持内联依赖声明,以 Python 为例:
PEP 723 定义了内联脚本元数据的标准格式。在 # /// 标记内的 TOML 块中声明依赖项:
# /// script # dependencies = [ # "beautifulsoup4", # ] # ///
from bs4 import BeautifulSoup
html = '<html><body><h1>Welcome</h1><p class="info">This is a test.</p></body></html>' print(BeautifulSoup(html, "html.parser").select_one("p.info").get_text())使用 uv(推荐)运行:
uv run scripts/extract.pyuv run 会创建一个隔离环境,安装声明的依赖项,并运行脚本。pipx(pipx run scripts/extract.py)也支持 PEP 723。
- 使用 PEP 508 说明符锁定版本:
"beautifulsoup4>=4.12,<5"。 - 使用
requires-python约束 Python 版本。 - 使用
uv lock --script创建锁文件以实现完全可重现性。
4. 为代理使用设计脚本
当代理运行你的脚本时,它通过读取 stdout 和 stderr 来决定下一步做什么。一些设计选择能让脚本对代理来说更易于使用。
4.1 避免交互式提示
这是代理执行环境的硬性要求。代理在非交互式 shell 中操作——它们无法响应 TTY 提示、密码对话框或确认菜单。阻塞在交互式输入上的脚本会无限挂起。
通过命令行标志、环境变量或 stdin 接受所有输入:
# 错误:挂起等待输入$ python scripts/deploy.py目标环境:_
# 正确:清晰的错误并附带指导$ python scripts/deploy.py错误:--env 是必需的。选项:development, staging, production。用法:python scripts/deploy.py --env staging --tag v1.2.34.2 使用 --help 文档化用法
--help 输出是代理学习脚本接口的主要方式。包含简要描述、可用标志和使用示例:
Usage: scripts/process.py [OPTIONS] INPUT_FILE
处理输入数据并生成摘要报告。
Options: --format FORMAT 输出格式:json, csv, table(默认:json) --output FILE 将输出写入 FILE 而不是 stdout --verbose 将进度信息打印到 stderr
Examples: scripts/process.py data.csv scripts/process.py --format csv --output report.csv data.csv保持简洁——输出会进入代理的上下文窗口,与它正在处理的其他内容一起。
4.2.1 --help 文档化用法与 Docstring 的不同
问得很好!这个问题触及了 agent skill 设计的核心思维。虽然两者看起来都是”给脚本写说明”,但服务对象和使用方式完全不同:
核心区别:运行时 vs 源码级
--help 文档化用法 | Docstring("""……""") | |
|---|---|---|
| 获取方式 | 运行命令:python script.py --help | 读取源码文件内容 |
| 面向谁 | 使用者(人类终端用户 + AI agent) | 开发者(读代码的人) |
| 要什么能力 | 只需要能执行命令 | 需要能读文件 |
| 内容聚焦 | 接口契约:参数、选项、示例 | 实现说明:算法逻辑、设计意图 |
我们可以用一个 agent skill 场景来理解:
假设你的 vault 里有一个数据处理脚本,AI agent 需要调用它。两种方式的过程对比:
方式一:Agent 读 Docstring(读源码)
1. Agent 找到脚本文件路径2. Agent 用 read 工具读取整个文件3. Agent 在一堆 import、函数定义中翻找 """...""" 注释4. Agent 自己推断怎么调用→ 消耗大量上下文窗口,还要”猜”用法。
方式二:Agent 读 —help(运行时查询)
1. Agent 直接运行: python script.py --help2. 得到清晰简洁的: Usage: script.py [OPTIONS] INPUT Options: --format json|csv Examples: script.py data.csv→ 零猜测,所见即所得,上下文窗口高效。
一个直观的类比是:
Docstring 像产品说明书印在机器外壳背面——你得走到机器跟前仔细看。
--help像按一下机器上的”帮助”按钮——机器直接告诉你它能做什么。
对于 agent 来说,按按钮的成本远低于绕到背后读说明书。Agent 的默认行为是执行命令获取帮助,而不是打开源文件阅读。这就是为什么 skill 设计规范强调 --help 作为”主要接口发现机制”。
那 Docstring 扮演的角色
"""数据预处理模块 —— 负责清洗和标准化原始传感器数据。 ← Docstring:给读代码的人看实现基于滑动窗口的异常值检测算法(详见 RFC-42)。"""
import argparse
def main(): parser = argparse.ArgumentParser( description="处理输入数据并生成摘要报告。 ← --help:给使用者看 )简单原则:Docstring 写”为什么这么设计”,--help 写”怎么用”。Agent 只关心后者。
4.3 编写有用的错误消息
当代理收到错误时,消息内容直接影响其下一步尝试。含糊的 “错误:无效输入” 会浪费一次尝试。相反,说明哪里出错、期望什么以及可以尝试什么:
错误:--format 必须是以下之一:json, csv, table。 收到:"xml"4.4 使用结构化输出
优先使用结构化格式——JSON、CSV、TSV——而非自由格式文本。结构化格式可被代理和标准工具(jq、cut、awk)消费,使你的脚本在管道中具有可组合性。
# 空格对齐——难以程序化解析NAME STATUS CREATEDmy-service running 2025-01-15
# 带分隔符——明确的字段边界{"name": "my-service", "status": "running", "created": "2025-01-15"}将数据与诊断信息分离: 将结构化数据发送到 stdout,将进度消息、警告和其他诊断信息发送到 stderr。这让代理既能捕获干净、可解析的输出,又能在需要时访问诊断信息。
4.5 其他考虑
- 幂等性。 代理可能会重试命令。“如果不存在则创建”比“创建并在重复时失败”更安全。
- 输入约束。 以清晰的错误拒绝模糊输入,而不是猜测。尽可能使用枚举和封闭集合。
- 试运行支持。 对于破坏性或有状态的操作,
--dry-run标志让代理预览将要发生的事情。 - 有意义的退出码。 为不同的失败类型(未找到、无效参数、身份验证失败)使用不同的退出码,并在
--help输出中记录它们,以便代理了解每个代码的含义。 - 安全的默认值。 考虑破坏性操作是否需要明确的确认标志(
--confirm、--force)或其他适合风险级别的防护措施。 - 可预测的输出大小。 许多代理框架会自动截断超过阈值(例如 10-30K 字符)的工具输出,可能丢失关键信息。如果脚本可能产生大量输出,默认使用摘要或合理限制,并支持如
--offset之类的标志,以便代理在需要时可以请求更多信息。或者,如果输出较大且不适合分页,要求代理传递--output标志,指定输出文件或-以明确选择 stdout。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!