Vercel AI SDK 6 : AI SDK Core – ツール呼び出し

基礎でカバーされたように、ツールは特定のタスクを遂行するためにモデルにより呼び出されるオブジェクトです。AI SDK コアのツールは幾つかのコア要素を含みます。

Vercel AI SDK 6 : AI SDK Core – ツール呼び出し

作成 : Masashi Okumura (@classcat.com)
作成日時 : 01/29/2026
バージョン : ai@6.0.57

* 本記事は ai-sdk.dev/docs の以下のページを参考にしています :

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

 

Vercel AI SDK 6.x : AI SDK Core – ツール呼び出し

基礎でカバーされたように、ツールは特定のタスクを遂行するためにモデルにより呼び出されるオブジェクトです。AI SDK コアの ツール は幾つかのコア要素を含みます :

  • description: ツールが選択される際に影響を与える可能性がある、(オプションの) ツールの説明。

  • inputSchema: 入力パラメータを定義する Zod スキーマまたは JSON スキーマ。スキーマは LLM により消費され、LLM ツール呼び出しを検証するためにも使用されます。

  • execute: ツール呼び出しからの入力で呼び出される、オプションの非同期関数。RESULT 型 (generic 型) の値を生成します。ツール呼び出しを同じプロセスで実行するのではなく、クライアントやキューに転送したい場合もあるので、オプションです。

  • strict: (オプション, boolean) プロバイダーによりサポートされる場合、厳密なツール呼び出しを有効にします。

 
generateText と streamText の tools パラメータは、キーとしてツール名を、値としてツールを備えたオブジェクトです :

Gateway

import { z } from 'zod';
import { generateText, tool, stepCountIs } from 'ai';

const result = await generateText({
  model: "openai/gpt-4o-mini",
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
  },
  stopWhen: stepCountIs(5),
  prompt: 'What is the weather in San Francisco?',
});

ℹ️ When a model uses a tool, it is called a “tool call” and the output of the tool is called a “tool result”.

ツール呼び出しはテキスト生成のみに制限されません。ユーザインターフェイス(Generative UI) のレンダリングに使用することもできます。

 

Strict モード

有効にすると、厳密なツール呼び出しを言語モデルプロバイダーは、定義された inputSchema に従って有効なツール呼び出しのみを生成します。これはツール呼び出しの信頼性を向上させます。ただし、すべてのスキーマが strict モードでサポートされるわけではなく、何がサポートされるかは特定のプロバイダーに依存します。

デフォルトでは、strict モードは無効です。ツール毎に strict: true を設定することで有効にできます :

tool({
  description: 'Get the weather in a location',
  inputSchema: z.object({
    location: z.string(),
  }),
  strict: true, // Enable strict validation for this tool
  execute: async ({ location }) => ({
    // ...
  }),
});

 

入力例

ツールに対して入力例を指定して、入力データがどのように構造化される必要があるかをモデルにガイドする支援を行うことができます。プロバイダーによりサポートされる場合、JSON スキーマ自体が意図した使用方法を完全には指定していない場合や、オプションの値がある場合に、入力例は役立ちます。

tool({
  description: 'Get the weather in a location',
  inputSchema: z.object({
    location: z.string().describe('The location to get the weather for'),
  }),
  inputExamples: [
    { input: { location: 'San Francisco' } },
    { input: { location: 'London' } },
  ],
  execute: async ({ location }) => {
    // ...
  },
});

ℹ️ Only the Anthropic providers supports tool input examples natively. Other providers ignore the setting.

 

ツール実行の承認

デフォルトでは、execute 関数を備えたツールは、モデルがそれらを呼び出した時、自動的に実行されます。needsApproval を設定することで、実行前に承認を要求することができます :

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

const runCommand = tool({
  description: 'Run a shell command',
  inputSchema: z.object({
    command: z.string().describe('The shell command to execute'),
  }),
  needsApproval: true,
  execute: async ({ command }) => {
    // your command execution logic here
  },
});

これは、コマンドの実行、支払い処理、データ変更、その他潜在的に危険なアクションのような、機密性の高い操作を遂行するツールのために有用です。

 

How It Works

ツールが承認を必要とする場合、generateText と streamText は実行を一時停止しません。代わりに、tool-approval-request パートを完了して、結果のコンテンツで返します。つまり、承認フローはモデルへの 2 回の呼び出しを必要とします。最初は承認リクエストを返し、(承認レスポンスを受信後) 2 回目はツールを実行するか、承認が拒否されたことをモデルに伝えます。

完全なフローは以下になります :

  1. needsApproval: true が設定されたツールで generateText を呼び出します

  2. モデルがツール呼び出しを生成します

  3. generateText は result.content に tool-approval-request パートを伴って返します

  4. アプリケーションは承認を要求してユーザの決定を収集します

  5. メッセージ配列に tool-approval-response を追加します

  6. 更新されたメッセージで generateText を再度呼び出します

  7. 承認されれば、ツールが実行されて結果を返します。拒否されれば、モデルは拒否を確認し、それに応じて応答します。

 

承認リクエストの処理

generateText または streamText を呼び出した後、tool-approval-request 部を result.content で確認します :

Gateway

import { type ModelMessage, generateText } from 'ai';

const messages: ModelMessage[] = [
  { role: 'user', content: 'Remove the most recent file' },
];
const result = await generateText({
  model: "openai/gpt-4o-mini",
  tools: { runCommand },
  messages,
});

messages.push(...result.response.messages);

for (const part of result.content) {
  if (part.type === 'tool-approval-request') {
    console.log(part.approvalId); // Unique ID for this approval request
    console.log(part.toolCall); // Contains toolName, input, etc.
  }
}

応答するには、tool-approval-response を作成して、メッセージに追加します :

import { type ToolApprovalResponse } from 'ai';

const approvals: ToolApprovalResponse[] = [];

for (const part of result.content) {
  if (part.type === 'tool-approval-request') {
    const response: ToolApprovalResponse = {
      type: 'tool-approval-response',
      approvalId: part.approvalId,
      approved: true, // or false to deny
      reason: 'User confirmed the command', // Optional context for the model
    };
    approvals.push(response);
  }
}

// add approvals to messages
messages.push({ role: 'tool', content: approvals });

それから更新されたメッセージで generateText を再度呼び出します。承認されれば、ツールは実行されます。拒否されれば、モデルは拒否を受信し、それに応じて応答できます。

ℹ️ When a tool execution is denied, consider adding a system instruction like “When a tool execution is not approved, do not retry it” to prevent the model from attempting the same call again.

 

動的承認

非同期関数を提供することで、ツールの入力に基づいて承認の決定を行うことができます :

const paymentTool = tool({
  description: 'Process a payment',
  inputSchema: z.object({
    amount: z.number(),
    recipient: z.string(),
  }),
  needsApproval: async ({ amount }) => amount > 1000,
  execute: async ({ amount, recipient }) => {
    return await processPayment(amount, recipient);
  },
});

この例では、$1000 を超える取引きのみ承認を必要とします。より小さい取引きは自動的に実行されます。

 

useChat を使用したツール実行承認

useChat を使用する場合、承認フローは UI 状態を通して処理されます。See Chatbot Tool Usage for details on handling approvals in your UI with addToolApprovalResponse.

 

マルチステップ呼び出し (stopWhen の使用)

stopWhen 設定を使用すると、generateText と streamText でマルチステップ呼び出しを有効にできます。stopWhen が設定されてモデルがツール呼び出しを生成する場合、AI SDK は、それ以上のツール呼び出しがないか停止条件が満たされるまで、ツールの結果を渡して新しい生成をトリガーします。

ℹ️ The stopWhen conditions are only evaluated when the last step contains tool results.

デフォルトでは、generateText または streamText を使用する場合、単一の生成をトリガーします。これは、レスポンスを生成するのにモデルの訓練データに依存できるような、多くのユースケースについてうまく機能します。けれども、ツールを提供する場合、モデルは通常のテキスト応答を生成するか、ツール呼び出しを生成するか、選択肢が増えます。モデルがツール呼び出しを生成する場合、その生成は完了してそのステップは終了します。

ツールが実行された後に、モデルにテキストを生成させたい場合があります、これはユーザ・クエリーのコンテキストでツールの結果を要約するためです。多くの場合、モデルが単一のレスポンス内で複数のツールを使用することを望む場合もあります。これがマルチステップの呼び出しが必要となるところです。

マルチステップ呼び出しは、人間との会話と同様の方法で考えることができます。ユーザが質問をする際、共通知識 (モデルのトレーニングデータ) に必要条件となる知識を持っていない場合、回答を提供する前に情報を検索する (ツールを使用する) 必要があるかもしれません。同様に、モデルは質問に答えるために必要な情報を取得するためにツールを呼び出す必要があるかもしれません。ここで、各生成 (ツール呼び出しまたはテキスト生成) がステップです。

 

以下の例では、2 つのステップがあります :

  1. ステップ 1

    1. プロンプト ‘What is the weather in San Francisco?’ がモデルに送信されます。
    2. モデルはツール呼び出しを生成します。
    3. ツール呼び出しが実行されます。

  2. ステップ 2

    1. ツールの結果がモデルに送信されます。
    2. モデルはツールの結果を考慮してレスポンスを生成します。

Gateway

import { z } from 'zod';
import { generateText, tool, stepCountIs } from 'ai';

const { text, steps } = await generateText({
  model: "openai/gpt-4o-mini",
  tools: {
    weather: tool({
      description: 'Get the weather in a location',
      inputSchema: z.object({
        location: z.string().describe('The location to get the weather for'),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 72 + Math.floor(Math.random() * 21) - 10,
      }),
    }),
  },
  stopWhen: stepCountIs(5), // stop after a maximum of 5 steps if tools were called
  prompt: 'What is the weather in San Francisco?',
});

 

ステップ

中間的なツール呼び出しと結果にアクセスするには、結果オブジェクトの steps、または streamText onFinish コールバックを使用することができます。それは各ステップのすべてのテキスト、ツール呼び出し、ツールの結果等を含みます。

 
例: すべてのステップからツールの結果を抽出

Gateway

import { generateText } from 'ai';

const { steps } = await generateText({
  model: "openai/gpt-4o-mini",
  stopWhen: stepCountIs(10),
  // ...
});

// extract all tool calls from the steps:
const allToolCalls = steps.flatMap(step => step.toolCalls);

 

onStepFinish コールバック

generateText または streamText を使用する場合、ステップが終了したとき、つまり、ステップについてのすべてのテキスト差分、ツール呼び出し、ツールの結果が利用可能になったときにトリガーされる onStepFinish コールバックを提供することができます。複数のステップがある場合、コールバックは各ステップ毎にトリガーされます。

import { generateText } from 'ai';

const result = await generateText({
  // ...
  onStepFinish({ text, toolCalls, toolResults, finishReason, usage }) {
    // your own logic, e.g. for saving the chat history or recording usage
  },
});

 

レスポンス・メッセージ

生成されたアシスタントメッセージとツールメッセージを会話履歴に追加することは、特にマルチステップのツール呼び出しを使用している場合には、一般的なタスクです。

generateText と streamText はどちらも response.messages プロパティを持ち、これを使用してアシスタントメッセージとツールメッセージを会話履歴に追加することができます。それはまた streamText の onFinish コールバックでも利用可能です。

response.messages プロパティは、会話履歴に追加できる ModelMessage オブジェクトの配列を含みます :

import { generateText, ModelMessage } from 'ai';

const messages: ModelMessage[] = [
  // ...
];

const { response } = await generateText({
  // ...
  messages,
});

// add the response messages to your conversation history:
messages.push(...response.messages); // streamText: ...((await response).messages)

 

動的ツール

AI SDK Core は、コンパイル時にツールスキーマが不明なシナリオ向けに動的ツールをサポートしています。これは以下の場合に便利です :

  • スキーマのない MCP (Model Context Protocol) ツール

  • 実行時のユーザ定義関数

  • 外部ソースからロードされたツール

 

dynamicTool の使用

dynamicTool ヘルパーは不明な入力/出力タイプを持つツールを作成します :

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

const customTool = dynamicTool({
  description: 'Execute a custom function',
  inputSchema: z.object({}),
  execute: async input => {
    // input is typed as 'unknown'
    // You need to validate/cast it at runtime
    const { action, parameters } = input as any;

    // Execute your dynamic logic
    return { result: `Executed ${action}` };
  },
});

 

型安全な処理

静的と動的ツールの両方を使用する場合、型の絞り込みのために dynamic フラグを使用します :

Gateway

const result = await generateText({
  model: "openai/gpt-4o-mini",
  tools: {
    // Static tool with known types
    weather: weatherTool,
    // Dynamic tool
    custom: dynamicTool({
      /* ... */
    }),
  },
  onStepFinish: ({ toolCalls, toolResults }) => {
    // Type-safe iteration
    for (const toolCall of toolCalls) {
      if (toolCall.dynamic) {
        // Dynamic tool: input is 'unknown'
        console.log('Dynamic:', toolCall.toolName, toolCall.input);
        continue;
      }

      // Static tool: full type inference
      switch (toolCall.toolName) {
        case 'weather':
          console.log(toolCall.input.location); // typed as string
          break;
      }
    }
  },
});

 

以上