关于 tools
assistant-ui tools 为开发者提供编写模型可调用工具及其界面的工具包系统。编译器将 `"use generative"` 文件拆分为服务端和客户端构建,并支持人工交互与 MCP 工具包。
主要功能
- 01编译生成服务端和客户端工具包
- 02支持后端、前端、人工及 Provider 工具
- 03渲染工具调用和流式状态
- 04支持人工输入与审批门控
- 05支持 MCP 工具包和应用
适用场景
- 01为聊天模型定义工具
- 02在聊天界面呈现工具调用
- 03因人工输入或审批暂停运行
MCP 相关
工具包可通过 `AISDKToolkit` 和 `defineMcpToolkit` 打开 MCP 服务。该技能也介绍了如何渲染 MCP App 的 `ui://` 小部件。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ assistant-ui/ skills --skill tools安装后在 Claude Code 中输入/tools确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/human-in-loop.mdlegacy-component-apis.mdmcp-apps.mdmcp-server.mdmulti-agent.mdtool-ui.mdtoolkits.mdwebmcp.md详细介绍
assistant-ui 工具
请始终查阅 assistant-ui.com/llms.txt,以获取最新 API。
工具是模型可以调用的具名能力。在 assistant-ui 中,你需要在工具包中声明工具。工具包是一个映射,其键是模型可见的工具名称,值则包含架构、执行器和渲染器。受支持的编写方式是使用由构建插件编译的 "use generative" 文件,该插件会将一个文件拆分为服务端构建(架构和后端执行器)与客户端构建(架构、渲染器和浏览器执行器)。
参考资料
- ./references/toolkits.md -- 端到端编写工具包:类型、渲染器、
toModelOutput、providerOptions、存根、文件拆分与合并、backendless - ./references/tool-ui.md -- 渲染状态、
useToolArgsStatus、延迟渲染、流式参数、ToolFallback和ToolGroup - ./references/human-in-loop.md -- 人工工具、
human()中断以及完整的审批门控接口 - ./references/mcp-server.md -- 服务端 MCP 服务和
defineMcpToolkit - ./references/mcp-apps.md -- 使用
McpAppRenderer渲染 MCP Appui://小组件 - ./references/webmcp.md -- 使用
unstable_useWebMcpProvider将前端工具发布给浏览器 Agent - ./references/multi-agent.md -- 工具调用中的子 Agent 对话
- ./references/legacy-component-apis.md -- 已弃用的
makeAssistantTool系列 API,以及如何迁移
编写模型
1. 添加构建插件
没有编译器,该指令不会产生任何作用。
import { withAui } from "@assistant-ui/next";
export default withAui({
/* your Next config */
});
对于 Vite 和 TanStack Start,请将 @assistant-ui/vite 中的 aui() 添加到 plugins;对于 Expo 和原生 React Native,请使用 @assistant-ui/metro 中的 withAui 包装 Metro 配置。这三种方式都接受一个 aui 选项对象,详见 toolkits.md。
2. 编写工具包
"use generative";
import { defineToolkit } from "@assistant-ui/react";
import { z } from "zod";
export default defineToolkit({
get_weather: {
description: "Get current weather for a location.",
parameters: z.object({
location: z.string().describe("City name or zip code"),
unit: z.enum(["celsius", "fahrenheit"]).default("celsius"),
}),
execute: async ({ location, unit }) => {
"use client";
return fetchWeatherAPI(location, unit);
},
render: ({ args, result }) =>
result ? (
<div>
{result.temperature} {args.unit}
</div>
) : (
<div>Fetching weather for {args.location}</div>
),
},
});
3. 在客户端挂载
"use client";
import { AssistantRuntimeProvider, AuiConfig, Tools } from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/ai-sdk";
import toolkit from "./toolkit";
export function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
const runtime = useChatRuntime();
const config = AuiConfig({ tools: Tools({ toolkit }) });
return (
<AssistantRuntimeProvider runtime={runtime} config={config}>
{children}
</AssistantRuntimeProvider>
);
}
如果只想将工具包作用域限定在树的一部分,可以用 <AuiProvider extends={aui} config={config}> 包装该子树,并使用 const aui = useAui()。useChatRuntime() 默认指向 /api/chat。
4. 将工具提供给模型
在路由处理程序中,同一个导入会解析为服务端构建。
import { AISDKToolkit } from "@assistant-ui/ai-sdk";
import { streamText, convertToModelMessages } from "ai";
import { openai } from "@ai-sdk/openai";
import toolkit from "../../toolkit";
const aiToolkit = new AISDKToolkit({ toolkit });
export async function POST(req: Request) {
const { messages, system, tools } = await req.json();
const result = streamText({
model: openai("gpt-5.6-luna"),
system,
messages: await convertToModelMessages(messages),
tools: await aiToolkit.tools({ frontend: tools }),
});
return result.toUIMessageStreamResponse();
}
AISDKToolkit.tools() 会向模型注册工具包中的所有工具;如果服务端构建包含后端 execute,则会将其接入;它还会合并客户端在请求正文中上传的前端工具,并启动工具包展开包含的任何 MCP 服务。服务端 execute 的优先级高于同名的已上传条目。
工具类型
类型根据 execute 推断,并写回为 type。你不会在 "use generative" 文件中编写 type。
你编写的 execute | 推断出的类型 | 服务端构建保留 | 客户端构建保留 |
|---|---|---|---|
普通的 async () => ... | backend | schema 和 execute,并由 server-only 保护 | schema 和 render |
async () => { "use client"; ... } | frontend | 仅 schema | schema、execute 以及 render 或 renderText |
humanTool() | human | 仅 schema | schema 和 render |
stubTool() | frontend,执行器在运行时提供 | 仅 schema | schema 以及 render 或 renderText |
providerTool({ ... }) | provider | schema 和 provider 配置 | schema 和 provider 配置 |
externalTool() | backend,定义在其他位置 | 省略 | type: "backend" 以及 render 或 renderText |
编译器会在构建时强制要求每个工具都声明 execute,frontend 工具声明 render 或 renderText,human 工具声明 render。humanTool() 和 stubTool() 没有运行时实现,执行到它们时会抛出异常,这意味着该文件从未被编译;externalTool() 同样是一个编译时标记。
渲染工具调用
render 接收作为 ToolCallMessagePartProps 的实时调用。
| 字段 | 类型 | 说明 |
|---|---|---|
args | TArgs | 已解析的参数;流式传输期间可能不完整 |
argsText | string | 原始 JSON,可能不完整 |
result | TResult | undefined | 调用有结果后才会存在 |
isError | boolean | undefined | 结果是否表示失败 |
status | ToolCallMessagePartStatus | running、complete、带有 reason 的 incomplete,或带有 reason: "tool-calls" | "interrupt" 的 requires-action |
toolName、toolCallId | string | 模型可见的名称,以及此次调用的稳定 ID |
timing | ToolCallTiming | undefined | 记录时的实际开始和完成时间 |
interrupt | { type: "human"; payload: unknown } | undefined | 来自前端执行器的暂停状态 human() 请求 |
approval | object | undefined | 服务端门控:id、approved?、options?、optionId?、resolution? |
addResult | (result) => void | 在 UI 中完成 human 工具 |
resume | (payload: unknown) => void | 响应 interrupt |
respondToApproval | (response: ToolApprovalResponse) => Promise<void> | 响应审批门控 |
如果要显示单行状态而不是组件,请设置 renderText,并提供 running 和 complete 值;每个值可以是字符串,也可以是 ({ args, result }) 的函数。在条目上设置 display: "standalone",使 UI 保持在折叠的工具组之外。没有渲染器的工具会回退到 ToolFallback 元素。
审批关卡
某些运行时会暂停服务,并发出客户端必须在工具运行前答复的审批请求。AI SDK v7 运行时会为调用级别 toolApproval 选项中列出的每个工具发出审批请求。
import { useState } from "react";
import { defineToolkit, type ToolApprovalResponse } from "@assistant-ui/react";
const toolkit = defineToolkit({
deploy: {
type: "backend",
render: ({ args, approval, respondToApproval, result }) => {
const [error, setError] = useState<string | null>(null);
const answer = async (response: ToolApprovalResponse) => {
setError(null);
try {
await respondToApproval(response);
} catch (failure) {
setError(failure instanceof Error ? failure.message : String(failure));
}
};
if (approval?.approved === undefined) {
if (approval?.isAutomatic) return <p>Auto approved by policy</p>;
return (
<div>
<p>Approve deploy to {args.target}?</p>
<button onClick={() => void answer({ approved: true })}>Approve</button>
<button onClick={() => void answer({ approved: false, reason: "user denied" })}>
Deny
</button>
{error && <p role="alert">{error}</p>}
</div>
);
}
if (approval?.approved === false) {
return <p>Denied{approval.reason ? `: ${approval.reason}` : ""}</p>;
}
return result === undefined ? <p>Approved, running</p> : <p>Deployed</p>;
},
},
});
approval.approved 有三种状态。undefined 表示关卡处于开放状态,且这是唯一可以调用 respondToApproval 的状态。true 表示已记录为允许,服务正在生成结果。false 表示已记录为拒绝;运行时会记录错误结果,并公开 approval.reason。当服务端策略而非用户授予审批时,approval.isAutomatic 为 true,此时应显示徽章,而不是按钮。
respondToApproval 会返回一个 promise:运行时接受答复后,该 promise 会兑现;无法记录答复时(例如关卡已过期或答复被拒绝),则会拒绝。应等待它完成后再禁用控件,这样答复被拒绝时,请求仍可重试。toolApprovalAcceptsText(approval) 会报告请求是否接受自由格式答复(单独接受或与选项同时接受),以便渲染器判断是否需要提供文本框。完整的选项、问题和处理结果接口见 human-in-loop.md。
人工工具
人工工具没有执行器:运行会暂停,直到渲染器提供结果。
select_date: {
description: "Ask the user to select a date.",
parameters: z.object({ prompt: z.string() }),
execute: humanTool(),
render: ({ args, result, addResult }) => {
if (result) return <p>Selected {result.date}</p>;
return <DatePicker prompt={args.prompt} onChange={(date) => addResult({ date })} />;
},
},
准确调用一次 addResult。当用户直接提供工具结果时,使用人工工具;当后端负责执行操作、只需要获得许可时,则使用审批关卡。
常见问题
humanTool() 或 stubTool() 在运行时抛出错误
- 文件未经过编译器处理。添加构建插件,并确保
"use generative"是文件的第一行。
工具 UI 从未渲染
- 工具包键必须与模型可见的工具名称完全一致,包括任何 MCP
prefix。 - 必须挂载工具包:将
const config = AuiConfig({ tools: Tools({ toolkit }) })作为config传给 provider。请从模块作用域或useMemo传入稳定的工具包。
模型从未获知前端工具或人工工具
- 客户端构建会跳过上传这些 schema,因为它假定你的后端导入了同一文件的服务端构建版本。如果你没有自己的后端,请使用
aui: { backendless: true }进行编译。
前端工具的结果从未传递给模型
- 使用来自
ai的sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls配置运行时;对于审批关卡,还需配置lastAssistantMessageIsCompleteWithApprovalResponses。
往返传递的结果忽略了 toModelOutput
- 将工具注册表同时传给
convertToModelMessages(messages, { tools })和streamText。
构建时出现工具名称警告
- 名称重复表示两个展开片段定义了相同的键,而对象展开会保留后者;请重命名其中一个条目。无法通过
makeTool()处理的工具来自不透明的工厂调用:请将其写成内联对象,或展开编译器可见的defineToolkit(...)或defineMcpToolkit(...)片段。
respondToApproval 被拒绝
- 只有当
approval.approved为undefined时才可调用。对于未声明display: "text"或allowFreeform的请求,提供text答复会抛出错误;未知的optionId也会导致错误。
MCP 连接不断累积
- 将
AISDKToolkit保留在模块作用域,以便客户端跨请求复用连接池,并在onFinish中调用aiToolkit.close()。