现在位置: 首页 > Skills 教程 > 正文

Skills 脚本扩展

除了在 SKILL.md 中编写指令外,你还可以通过 scripts 目录为技能添加可执行代码。

本节将教你如何使用脚本来扩展技能的功能。


为什么使用脚本

使用脚本可以:

  • 封装复杂的逻辑,避免在 SKILL.md 中写大量代码
  • 提供经过测试的可靠实现
  • 让代理可以重复使用相同的工具和函数
  • 处理更复杂的输入输出操作

提示:当你在多次执行中发现代理重复实现相同逻辑时,这往往是创建脚本的好时机。


脚本目录结构

脚本应该放在技能目录的 scripts/ 子目录下:

目录结构

my-skill/
├── SKILL.md
└── scripts/
    ├── script1.py
    ├── script2.sh
    └── helper.js

脚本编写规范

1. 自包含性

脚本应该尽量自包含,或者清楚地记录依赖关系。

自包含脚本示例

#!/usr/bin/env python3
"""
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. 清晰的错误处理

脚本应该提供有用的错误信息,帮助代理理解出了什么问题。

错误处理示例

import sys

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. 处理边界情况

脚本应该优雅地处理各种边界情况,而不是崩溃。

边界情况处理示例

def validate_input(text):
    """验证输入是否有效"""
    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 和脚本:

技能结构

pdf-tool/
├── 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 包,且缓存积极,重复运行几乎瞬间完成。

实例

# 运行指定版本的 ruff 检查代码
uvx ruff@0.8.0 check .

# 运行指定版本的 black 格式化代码
uvx black@24.10.0 .

pipx(Python)

pipx 是 uvx 的成熟替代方案,可通过系统包管理器安装(如 brew install pipx)。

实例

# 运行指定版本的 black
pipx run 'black==24.10.0' .

# 运行指定版本的 ruff
pipx run 'ruff@0.8.0' check .

npx(Node.js)

npx 随 npm 自带,按需下载并运行 npm 包。

实例

# 运行 ESLint 检查
npx eslint@9 --fix .

# 使用 Vite 创建项目
npx create-vite@6 my-app

go run(Go)

Go 自带的工具运行命令,直接编译并运行远程包。

实例

# 运行 goimports 格式化导入
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 自包含脚本

# /// script
# 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 运行(推荐)
uv run scripts/extract.py

# 使用 pipx 运行
pipx run scripts/extract.py
RUNOOB 测试

Deno 自包含脚本

Deno 通过 npm:jsr: 导入说明符实现自包含:

实例:Deno 自包含脚本

#!/usr/bin/env -S deno run

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());

实例

deno run scripts/extract.ts

Bun 自包含脚本

Bun 在没有 node_modules 目录时会自动安装缺失的包:

实例:Bun 自包含脚本

#!/usr/bin/env 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());

实例

bun run scripts/extract.ts

Ruby 自包含脚本

Ruby 使用 bundler/inline 在脚本中声明 gem 依赖:

实例:Ruby 自包含脚本

require 'bundler/inline'

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

实例

ruby scripts/extract.rb

为 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 输出

Usage: scripts/process.py [OPTIONS] INPUT_FILE

处理输入数据并生成摘要报告。

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 目录结构:

实例

csv-analyzer/
├── 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 中引用脚本时使用相对路径
  • 确保脚本可以在目标环境中运行