01 工具定义抽象与参数 Schema 编译 本节摘要:Agent 能「动手」靠的是工具——读、写、改、执行命令、搜索。本节讲工具系统的最底层:工具定义抽象。一个工具长什么样(标识 + 参数 Schema + 执行函数),定义时为什么会「编译一次校验器」,执行时自动加了哪些横切关注点(校验、截断、链路追踪)。理解了这一节,你才知道一个工具从「写出来」到「能被模型调用」中间发生了什么。 一、工具的最小定义 一个工具的最小定义包含三样东西: 部分 | 作用 标识(id) | 唯一名字,模型和注册表用它引用 参数 Schema | 描述「这个工具接受什么参数」(字段、类型、必填) 执行函数 | 拿到参数后实际干活的函数 这三样定义完,一个工具就「存在」了。
本节摘要:Agent 能「动手」靠的是工具——读、写、改、执行命令、搜索。本节讲工具系统的最底层:工具定义抽象。一个工具长什么样(标识 + 参数 Schema + 执行函数),定义时为什么会「编译一次校验器」,执行时自动加了哪些横切关注点(校验、截断、链路追踪)。理解了这一节,你才知道一个工具从「写出来」到「能被模型调用」中间发生了什么。
一个工具的最小定义包含三样东西:
| 部分 | 作用 |
|---|---|
| 标识(id) | 唯一名字,模型和注册表用它引用 |
| 参数 Schema | 描述「这个工具接受什么参数」(字段、类型、必填) |
| 执行函数 | 拿到参数后实际干活的函数 |
Tool.define("read_file", { parameters: Schema({ path: Schema.string }), // 参数 Schema execute: async ({ path }) => { ... 读文件 ... } // 执行函数 })
这三样定义完,一个工具就「存在」了。但要真正被模型调用,还要经过注册表(第 02 节)和运行时(第 04 节)。
工具定义时,有一个关键动作——编译一次参数校验器。这是从参数 Schema 编译出来的一个「校验函数」,之后每次执行都用它校验参数。
定义时: 参数 Schema ──编译──► 校验函数(一次) │ 执行时(每次): 传入参数 ──校验函数──► 合法? ──► 执行 └─ 不合法 ──► 抛错给模型
为什么「编译一次」?因为校验逻辑只跟 Schema 有关,不跟具体参数有关。Schema 不变,编译出的校验函数也不变。所以定义时编译一次,缓存起来,之后每次执行直接用——几乎零成本。
💡 性能红利:如果不编译,每次执行都要「解析 Schema → 校验」,开销随执行次数线性增长。编译一次后,校验变成「调用现成函数」,极快。这是工具系统能高效的关键之一。
工具执行时,除了「调你写的执行函数」,还自动加了几个横切关注点——你写执行函数时不用管,系统自动套上:
| 横切关注点 | 作用 |
|---|---|
| 参数校验 | 用编译好的校验器校验参数,不合法抛错 |
| 输出截断 | 工具输出太大时自动截断,防撑爆上下文 |
| 链路追踪 | 每次执行是一个追踪 span,便于调试与观测(第 12 章) |
模型调用 read_file({ path: "xxx" }) │ ▼ 1. 参数校验(用编译好的校验器) │ └─ 不合法 ──► 抛「参数错误」给模型(让它重写) │ ▼ 2. 开追踪 span │ ▼ 3. 执行你的 execute 函数 │ ▼ 4. 输出截断(太长则截) │ ▼ 5. 结束追踪 span │ ▼ 返回结果给模型
这些横切关注点让工具作者只写业务逻辑(execute 函数),不用操心校验、截断、追踪——系统全包了。
参数校验失败时,抛的不是「给程序员的栈」,而是「给模型看的错误」。这是个有意的区别:
模型调用 read_file({ path: 123 }) // path 应是字符串,传了数字 │ ▼ 校验失败 │ ▼ 抛给模型的错误:「path 应为字符串,收到数字」 │ ▼ 模型看到错误,重写参数 read_file({ path: "xxx" }) // 这次对了
这种「错误让模型自我修正」的设计,让工具调用更鲁棒——模型传错参数不会卡死,它会看到错误描述然后改正。
工具输出有时会非常大(比如读了一个 10 万行的日志文件)。如果不截断,这个输出会全塞进模型上下文,瞬间撑爆(第 9 章就要不停压缩)。所以执行后自动做截断:
⚠️ 截断是双刃剑:截断保护了上下文,但也可能让模型看不到完整信息。所以更复杂的输出边界控制(把大输出落盘只回传引用)在「运行时层」(第 04 节)处理,本节的截断只是第一道防线。
每次工具执行自动开一个追踪 span(OpenTelemetry,第 12 章)。这意味着:
这是「可观测性」的基础——工具执行不是黑盒,而是有完整轨迹的。
工具定义清楚了,下一节讲它如何进入「可用集合」——注册表与按模型过滤。