编写第一个插件
写一个只有一个工具的 PLUGIN.md 插件,用录制的响应测试它,再以链接的形式分享。示例是空闲教室插件的简化版。
本页带你写一个只有一个工具的小型插件文档。这个工具回答一个问题:现在各教学楼分别有多少间空闲教室。写好之后,你会离线测试它,并把它作为链接分享出去。
示例是插件仓库里真实插件 plugins/classrooms 的简化版。它只调用一个公开的校园接口,所以不需要登录。
开始之前,请装好 bun 和 git。在最后一步之前,你都不需要 App 或手机。
完整的语言说明见声明式插件,规范原文见 PLUGIN-LANGUAGE.md。
获取作者 CLI
插件仓库里有一个给插件作者用的命令行工具(CLI)。它做的检查和 App 完全一样,所以你在自己电脑上就能发现错误,不必等到有人安装时才出问题。
git clone https://github.com/pkuinfo/pku-harness-plugins
cd pku-harness-plugins
bun install命令的格式是:bun run plugin validate|test|run <plugin-dir> [tool] [json-input]。
编写 PLUGIN.md
一个插件就是一个文件夹,PLUGIN.md 是其中的主文件。这个文件分为两部分。
顶部两行 --- 之间的 YAML frontmatter 是插件的「清单」(manifest)。它写明插件的名字、包含哪些工具、可以访问哪些网站。下面的 Markdown 正文是写给 AI 助手看的,告诉它什么时候、怎样使用你的工具。
---
spec: 1
id: my-classrooms
name: 教室概况
description: 各教学楼现在有多少空闲教室。
version: 0.1.0
author: your-github-name
network: [portal.pku.edu.cn]
tools:
overview: tools/overview.yaml
i18n:
en:
name: Classroom overview
description: How many classrooms are free in each teaching building right now.
tools: { overview: { title: Free rooms by building, summary: Free classrooms in each building } }
---
my-classrooms__overview:各楼现在空闲教室数量。用户想找自习的地方时先用它挑楼。network 列出插件可以访问的全部公开域名。访问其他域名的请求都会失败,而同学们在安装前看到的也正是这份列表。i18n.en 里放英文文字,使用英文界面的用户会看到它们,而不是中文。
App 只加载 frontmatter 里写明的文件,比如这里的 tools/overview.yaml。它从不列出文件夹的内容,所以插件放在任何静态文件服务器上都能用。
编写工具
一个工具文件描述一个操作。它包含给人看的字段、给模型看的字段、声明的效果,以及按顺序执行的一组步骤。
title: 各楼空闲教室数
summary: 各教学楼现在的空闲教室数量
description: 'How many classrooms are free right now in each teaching building: building, free, total.'
effect: read
steps:
- let: r
request:
url: https://portal.pku.edu.cn/publicQuery/classroomQuery/retrClassRoomInfos.do
headers: { referer: 'https://portal.pku.edu.cn/publicQuery/', accept: 'application/json' }
as: json
- check: "r.success && isList(r.rows)"
error: 教室概况查询未成功
return: |
{
buildings: r.rows.map(b => ({ building: b.buildingName, free: b.classroomFree, total: b.classroomSum })),
}各字段的作用如下:
title是工具在 App 里显示的名字,用中文写;英文版写在i18n.en.tools.overview.title。summary可以不写。它是一行不超过 80 个字符的说明,告诉用户这个工具做什么,App 会把它显示在安装审核页的工具名下方。英文版写在i18n.en.tools.overview.summary;如果不写英文版,App 就不会给使用英文界面的用户显示这一行说明。description只给模型看,从不显示给用户。写清楚工具返回什么、什么时候该用。effect(效果)取read、write、money或public。用户会按效果分别决定助手能否不经询问直接运行工具,所以必须如实声明。- 第一个步骤发出请求,把解析后的 JSON 绑定到名字
r上。如果响应和预期不符,check步骤会带着错误信息停止。return负责组装结果。
返回数据,不要返回指令。模型会把结果当作数据来读。
YAML 里表达式的引号
如果表达式或 ${…} 模板里含有 : ,或者以 {、[ 开头,就要给它加引号。不加引号时,YAML 会把它当成映射或列表,而不是字符串。遇到这种情况,validate 会给出提示。
录制测试
测试回放事先录好的响应,而不去访问真实网站。所以它可以离线运行,每次结果都一样,校园网页改版后也不受影响。把一段真实响应裁剪到需要的部分,贴进 body。如果响应很长,可以存成测试旁边的文件,再用 file: 引用。
tool: overview
responses:
- match: retrClassRoomInfos.do
headers: { content-type: application/json }
body: '{"success":true,"rows":[{"buildingName":"一教","classroomFree":16,"classroomSum":32}]}'
expect: { buildings: [{ building: 一教, free: 16, total: 32 }] }测试用 tool 指明要运行的工具,还可以给出 input 和一个固定的时间 now。responses 里的每一项,会回应 URL 中包含 match 文字的请求,内容来自 body 或 file。如果是校园请求,还要加上 campus,写明服务名。
测试的最后写下面两个字段之一。expect 会和工具的结果做严格比较;expectError 是一个模式,错误信息必须与它匹配。
校验、测试、运行
bun run plugin validate path/to/my-classrooms
bun run plugin test path/to/my-classrooms
bun run plugin run path/to/my-classrooms overview '{}'validate 检查清单、工具的 schema、每个表达式和每个文件路径,和 App 以及插件市场做的检查完全一样。test 回放 tests/ 里的文件。run 会真实调用工具,但只限公开域名。校园请求必须在 App 里发出,因为只有 App 掌握用户的登录状态,所以要用录制的测试覆盖它们。
分享
把文件夹推送到一个公开的 GitHub 仓库。文件夹的 URL 就是安装链接:
https://github.com/<you>/<repo>/tree/main/my-classrooms其他同学可以在 App 里粘贴这个链接、扫描它的二维码,或者打开 pkuharness://plugin?src=<url-encoded link>。安装之前,App 会先显示审核页:标明这是社区插件还是官方插件,列出它能访问的网站(这里是 portal.pku.edu.cn),并列出每个工具及其说明和效果。
此后每一次 git push 都是一次更新。想把插件登记到插件市场,就去提交仓库。一个仓库里可以放任意多个插件。见发布。
下一步
- 输入、步骤、表达式、宿主能力和错误处理:声明式插件
- 在 App 里和助手一起起草、校验、试运行插件:让 AI 帮你写插件
- 需要用到同学校园登录状态的请求:声明
campus(见规范 §4 和 §9) - 审核会检查什么:审核与安全