tools について
assistant-ui tools は、モデルが呼び出すツールとその UI を作る開発者向けのツールキットシステムです。コンパイラーが `"use generative"` ファイルをサーバー用とクライアント用に分割し、人による操作や MCP ツールキットにも対応します。
主な機能
- 01サーバー用・クライアント用ビルドの生成
- 02バックエンド、フロントエンド、人間、Provider のツール種別
- 03ツール呼び出しとストリーミング状態の描画
- 04人間の入力と承認ゲート
- 05MCP ツールキットとアプリへの対応
活用シーン
- 01チャットモデル向けのツール定義
- 02チャット UI でのツール呼び出し表示
- 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 ツール
最新の API については、常に assistant-ui.com/llms.txt を参照してください。
ツールとは、モデルが呼び出せる名前付きの機能です。assistant-ui では、ツールをツールキット内で宣言します。ツールキットは、キーがモデルから見えるツール名であり、値がスキーマ、エグゼキューター、レンダラーを保持するマップです。サポートされている作成方法は、ビルドプラグインによってコンパイルされる "use generative" ファイルです。これは 1 つのファイルをサーバービルド(スキーマとバックエンドエグゼキューター)とクライアントビルド(スキーマとレンダラーとブラウザーエグゼキューター)に分割します。
リファレンス
- ./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 App のui://ウィジェットをレンダリングする - ./references/webmcp.md --
unstable_useWebMcpProviderでフロントエンドツールをブラウザーエージェントに公開する - ./references/multi-agent.md -- ツール呼び出し内でのサブエージェント会話
- ./references/legacy-component-apis.md -- 非推奨の
makeAssistantToolファミリーと、そこからの移行方法
作成モデル
1. ビルドプラグインを追加する
コンパイラーがなければ、このディレクティブは何も行いません。
import { withAui } from "@assistant-ui/next";
export default withAui({
/* your Next config */
});
Vite と TanStack Start では、代わりに @assistant-ui/vite の aui() を plugins に追加します。Expo と bare React Native では、@assistant-ui/metro の withAui で Metro 設定をラップします。3 つとも 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 | server-only でガードされた、スキーマと execute | スキーマと render |
async () => { "use client"; ... } | frontend | スキーマのみ | スキーマと execute、さらに render または renderText |
humanTool() | human | スキーマのみ | スキーマと render |
stubTool() | frontend、executor は実行時に提供される | スキーマのみ | スキーマと render または renderText |
providerTool({ ... }) | provider | スキーマと provider 設定 | スキーマと 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 | frontend executor からの一時停止された human() リクエスト |
approval | object | undefined | サーバー側のゲート: id、approved?、options?、optionId?、resolution? |
addResult | (result) => void | UI から human ツールを完了させる |
resume | (payload: unknown) => void | interrupt に応答する |
respondToApproval | (response: ToolApprovalResponse) => Promise<void> | 承認ゲートに応答する |
コンポーネントの代わりに 1 行のステータスを使うには、renderText に running と complete の値を設定します。それぞれ文字列か、({ args, result }) を引数とする関数です。UI を折りたたまれたツールグループの外に保つには、エントリで display: "standalone" を設定します。レンダラーを持たないツールは 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 には 3 つの状態があります。undefined はゲートが開いていることを意味し、respondToApproval が合法となる唯一の状態です。true は決定が許可として記録され、サーバーが結果を生成していることを意味します。false は拒否として記録されたことを意味し、ランタイムはエラー結果を記録して approval.reason を公開します。approval.isAutomatic は、ユーザーではなくサーバー側のポリシーが決定を下した場合に true になるため、ボタンではなくバッジをレンダリングしてください。
respondToApproval は、ランタイムが応答を受け入れると解決し、記録できなかった場合(たとえばゲートの期限切れや回答の拒否)に拒否されるプロミスを返します。拒否された応答でもリクエストを再試行できるよう、コントロールを無効化する前にこれを await してください。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 は正確に 1 回だけ呼び出してください。ユーザーがツール結果そのものを提供する場合はヒューマンツールを、バックエンドがアクションを所有し許可だけを必要とする場合は承認ゲートを使用してください。
よくある落とし穴
humanTool() または stubTool() が実行時にスローする
- そのファイルがコンパイラーによって処理されていません。ビルドプラグインを追加し、
"use generative"をファイルの先頭行として維持してください。
ツール UI がまったくレンダリングされない
- ツールキットのキーは、MCP の
prefixを含め、モデルから見えるツール名と完全に一致している必要があります。 - ツールキットがマウントされている必要があります。
const config = AuiConfig({ tools: Tools({ toolkit }) })をプロバイダーのconfigとして渡してください。モジュールスコープまたはuseMemoから安定したツールキットを渡してください。
モデルがフロントエンドツールやヒューマンツールを認識しない
- クライアントビルドは、バックエンドが同じファイルのサーバービルドをインポートしたと想定するため、それらのスキーマのアップロードをスキップします。自分のバックエンドがない場合は、
aui: { backendless: true }を指定してコンパイルしてください。
フロントエンドツールの結果がモデルに届かない
- ランタイムを
aiのsendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCallsで設定し、承認ゲートにはlastAssistantMessageIsCompleteWithApprovalResponsesを設定してください。
ラウンドトリップされた結果で toModelOutput が無視される
- ツールレジストリを
streamTextだけでなくconvertToModelMessages(messages, { tools })にも渡してください。
ツール名に関するビルド警告
- 名前の重複は、2 つのスプレッドフラグメントが同じキーを定義し、オブジェクトスプレッドが後のものを保持することを意味します。どちらか一方のエントリの名前を変更してください。
makeTool()できないツールは、不透明なファクトリー呼び出しに由来します。インラインオブジェクトとして記述するか、コンパイラーから見えるdefineToolkit(...)またはdefineMcpToolkit(...)フラグメントをスプレッドしてください。
respondToApproval が拒否される
- これは
approval.approvedがundefinedである間だけ合法です。display: "text"もallowFreeformも宣言していないリクエストへのtext回答はスローされ、不明なoptionIdも同様です。
MCP 接続がたまる
AISDKToolkitをモジュールスコープに保持して、クライアントがリクエストをまたいでプールされるようにし、onFinishからaiToolkit.close()を呼び出してください。