跳到主要内容

03 · 工具系统

本章讲什么: 一个"工具"到底是什么数据结构、为什么分四种、tool() 助手做了什么、模型给的工具输入怎么被校验和修复、以及"危险操作要人点同意"的 approval 机制。

3.1 工具是什么

一个工具 = 告诉模型"你有这个能力"的元数据 + 一段真正干活的代码。最小形态就三样:

import { tool } from 'ai';
import { z } from 'zod';

const getWeather = tool({
description: '查询某城市天气', // 告诉模型这工具干嘛
inputSchema: z.object({ city: z.string() }), // 模型该给什么参数
execute: async ({ city }) => { // SDK 真正执行的代码
return { tempC: 21, city };
},
});
  • inputSchema 既用来"告诉模型该生成什么形状的参数",也用来"校验模型真给了对的形状"(tool.ts:86-93)。
  • execute 可选。不给 execute 的工具,SDK 不会自动执行——它会把工具调用交还给你(常见于前端工具:模型说要调,实际由浏览器侧处理)。

3.2 四种工具(按"谁定义 schema / 谁执行"分)

这是工具系统最该先建立的心智模型。两条正交的轴:schema 是用户定义还是 provider 定义?执行在客户端还是 provider 端? 交叉出四类:

类型schema 谁定义谁执行典型例子
FunctionTool用户客户端(SDK 调你的 execute)自定义 getWeather
DynamicTool用户(运行时才知道类型)客户端MCP 动态工具
ProviderDefinedToolprovider客户端local shell:schema 由 provider 定,但命令在你本地跑
ProviderExecutedToolproviderprovider 端内置 web search / code execution

对应类型定义在 packages/provider-utils/src/types/tool.ts:FunctionTool(:221)、DynamicTool(:235)、ProviderDefinedTool(:275)、ProviderExecutedTool(:295)。

为什么这个分类重要? 因为它决定了 02 那个循环要不要执行某个工具:循环只执行 !providerExecuted 的工具(generate-text.ts:1160)。provider-executed 的工具(isProviderExecuted: true)由模型那边执行,SDK 只是把结果收下、拼进消息。

ProviderExecutedTool 还有个 supportsDeferredResults 标记(tool.ts:318),处理"结果不在同一轮返回"的场景(对应 02 里的 pendingDeferredToolCalls)。

3.3 tool() 助手:为什么它"几乎什么都不做"

你可能以为 tool() 做了很多。实际上它的运行时实现是:

// packages/provider-utils/src/types/tool.ts —— 多个重载,最后实现
export function tool(tool: any): any {
return tool; // 原样返回!
}

tool(packages/provider-utils/src/types/tool.ts:368)。运行时它就是恒等函数。它存在的全部价值在类型层:那一堆函数重载(tool.ts:351-365)让 TypeScript 能从 inputSchema 推断出 execute 参数的类型、从 execute 返回值推断输出类型。

精华: "开发体验"在这里是纯编译期产物。tool() 不增加任何运行时开销,只是给你的工具对象"贴上正确的类型"。

3.4 模型给的工具调用怎么被校验/修复

模型输出的工具调用是不可信的:工具名可能不存在、参数可能不符合 schema、JSON 可能拼错。parseToolCall 是这道关卡:

// packages/ai/src/generate-text/parse-tool-call.ts —— 关键路径
// · 工具名不在 tools 里 → 抛 NoSuchToolError
// · 输入不符合 inputSchema → 抛 InvalidToolInputError
// · 有 repairToolCall → 给模型/自定义逻辑一次"修"的机会

相关符号:NoSuchToolError(parse-tool-call.ts:11)、InvalidToolInputError(parse-tool-call.ts:10)、repairToolCall(parse-tool-call.ts:21)。

repair 机制的思路: 模型偶尔会给出"差一点点"的工具调用(比如少个引号、字段名写错)。与其直接报错中断,不如把"坏的调用 + 错误信息"再丢给一个修复函数(常见做法:再调一次模型让它改)。你通过 experimental_repairToolCall 传入这个函数,它在 generateText 入口被接住(generate-text.ts:250)并下传给 parseToolCall

还有个相关旋钮 experimental_refineToolInput(generate-text.ts:251):在执行前"精修"已解析的输入(形状不变),用于在工具执行/回调/遥测之前统一修正输入。

3.5 tool approval:危险操作要人点同意

有些工具(删文件、转账、发邮件)不能让模型自己想调就调。AI SDK v7 把审批做成循环级机制(注意:工具上的 needsApproval 已 deprecated,tool.ts:105,改在 generateText/streamText 层用 toolApproval 配置)。

流程(读 generate-text.ts:1061-1137 那段):

模型要调工具X


resolveToolApproval(X) —— 这工具要不要审批?

├─ 'not-applicable' → 直接执行(不消耗 approvalId)
├─ 'approved' → 自动批准(记一条 approval-response)
├─ 'denied' → 自动拒绝,X 被加入 blockedToolCallIds,不执行
└─ 'user-approval' → 生成 approvalId,X 进 blocked,产出一条
tool-approval-request,等人类回复

几个细节:

  • 被 block 的工具不执行。 executeTools 的输入会 filter(... && !blockedToolCallIds.has(id))(generate-text.ts:1171-1173)。
  • 审批请求可被签名防伪。experimental_toolApprovalSecret 后,每个审批请求用 HMAC 签名(maybeSignApproval,generate-text.ts:1078),回放时验签,防止客户端伪造"已批准"(generate-text.ts:363)。这是个值得借鉴的安全设计:人审批的"同意"凭证必须不可伪造
  • 审批是跨请求的。 人类的批准/拒绝下次随消息带回,入口处 collectToolApprovals + validateApprovedToolApprovals 先把上一轮待审批的执行掉(generate-text.ts:652-759)。

3.6 巧妙之处

  • tool() 是零运行时的类型助手 —— DX 全靠重载,实现是恒等函数(tool.ts:368)。
  • 四象限分类直接驱动循环行为 —— providerExecuted 一个布尔决定 SDK 执不执行(generate-text.ts:1160),分类不是文档摆设,是控制流开关。
  • 审批凭证可 HMAC 签名 —— 把"人类同意"当成需要防篡改的安全凭证,而非简单布尔(tool-approval-signature.ts)。
  • repair 把"模型犯错"当可恢复事件 —— 解析失败不直接崩,给一次修复机会(parse-tool-call.ts)。

3.7 边界与局限

  • 工具级 needsApproval 已废弃,审批统一上移到调用层 toolApproval(tool.ts:105)。混用老代码要注意。
  • execute 的工具不被自动执行——这是特性不是 bug(用于前端/人类侧执行),但容易让人误以为"工具没跑"。
  • provider-executed 工具的结果完全由 provider 控制,SDK 无法在中途干预其执行。

3.8 代码地图

主题文件符号
工具类型与助手packages/provider-utils/src/types/tool.tsTool, tool, FunctionTool, ProviderExecutedTool
工具执行选项packages/provider-utils/src/types/tool-execute-function.tsToolExecuteFunction, ToolExecutionOptions
解析+修复packages/ai/src/generate-text/parse-tool-call.tsparseToolCall, repairToolCall
单工具执行packages/ai/src/generate-text/execute-tool-call.tsexecuteToolCall
审批解析packages/ai/src/generate-text/resolve-tool-approval.tsresolveToolApproval
审批签名packages/ai/src/generate-text/tool-approval-signature.tsmaybeSignApproval
收集历史审批packages/ai/src/generate-text/collect-tool-approvals.tscollectToolApprovals

下一章:04 · 流式与 UI