大模型应用
assistant-ui 工具不调用、UI 不渲染…
在 assistant-ui 中通过 "use generative" + defineToolkit + 构建插件声明、挂载、暴露、渲染和审批模型可调用工具,排查工具不调用、UI 不渲染、前端结果不回传、humanTool() 运行时报错、审批门无法回答等问题。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
assistant-ui 工具不调用、U特定
解决思路
- 先查阅 https://www.assistant-ui.com/llms.txt 获取最新 API。
- 添加构建插件:Next 在 `next.config.ts` 中用 `withAui` from `@assistant-ui/next` 导出 `withAui({ ... })`;Vite 和 TanStack Start 把 `@assistant-ui/vite` 的 `aui()` 加到 `plugins`;Expo 和裸 React Native 用 `@assistant-ui/metro` 的 `withAui` 包装 Metro config;三者都接收 `aui` options object。
- 写 toolkit:创建首行为 `"use generative"` 的文件,默认导出 `defineToolkit({...})`;键是模型可见的工具名,值是 schema、executor、renderer;如 `get_weather` 声明 `description`、`parameters: z.object(...)`、`execute: async ({ location, unit }) => { ... }`、`render: ({ args, result }) => ...`。
- 编译语义:`"use generative"` 文件会被构建插件拆成服务端构建(schema 加后端 executors)和客户端构建(schema 加 renderers 加浏览器 executors);没有编译器时 directive 无效。
- 客户端挂载:在 `"use client"` 的 provider 中用 `useChatRuntime()`,创建 `const config = AuiConfig({ tools: Tools({ toolkit }) })`,把 `config` 传给 `AssistantRuntimeProvider`;只作用于子树时用 `const aui = useAui()`,并用 `<AuiProvider extends={aui} config={config}>` 包住该子树;`useChatRuntime()` 默认请求 `/api/chat`。
- 服务端暴露给模型:在 route handler 中从同一路径 import toolkit(会解析到 server build),`const aiToolkit = new AISDKToolkit({ toolkit })`;在 `streamText` 中传 `tools: await aiToolkit.tools({ frontend: tools })`;`tools()` 会注册所有 toolkit 工具、接上后端 `execute`、合并客户端上传的 frontend tools、打开 toolkit 展开的 MCP servers;同名时服务端 `execute` 优先于上传条目。
- 不要手写 `type`:kind 由 `execute` 推断并写回 `type`。普通 `async () => ...` 推断为 backend(server build 保留 schema 加 `execute`,由 `server-only` 保护;client build 保留 schema 加 `render`)。
- `async () => { "use client"; ... }` 推断为 frontend(server build 只保留 schema;client build 保留 schema 加 `execute` 加 `render` 或 `renderText`)。
- `humanTool()` 推断为 human(server build 只保留 schema;client build 保留 schema 加 `render`)。
- `stubTool()` 推断为 frontend、executor 在运行时提供(server build 只保留 schema;client build 保留 schema 加 `render` 或 `renderText`)。
- `providerTool({ ... })` 推断为 provider(server build 和 client build 都保留 schema 加 provider config)。
- `externalTool()` 推断为 backend、在别处定义(server build 省略;client build 保留 `type: "backend"` 加 `render` 或 `renderText`)。
- 满足编译期约束:每个工具必须声明 `execute`,frontend 工具必须声明 `render` 或 `renderText`,human 工具必须声明 `render`;`humanTool()` 和 `stubTool()` 没有运行时实现,被触达会抛错,说明该文件从未被编译;`externalTool()` 同样是编译期标记。
- 渲染时使用 `ToolCallMessagePartProps`:`args` 是已解析参数、流式时可能不完整;`argsText` 是原始可能不完整的 JSON;`result` 在调用有结果后存在;`isError` 表示结果是否失败;`status` 为 `running`、`complete`、带 `reason` 的 `incomplete`,或带 `reason: "tool-calls" | "interrupt"` 的 `requires-action`;另有 `toolName`、`toolCallId`、`timing`、`interrupt`、`approval`、`addResult`、`resume`、`respondToApproval`。
- `interrupt` 的类型是 `{ type: "human"; payload: unknown } | undefined`,表示来自 frontend executor 的暂停 `human()` 请求;`approval` 含 `id`、`approved?`、`options?`、`optionId?`、`resolution?`,是服务端门禁;`addResult` 从 UI 完成 human tool;`resume` 回答 `interrupt`;`respondToApproval` 回答 approval gate。
- 需要一行状态而非组件时,用 `renderText` 设置 `running` 和 `complete`,值可以是字符串或接收 `({ args, result })` 的函数;在条目上设 `display: "standalone"` 让 UI 保持在折叠的 tool group 外;没有 renderer 的工具回退到 `ToolFallback` 元素。
- 处理 approval gates:有些 runtime 在服务端暂停并发出 approval request,客户端必须回答后工具才运行;AI SDK v7 runtime 会对调用级 `toolApproval` 选项列出的每个工具发出请求。在 `render` 中使用 `approval` 和 `respondToApproval`,用 `useState` 保存错误,回答时 `await respondToApproval(response)`,失败则显示 `failure.message`。
- `approval.approved` 三态:`undefined` 表示 gate 打开且只有此时可合法调用 `respondToApproval`;`true` 表示已记录为 allow 且服务端正在产生结果;`false` 表示已记录为 deny,runtime 会记录错误结果并暴露 `approval.reason`;`approval.isAutomatic` 为 `true` 表示服务端策略自动批准,应渲染 badge 而不是按钮。
- `respondToApproval` 返回 promise:runtime 接受后 resolve,无法记录时 reject(如 gate 过期或回答被拒);先 await 再禁用控件,让被拒回答仍可重试;用 `toolApprovalAcceptsText(approval)` 判断请求是否接受自由文本,以便决定是否显示文本框。
- 实现 human tools:声明 `execute: humanTool()`,在 `render({ args, result, addResult })` 中,如果已有 `result` 就显示结果,否则显示 UI 并在用户选择后调用 `addResult({ ... })`;`addResult` 必须恰好调用一次;当用户提供工具结果本身时用 human tool,当后端拥有动作且只需要许可时用 approval gate。
- 修复 `humanTool()` 或 `stubTool()` 运行时抛错:说明文件未被编译器处理;添加构建插件并保持 `"use generative"` 为文件第一行。
- 修复工具 UI 不渲染:toolkit 键必须与模型可见工具名完全一致(包括 MCP `prefix`);toolkit 必须挂载,即把 `const config = AuiConfig({ tools: Tools({ toolkit }) })` 作为 `config` 传给 provider;从 module scope 或 `useMemo` 传入稳定 toolkit。
- 修复模型不知道 frontend 或 human 工具:client build 会跳过上传这些 schema,因为它假设后端 import 了同一文件的 server build;没有自己的后端时,用 `aui: { backendless: true }` 编译。
- 修复 frontend tool 结果不回传模型:给 runtime 配置来自 `ai` 的 `sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls`;approval gates 使用 `lastAssistantMessageIsCompleteWithApprovalResponses`。
- 修复 `toModelOutput` 在 round-tripped results 上被忽略:把 tool registry 同时传给 `convertToModelMessages(messages, { tools })` 和 `streamText`。
- 处理工具名构建警告:重复名表示两个 spread fragment 定义了同一键,object spread 保留后者,重命名一个;不能 `makeTool()` 的工具来自不透明 factory 调用,写成 inline object,或 spread 编译器可见的 `defineToolkit(...)` 或 `defineMcpToolkit(...)` fragment。
- 修复 `respondToApproval` rejects:只在 `approval.approved` 为 `undefined` 时合法;对既未声明 `display: "text"` 也未声明 `allowFreeform` 的请求传 `text` 会抛错,传未知 `optionId` 也会抛错。
- 避免 MCP 连接堆积:把 `AISDKToolkit` 放在 module scope,使 clients 跨请求池化;在 `onFinish` 中调用 `aiToolkit.close()`。
- 按主题查阅 references:toolkits.md(工具包、kinds、renderers、`toModelOutput`、`providerOptions`、stubs、拆分合并文件、`backendless`);tool-ui.md(渲染状态、`useToolArgsStatus`、deferred rendering、streaming args、`ToolFallback` 和 `ToolGroup`);human-in-loop.md(human tools、`human()` interrupts、完整 approval gate);mcp-server.md(服务端 MCP servers、`defineMcpToolkit`);mcp-apps.md(用 `McpAppRenderer` 渲染 MCP App `ui://` widgets);webmcp.md(用 `unstable_useWebMcpProvider` 发布 frontend tools 给浏览器 agent);multi-agent.md(工具调用内的 sub-agent conversations);legacy-component-apis.md(已废弃的 `makeAssistantTool` 家族及迁移)。
适用边界 · 注意事项
- 不适用于没有集成 assistant-ui 及其构建插件的项目;这些做法依赖 `@assistant-ui/*`、`defineToolkit`、`AISDKToolkit` 等。
- 如果目标是让模型组合 UI,应使用 generative-ui;如果 MCP servers 由最终用户在浏览器中添加并认证,应使用 react-mcp。
- 如果需要样式化的 `ToolFallback` 和 `ToolGroup`,应使用 elements;如果只是迁移旧工具代码,应参考 update。