← 返回目录

第 2 卷

第 2 / 4 卷日期:

3{ 4"name": "greet",

5"description":"Greet someone by name.",
6"parameters": {
7"type":"object",
8"properties": {
9"name": {"type":"string","description":"The name to greet"}
10},
11"required": ["name"]
12}
13}
14 ]
15 ---调用greet ---
16 { isError:false, content: [ {type:'text', text:'Hello, World!'} ], value:'H
17 ---卸载后再看清单---
18 []

调用用 root.tools.execute(),参数是一个对象而不是 (name, args):

复制代码 1await root.tools.execute({

2callId:CallId('call-1'),//调用标识,用于追踪
3name:'greet',
4arguments: { name:'World'},
5signal: newAbortController().signal,//取消信号,生产环境由agent loop传入
6})

callId 和 signal 都是必填。真实项目里它们由 Agent Loop 提供,这里我手动构造只是为了跑通。

顺带提一句,execute 还有第二个参数 exec,携带 signal(取消信号)、token(身份标识)、agent(当前 Agent 句柄)等运行时上下文——greet 用不到,但写真实工具时你

会靠它拿到取消信号。

模型眼中的工具

清单里只有三个字段:name、description、parameters。这就是模型能看到的全部。

description 值得我们多花点心思,它是模型判断“该不该用、什么时候用”的唯一依据。这里有几条实用经验,分享给你:

说清干什么,也说清不干什么。"Read a file from the filesystem" 不如 "Read a file from the filesystem. Use for reading, not writing; use grep to search across files."

参数描述同样重要。description: 'The name to greet' 能防止模型往里塞 JSON 字符串。

别把实现细节写进去。模型只需要知道“能读文件”,不需要知道你底层用的是本地磁盘还是云存储。

你在工具里写的其他一切,模型都看不见:output、execute、timeoutMs、 isConcurrencySafe……注册表用显式白名单把它们挡在外面。这不是疏漏,而是设计:

只给模型“能干什么、怎么调用”,不给它“怎么实现”,它就不会被实现细节带偏。

register 只是把工具登记进注册表,真正让模型“看见”的,是系统提示词组装。还记得我们刚刚讲要先挂 SystemPrompt 吗?ToolRuntime 依赖的 systemPrompt 服务,会在每

一轮开场时调用 schemas(),把每个工具的 name/description/parameters 拼进发给模型的那段话里。也就是说,你对模型认知边界的控制权,就藏在这三个字段里。

工具的返回值

output 是必填的,不写注册时就抛 TypeError。它由两部分组成:

复制代码

顺便说一句,上面的例子用的是字符串,简单。但如果返回值是一个对象(本讲后面的 env_probe 就返回 { platform, node }),schema 就得写成 type: 'object',这

时有个容易踩的坑:每个显式 object 节点都必须声明 additionalProperties: true | false,缺了注册时就抛 JsonSchemaError(错误码 UNSUPPORTED_SCHEMA)。

复制代码 1schema: {

2type: 'object',

3additionalProperties: false,//少这一行会注册失败
4properties: { platform: { type:'string'}, node: { type:'string'} },
5}

为什么要同时声明 schema 和 render?下一讲我们会提到,模型写的程序要 await tools.<name>(args)</name>,然后用代码处理返回值——程序要的是结构化字段

(value),模型要的是一段排版好的文本(content)。schema 声明前者,render 产出后者,各取所需。如果只返回一个字符串,程序就没法按字段取用。

顺带记住一条硬规矩:execute 返回的是规范 JSON 值,不是内容块,把它翻成模型能看的文本,是 render 的活。注意,调用到这里还没结束:这个值之后还要被校验、可能被改

写、最终才呈现(这条完整流水线,下一讲展开)。所以你的工具只负责执行,执行完给谁看、要不要改,都交给框架。

最后看清 render 的返回类型:它是一个内容块数组,[{ type: 'text', text }] 里的 text 只是其中一种,还可以是图片、工具调用块等。所以 render 返回数组而不是单个

字符串,是因为模型收到的消息本来就是一堆块的序列,文本只是最朴素的一种。

seam:让能力可替换

现在麻烦来了:假设你要提供一个“读环境变量”的能力,但生产、测试要不同的实现。也就是

全站分卷索引 · 4 卷
  1. 1第 1 卷
  2. 2第 2 卷
  3. 3第 3 卷
  4. 4第 4 卷