PKU harness

工具与效果

工具是插件对外提供功能的唯一方式。每个工具有 id、标题、写给模型的说明、zod 输入 schema,以及唯一一个效果(read、write、money 或 public)。

工具(tool)是插件提供的一个有类型的操作,例如「查询校园卡余额」或「创建一条提醒」。

同一个工具可以被四种方式调用:助手在对话中调用它;页面在用户点击时通过 invoke 调用它;小组件通过 host.useToolData 读取它;定时检查也会运行它。除了工具,没有其他途径能用到插件的功能。所以,插件定义了哪些工具,就决定了它对外提供哪些功能。

工具的结构

下面是校园卡余额工具的定义。

import { defineTool } from '@pku-harness/sdk';
import { z } from 'zod';

const cardBalance = defineTool({
  title: '校园卡余额',                       // 展示给用户(对话中的标签、审批弹窗、设置页)
  description: 'Campus card balance in CNY, today\'s spending and the daily limit. Use when the user asks how much money is left on the card.',
  input: z.object({}),                      // 在 execute 运行前校验
  effect: 'read',                           // 它会改变什么:read | write | money | public
  execute: async (_input, ctx) => {
    const res = await ctx.services.campus.request('card', { url: '/berserker-app/ykt/tsm/queryCard?synAccessSource=h5' });
    return parseBalance(res.body);          // 只返回 JSON 数据,绝不包含指令
  },
});

每个字段的规则如下。

  • title:简短的中文标题,展示给用户看。英文标题写在 i18n.en.tools.<name>.title。
  • description:写给模型看的说明。它要告诉模型什么时候用这个工具、怎么用,以及单位、格式和限制。可以用中文或英文写,不需要翻译。它也可以是一个依赖宿主服务的函数(Dynamic<string>)。
  • input:一个 zod schema。请给每个字段加上 .describe(),因为模型看到的是由它生成的 JSON Schema。
  • effect:工具的效果,从下一节的四种效果中选一个,并且要如实填写。
  • untrusted:如果结果里有第三方写的文字,例如论坛帖子、通知或活动简介,设为 true。
  • userOnly:有些操作只能由用户亲自触发,助手不能自己打开。这时设为 true。工具会对模型隐藏,来自助手和定时任务的调用都会被拒绝。
  • execute:形式为 (input, ctx) => Promise<output>。失败时抛出一个 Error,错误信息要清楚,让用户能看懂。

工具的 id 与名称

工具有两种写法,分别给代码和模型使用。

工具 id 的形式是 <plugin id>.<tool name>,例如 wallet.cardBalance 和 scheduler.create。代码里都使用 id,例如 invoke('memory.list')、host.useToolData('wallet.cardBalance'),以及检查任务和小组件绑定。

模型看到的名称是把 id 中第一个 . 换成 __,例如 wallet__cardBalance。这是因为模型 API 不允许工具名里出现点号。在 instructions 或说明里提到某个工具时,请使用这种写法,例如 "use canteen__openNow for …"。

远程插件的工具 id 是 mcp-<server>.<tool>,模型看到的名称是 mcp-<server>__<tool>。

四种效果

效果(effect)描述一个工具会改变什么。每个工具只能有一种效果。

效果含义例子
read(读取)只读取数据,不改变任何东西查余额、查课表、搜索、列出
write(修改)改变手机上或服务端的状态添加待办、创建提醒、关注帖子、预约房间
money(花钱)花钱、转钱,或者让付款成为可能校园卡充值、创建支付订单、切换到电子校园卡付款
public(公开)发布别人能看到的内容在树洞发帖或回复

如果一个工具同时符合几种效果,请选最强的那个。例如,一个工具既公开发帖,又修改本地状态,它的效果就是 public。

效果决定了权限。你可以针对每个插件的每种效果,选择助手可以直接执行、需要先询问,还是完全不能执行。

定时任务只能读取

定时任务运行时没有人在旁边看着。所以无论你怎么设置授权方式,定时任务都会拒绝所有效果不是 read 的工具。只有确实不改变任何东西的工具,才能标成 read。否则,基于它的定时检查会误导用户。

谁在调用工具

ctx.actor 告诉工具是谁发起了这次调用。调用者有三种。

调用者何时出现是否需要批准
user页面或小组件响应用户点击,调用了 invoke(...)从不询问
agent模型在对话中调用了工具该效果的授权方式为 ask 时询问
scheduler定时检查或定时 Agent 任务只能使用 read 工具

用户的调用从不询问,因为点击本身就表达了用户的意图。定时任务遇到授权方式为 ask 的工具时,会把它当作被拒绝。

助手在对话中调用工具时,ctx.turn 会被设置为 { sessionId, tainted, userAsked }。其中 tainted 的含义见安全模型。

如何返回结果

  • 返回能直接回答问题的 JSON。 字段名里带上单位,例如 balanceYuan 和 minutesUntil。时间用 ISO 格式或本地时间 "YYYY-MM-DD HH:mm"。如果模型可能要把某个 id 传给下一个工具,就把它也放进结果。
  • 不要返回指令,例如「现在告诉用户……」。模型会把结果当作数据。
  • 结果要小。 在模型的历史记录里,框架会把单个结果截断到 12,000 个字符,界面上仍然保留完整输出。所以请分页或过滤,不要把所有数据一次返回。
  • 预期内的、可以恢复的状态,返回状态对象,不要抛出异常。 例如树洞需要短信验证时,返回 { status: 'verification_required', methods: ['sms'], message: '…' }。
  • 绝不返回机密信息,例如付款码、令牌和分享链接。校园卡付款码只会显示在页面上,不会出现在工具结果里。

数据源

有些读取工具很适合给小组件和定时检查使用。插件可以在 PluginDef.dataSources 里列出这些工具,并为每个工具指定缓存时长。助手通过 dashboard.sources 找到它们。

dataSources: {
  crowd: { title: '食堂就餐人数', tool: 'crowd', ttlMs: 2 * 60_000 },
},

tool 是本插件内部的工具名。如果需要固定的参数,写在 args 里。

本页目录