Skills 脚本扩展
除了在 SKILL.md 中编写指令外,你还可以通过 scripts 目录为技能添加可执行代码。
本节将教你如何使用脚本来扩展技能的功能。
为什么使用脚本
使用脚本可以:
- 封装复杂的逻辑,避免在 SKILL.md 中写大量代码
- 提供经过测试的可靠实现
- 让代理可以重复使用相同的工具和函数
- 处理更复杂的输入输出操作
提示:当你在多次执行中发现代理重复实现相同逻辑时,这往往是创建脚本的好时机。
脚本目录结构
脚本应该放在技能目录的 scripts/ 子目录下:
目录结构
├── SKILL.md
└── scripts/
├── script1.py
├── script2.sh
└── helper.js
脚本编写规范
1. 自包含性
脚本应该尽量自包含,或者清楚地记录依赖关系。
自包含脚本示例
"""
PDF 文本提取脚本
依赖:pip install pdfplumber
运行:python scripts/extract_pdf.py input.pdf
"""
import sys
import pdfplumber
def extract_text_from_pdf(pdf_path):
"""从 PDF 文件提取文本"""
with pdfplumber.open(pdf_path) as pdf:
text = ""
for page in pdf.pages:
text += page.extract_text() or ""
return text
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法: python extract_pdf.py <pdf 文件>")
sys.exit(1)
pdf_path = sys.argv[1]
text = extract_text_from_pdf(pdf_path)
print(text)
2. 清晰的错误处理
脚本应该提供有用的错误信息,帮助代理理解出了什么问题。
错误处理示例
def main():
if len(sys.argv) < 2:
print("错误:缺少必需的参数", file=sys.stderr)
print("用法: python script.py <输入文件> [输出文件]", file=sys.stderr)
sys.exit(1)
input_file = sys.argv[1]
try:
# 执行主要逻辑
process_file(input_file)
except FileNotFoundError:
print(f"错误:找不到文件 {input_file}", file=sys.stderr)
sys.exit(1)
except PermissionError:
print(f"错误:没有权限读取文件 {input_file}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"错误:处理文件时发生未知错误: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
3. 处理边界情况
脚本应该优雅地处理各种边界情况,而不是崩溃。
边界情况处理示例
"""验证输入是否有效"""
if not text:
return False, "输入不能为空"
if len(text) > 10000:
return False, "输入长度超过限制(最大 10000 字符)"
return True, ""
# 使用验证
is_valid, error_msg = validate_input(user_input)
if not is_valid:
print(f"验证失败: {error_msg}")
sys.exit(1)
在 SKILL.md 中引用脚本
当技能需要执行脚本时,在 SKILL.md 中说明如何调用它。
引用脚本:
## PDF 文本提取 使用脚本提取 PDF 中的文本: ```bash python scripts/extract_pdf.py input.pdf ``` 脚本会从 input.pdf 提取所有文本并输出到标准输出。 如果需要将结果保存到文件: ```bash python scripts/extract_pdf.py input.pdf > output.txt ```
注意:引用脚本时使用相对于技能根目录的路径。
支持的脚本语言
支持的脚本语言取决于你使用的代理实现。常见的选项包括:
| 语言 | 说明 |
|---|---|
| Python | 最常用的选择,功能强大且易于编写 |
| Bash | 适合系统操作和命令行工具 |
| JavaScript | 适合 Web 相关的任务 |
提示:在选择脚本语言时,考虑目标用户的运行环境。如果不确定,Python 是最安全的选择。
完整示例:PDF 处理技能
让我们看一个完整的示例,展示如何结合 SKILL.md 和脚本:
技能结构
├── SKILL.md
└── scripts/
├── extract.py
├── fill_form.py
└── merge.py
SKILL.md 内容:
---
name: pdf-tool
description: 处理 PDF 文件,包括提取文本、填写表单、合并文件。使用场景:处理 PDF、提取文本、填写表单、合并 PDF。
---
## 提取 PDF 文本
从 PDF 文件中提取文本内容:
```bash
python scripts/extract.py input.pdf
```
输出是提取的纯文本内容。
## 填写 PDF 表单
使用 JSON 数据填写 PDF 表单:
```bash
python scripts/fill_form.py input.pdf data.json output.pdf
```
data.json 格式:
```json
{
"field_name": "字段值",
"another_field": "另一个值"
}
```
## 合并 PDF
将多个 PDF 文件合并为一个:
```bash
python scripts/merge.py output.pdf input1.pdf input2.pdf input3.pdf
```
## 依赖安装
需要安装 pdfplumber 和 PyPDF2:
```bash
pip install pdfplumber PyPDF2
```
两种使用方式
在 Skill 中使用代码有两种方式:
| 方式 | 适用场景 | 是否需要 scripts/ 目录 |
|---|---|---|
| 一次性命令 | 只需调用现有工具,无需自定义逻辑 | 不需要 |
| 自包含脚本 | 需要复用的复杂逻辑 | 需要 |
一次性命令
当现有的包管理器工具已经能满足需求时,直接在 SKILL.md 的指令中引用即可,无需创建 scripts/ 目录。
以下是各语言生态的常用运行方式:
uvx(Python)
uvx 是 uv 自带的工具运行命令,会在隔离环境中运行 Python 包,且缓存积极,重复运行几乎瞬间完成。
实例
uvx ruff@0.8.0 check .
# 运行指定版本的 black 格式化代码
uvx black@24.10.0 .
pipx(Python)
pipx 是 uvx 的成熟替代方案,可通过系统包管理器安装(如 brew install pipx)。
实例
pipx run 'black==24.10.0' .
# 运行指定版本的 ruff
pipx run 'ruff@0.8.0' check .
npx(Node.js)
npx 随 npm 自带,按需下载并运行 npm 包。
实例
npx eslint@9 --fix .
# 使用 Vite 创建项目
npx create-vite@6 my-app
go run(Go)
Go 自带的工具运行命令,直接编译并运行远程包。
实例
go run golang.org/x/tools/cmd/goimports@v0.28.0 .
# 运行 golangci-lint 检查
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.0 run
编写一次性命令的建议:固定版本号(如
npx eslint@9.0.0)以确保行为一致;在 SKILL.md 中声明前置条件(如"需要 Node.js 18+");当命令变得复杂时,应将其提取到 scripts/ 目录。
在 SKILL.md 中引用脚本
使用从 Skill 目录根开始的 相对路径 来引用打包的文件。
Agent 会自动解析这些路径,无需使用绝对路径。
首先在 SKILL.md 中列出可用脚本:
实例
- **`scripts/validate.sh`** — 验证配置文件
- **`scripts/process.py`** — 处理输入数据
- **`scripts/report.py`** — 生成分析报告
然后在操作步骤中引用它们:
实例
1. 运行验证脚本:
```bash
bash scripts/validate.sh "$INPUT_FILE"
```
2. 处理数据:
```bash
python3 scripts/process.py --input results.json
```
脚本执行路径是相对于 Skill 目录根的,因为 Agent 会从 Skill 目录中运行命令。这在 references/*.md 等辅助文件中同样适用。
自包含脚本
当你需要可复用的逻辑时,可以将脚本打包在 scripts/ 目录中,并在脚本内声明依赖。
Agent 只需一条命令即可运行,无需额外的安装步骤。
Python 自包含脚本(PEP 723)
PEP 723 定义了在 Python 脚本内声明依赖的标准格式。
使用 # /// 标记包裹 TOML 格式的依赖声明:
实例:Python 自包含脚本
# dependencies = [
# "beautifulsoup4",
# ]
# ///
from bs4 import BeautifulSoup
# 示例 HTML 内容
html = '<html><body><h1>Welcome</h1><p class="info">RUNOOB 测试</p></body></html>'
# 使用 BeautifulSoup 解析 HTML
soup = BeautifulSoup(html, "html.parser")
# 提取 class="info" 的段落文本
info_text = soup.select_one("p.info").get_text()
print(info_text)
运行方式:
实例
uv run scripts/extract.py
# 使用 pipx 运行
pipx run scripts/extract.py
RUNOOB 测试
Deno 自包含脚本
Deno 通过 npm: 和 jsr: 导入说明符实现自包含:
实例:Deno 自包含脚本
import * as cheerio from "npm:cheerio@1.0.0";
const html = `<html><body><h1>Welcome</h1><p class="info">RUNOOB 测试</p></body></html>`;
const $ = cheerio.load(html);
console.log($("p.info").text());
实例
Bun 自包含脚本
Bun 在没有 node_modules 目录时会自动安装缺失的包:
实例:Bun 自包含脚本
import * as cheerio from "cheerio@1.0.0";
const html = `<html><body><h1>Welcome</h1><p class="info">RUNOOB 测试</p></body></html>`;
const $ = cheerio.load(html);
console.log($("p.info").text());
实例
Ruby 自包含脚本
Ruby 使用 bundler/inline 在脚本中声明 gem 依赖:
实例:Ruby 自包含脚本
gemfile do
source 'https://rubygems.org'
gem 'nokogiri'
end
html = '<html><body><h1>Welcome</h1><p class="info">RUNOOB 测试</p></body></html>'
doc = Nokogiri::HTML(html)
puts doc.at_css('p.info').text
实例
为 Agent 设计脚本的要点
Agent 通过读取 stdout 和 stderr 来决定下一步操作,脚本的设计要考虑 Agent 的使用特点。
避免交互式提示
这是硬性要求。Agent 在非交互式 Shell 中运行,无法响应 TTY 提示、密码对话框或确认菜单。
所有输入应通过命令行参数、环境变量或 stdin 传入。
实例:错误做法 vs 正确做法
$ python scripts/deploy.py
Target environment: _
# 正确:清晰的错误信息和引导
$ python scripts/deploy.py
Error: --env is required. Options: development, staging, production.
Usage: python scripts/deploy.py --env staging --tag v1.2.3
提供 --help 文档
--help 输出是 Agent 了解脚本接口的主要方式。
包含简要说明、可用参数和使用示例:
实例:良好的 --help 输出
处理输入数据并生成摘要报告。
Options:
--format FORMAT 输出格式: json, csv, table (默认: json)
--output FILE 将输出写入文件而非 stdout
--verbose 在 stderr 中打印进度信息
Examples:
scripts/process.py data.csv
scripts/process.py --format csv --output report.csv data.csv
编写有用的错误信息
Agent 看到错误信息后会据此调整策略,信息越具体,Agent 越容易自我纠正。
实例
Error: invalid input
# 好的错误信息
Error: --format must be one of: json, csv, table.
Received: "xml"
使用结构化输出
优先使用 JSON、CSV、TSV 等结构化格式输出,而非自由文本。
结构化格式可同时被 Agent 和标准工具(jq、cut、awk)消费,方便组合使用。
实例
NAME STATUS CREATED
my-service running 2025-01-15
# 推荐:JSON 格式,字段边界清晰
{"name": "my-service", "status": "running", "created": "2025-01-15"}
将结构化数据输出到 stdout,将进度消息和诊断信息输出到 stderr。这样 Agent 可以获取干净的、可解析的输出,同时需要时也能查看诊断信息。
更多脚本设计建议
| 建议 | 说明 |
|---|---|
| 幂等性 | Agent 可能重试命令。"不存在则创建"比"创建并在重复时报错"更安全 |
| 输入约束 | 用清晰的错误拒绝模糊输入,而非猜测用户意图 |
| Dry-run 支持 | 对于破坏性或有状态操作,提供 --dry-run 标志让 Agent 预览操作 |
| 有意义的退出码 | 不同失败类型使用不同退出码(未找到、参数无效、认证失败等) |
| 可预测的输出大小 | 默认输出摘要,提供 --offset 等参数让 Agent 按需获取更多信息 |
| 安全默认值 | 破坏性操作应要求明确的确认标志(--confirm、--force) |
完整的 Skill 脚本示例
下面是一个包含脚本的完整 Skill 目录结构:
实例
├── SKILL.md # 主指令文件
├── scripts/
│ ├── overview.py # 数据概览脚本
│ ├── summary.py # 统计摘要脚本
│ └── validate.py # 数据验证脚本
├── references/
│ └── api-errors.md # API 错误参考
└── assets/
└── report-template.md # 报告模板
对应的 SKILL.md 内容:
实例
name: csv-analyzer
description: >
分析 CSV 和表格数据文件。当用户有 CSV、TSV 或 Excel 文件
并想探索、转换或可视化数据时使用。
---
# CSV 数据分析
## 可用脚本
- **`scripts/overview.py`** — 显示数据概览(行数、列名、类型)
- **`scripts/summary.py`** — 生成统计摘要
- **`scripts/validate.py`** — 验证数据质量
## 操作流程
1. 首先运行概览脚本了解数据结构:
```bash
uv run scripts/overview.py input.csv
```
2. 生成统计摘要:
```bash
uv run scripts/summary.py input.csv --output summary.json
```
3. 验证数据质量:
```bash
uv run scripts/validate.py input.csv
```
## 注意事项
- 大文件使用 `--chunk-size` 参数分块处理
- 中文 CSV 尝试 `--encoding gbk` 参数
脚本分发和依赖
如果脚本需要外部依赖,你有几个选择:
1. 在 SKILL.md 中说明依赖
最简单的方式是在 SKILL.md 中记录所需的依赖。
2. 使用虚拟环境
对于复杂的项目,可以创建一个虚拟环境。
3. 使用 Docker
如果需要完整的运行环境,可以使用 Docker 容器。
注意:确保脚本可以在目标环境中运行。在编写脚本之前,了解目标用户的运行环境。
总结
本节我们学习了如何使用脚本扩展技能功能:
- 脚本放在 scripts/ 目录下
- 脚本应该自包含、有清晰的错误处理
- 在 SKILL.md 中引用脚本时使用相对路径
- 确保脚本可以在目标环境中运行
