技術0 阅读

関数からToolへ:Vercel AI SDKでツール呼び出しの扉を開く

Next.js で API Route を書き、サードパーティの API を呼び出すことに慣れているなら、あとは「自分で API を呼び出せる大規模言語モデルアシスタント」を作るまであと一歩です。これらの関数を Tool としてラップし、モデルにいつ使うかを決めさせるだけです。

この記事では、MCP や Agent フレームワークといった「大きな言葉」には触れず、次のことだけを行います:

「普通の関数を書く」ところから「大規模言語モデルから呼び出せる Tool を書く」ところまでをサポートし、その背後にある考え方を明確にします。

読み終わった後、次のことができるようになるはずです:

1. なぜ「ツール」というレイヤーが必要なのか?

まず単純な要件を想像してみてください:「天気を調べるチャットボット」を作りたいとします。

最もナイーブな方法は次の通りです:

  1. ユーザーが「北京の今日の天気を調べて」と入力する。

  2. サーバー上でこの自然言語を取得する。

  3. 自分で if/else や正規表現を書いて都市名を抽出する。

  4. 天気 API を呼び出して結果を取得する。

  5. 結果をプロンプトに戻し、モデルに一文にまとめさせてユーザーに送信する。

この方法は使えますが、明らかな問題があります:

Tool というレイヤーは、「あなたの if/else + API 呼び出し」をモデルから見える機能に昇格させます。

言い換えれば:

この抽象化こそ、Vercel AI SDK の Tool が行っていることです。

2. Vercel AI SDK の Tool とは何か?

Tool は次のように理解できます:

「取扱説明書付きの関数」。説明書はモデル向けで、関数本体はあなた自身のコード向けです。

Vercel AI SDK では、Tool は通常次の3つの情報を含みます:

  1. description:このツールが何をするかを一文で説明します。

  2. parameters:JSON Schema(または Zod)を使って、必要な入力パラメータを記述します。

  3. execute:実際に実行される関数。HTTP API の呼び出しやデータベースのクエリなどを行います。

おおよそ次のような外見です(疑似コードで、定義を先に感じ取ってください):

ts
import { tool } from 'ai';

const getWeather = tool({
  description: '查询指定城市的实时天气',
  parameters: {
    type: 'object',
    properties: {
      city: { type: 'string', description: '城市名称,比如 Beijing' },
    },
    required: ['city'],
  },
  execute: async ({ city }) => {
    // 这里就是你平时会写的业务代码
    const res = await fetch(`https://api.example.com/weather?city=${city}`);
    return await res.json();
  },
});

その後、モデルを呼び出す際に、この getWeather を AI SDK に渡します:

「都市名を解析する」「いつ API を呼び出すかを決定する」といったロジックを自分で書く必要はなくなり、「主導権」をモデルに委ねます。

3. 最小限の機能する Tool の例

実際に動作する最小限の例を書いてみましょう:現在時刻を調べる

3.1 シンプルな Tool を定義する

Next.js プロジェクトに ai パッケージ(Vercel AI SDK)がインストールされていると仮定します。サーバー側でシンプルな Tool を作成します:

ts
// app/api/chat/tools.ts
import { tool } from 'ai';

export const getCurrentTime = tool({
  description: '获取当前服务器时间(ISO 字符串)',
  parameters: {
    type: 'object',
    properties: {},
  },
  async execute() {
    return {
      now: new Date().toISOString(),
    };
  },
});

ここで注目すべき点がいくつかあります:

3.2 API Route で Tool を使用する

次に、最もシンプルなチャット API を書き、モデルが会話中にこの Tool を呼び出せるようにします。具体的な呼び出しの詳細はバージョンによって若干異なりますが、ここでは「構造」と「考え方」に焦点を当てます。疑似コードは次のようになります:

ts
// app/api/chat/route.ts
import { NextRequest } from 'next/server';
import { streamText } from 'ai'; // 假设使用 AI SDK 的某个调用方法
import { getCurrentTime } from './tools';
import { openai } from '@ai-sdk/openai';

export async function POST(req: NextRequest) {
  const { messages } = await req.json();

  const response = await streamText({
    model: openai('gpt-4o'), // 你选的模型
    messages,
    tools: {
      getCurrentTime,
    },
  });

  return response.toAIStreamResponse();
}

ここでの主要なポイント:

3.3 シンプルなフロントエンドチャットインターフェース

フロントエンドでは useChat のような Hook を使って最小限のチャット UI を作成できますが、この部分はこの記事の主要トピックではないため、詳しくは説明しません。重要なのは、フロントエンドから見ると通常のチャット API と変わらず、モデルが「自分で時刻を調べる」ようになったという点です。

4. Tool と通常の関数の本質的な違い

ここで疑問に思うかもしれません:ルート内で直接 new Date().toISOString() と書けば時刻を調べられるのでは?なぜ Tool を使う必要があるのか?

重要な違いは 誰が「決定」を下すか にあります:

これにより、いくつかの実用的な利点が生まれます:

  1. if/else を減らす

    • 自然言語を解析したり、キーワードをマッチングしたり、分岐を選択したりする必要がなくなります。

    • モデルがコンテキストに基づいて、特定の Tool を使うべきかどうかを自分で判断します。

  2. 複数の Tool の自動連携をサポート

    • 複数のツール(時刻確認、天気確認、スケジュール確認)を定義し、モデルが一回の会話の中で必要に応じて複数のツールを呼び出すことができます。

    • ツールが増えても、大量の「ビジネスルーティングテーブル」を維持する必要はなく、モデル自身が「なんとかする」ようになります。

  3. モデル間の統一性

    • モデルベンダーごとに Tool / Function Calling のインターフェースは異なりますが、AI SDK がこれらの詳細を統一してくれます。

    • Tool 定義を1セット維持するだけで、異なるモデル間を切り替えることができます。

Tool は次のように捉えることができます:

「モデル ↔ あなたのコード」の間に「構造化されたメタデータ付きの関数」というレイヤーを追加し、この連携をインテリジェントでありながら制御可能にします。

5. 使いやすい Tool の設計:パラメータと戻り値

Tool を書く際は、「動けばいい」だけでは不十分です。パラメータと戻り値を適切に設計することで、モデルがより安定して制御可能にツールを使えるようになります。

5.1 パラメータ:モデルが物事を明確に表現できるようにする

パラメータ定義にはいくつかの小さな原則をお勧めします:

例:

ts
const getWeather = tool({
  description: '查询指定城市在指定日期的天气',
  parameters: {
    type: 'object',
    properties: {
      city: {
        type: 'string',
        description: '城市名称,比如 Beijing、Shanghai',
      },
      date: {
        type: 'string',
        description: '日期,格式为 YYYY-MM-DD,比如 2025-05-17',
      },
      unit: {
        type: 'string',
        enum: ['celsius', 'fahrenheit'],
        description: '温度单位,默认 celsius',
      },
    },
    required: ['city', 'date'],
  },
  async execute({ city, date, unit = 'celsius' }) {
    // ...
  },
});

5.2 戻り値:できるだけ構造化し、文字列の一塊にしない

execute 内で直接「東京、今日25度、晴れ」のような文字列を返すこともできますが、これにより柔軟性が大きく損なわれます:

推奨される方法は、明確なオブジェクトを返すことです:

ts
async execute({ city, date, unit = 'celsius' }) {
  const data = await fetchWeather(city, date, unit);
  return {
    city,
    date,
    unit,
    temperature: data.temp,
    condition: data.condition, // sunny, cloudy, etc.
  };
}

こうすることで、モデルは自分で言葉を組み立ててユーザーに応答できるだけでなく、後続のステップでこれらのフィールドを引き続き利用できます(例えば、別の Tool を呼び出すなど)。

6. 複数の Tool を一度に接続する:モデル自身に選ばせる

これまでは getCurrentTime だけを使っていましたが、ここに getWeather を追加して、まとめてモデルに渡してみましょう。

ts
// app/api/chat/tools.ts
export const tools = {
  getCurrentTime,
  getWeather,
};

モデルを呼び出す際:

ts
const response = await streamText({
  model: openai('gpt-4o'),
  messages,
  tools,
});

このとき、ユーザーが次のように言ったとします:

「明日の上海の天気を教えて。もし晴れなら、ついでに今の時刻も教えて。」

モデルは次のようなことを行う可能性があります(論理レベル):

  1. まず getWeather を呼び出して、「晴れかどうか」を判断する。

  2. 次に getCurrentTime を呼び出して、現在時刻を取得する。

  3. 2つの呼び出し結果を統合した最終応答を生成する。

この一連のロジックのために「ビジネスフローチャート」を書く必要は一切ありません。必要なのは:

これこそが「ツール呼び出し」の真の力です:あなたは「フローを書く」ことから「能力を定義する」ことへと移行します。

7. この記事のまとめ:まず Tool を使いこなしてから、プロトコル層を考える

この記事では、次の3つのことだけを行いました:

  1. Tool を概念的に分解:それは「取扱説明書付きの関数」であり、説明書はモデル向けです。

  2. 最小限の Tool の例を作成し、Next.js API Route でそれを使用する方法を示しました。

  3. パラメータと戻り値の設計方法について説明し、モデルがツールをより使いやすくする方法を述べました。

Vercel AI SDK の Tool だけで、次のようなものを作成できます:

そしてこの段階では、MCP などのプロトコル層を全く気にする必要はありません。これらは主に「ホストやプロジェクトをまたいでツールを再利用する」問題を解決するものであり、ツールの数や呼び出し環境が複雑になってからゆっくり導入するのに適しています。

次の記事では、「Tool だけを使う」ことをベースに、ツールが増え、呼び出し環境が複雑になったときに、Tool だけではどのような限界や課題に直面するか、そしてなぜ MCP のようなプロトコルが登場するのかについて、具体的に掘り下げます。

共有

共有

この記事を共有します。