mcpField
V
vercel-ai-sdk

vercel-ai-sdk

github.com/wsimmonds開発109339 回インストール53 週間前に更新
リポジトリ

vercel-ai-sdk について

Vercel AI SDK v5 を使う開発者向けに、チャット、テキスト生成、ツール呼び出し、MCP 連携の実装方法を案内します。v5 の API パターン、依存関係の管理、ビルドとテストの反復確認を扱います。

主な機能

  1. 01チャットとストリーミング応答の実装
  2. 02ツール呼び出しと埋め込み生成
  3. 03v4 から v5 への移行ガイド
  4. 04依存関係の導入とパッケージマネージャーの確認
  5. 05ビルド、Lint、テストのデバッグ

活用シーン

  1. 01useChat によるチャット UI の実装
  2. 02生成またはストリーミング API の作成
  3. 03セマンティック検索用の埋め込み生成
  4. 04プロジェクトの AI SDK v5 への移行

MCP 対応

MCP サーバーとの連携方法を扱います。このスキル自体が MCP サーバーを提供するとは記載されていません。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/wsimmonds/claude-nextjs-skills --skill vercel-ai-sdk

インストール後、Claude Code で/vercel-ai-sdkを入力して読み込まれたか確認します。

詳細

Vercel AI SDK v5 実装ガイド

このスキルを使用する場面

次の場合にこのスキルを使用します:

  • useChat フックを使用して AI チャットインターフェースを実装する
  • AI の応答を生成またはストリーミングする API ルートを作成する
  • ツール呼び出し/関数呼び出し機能を追加する
  • セマンティック検索や RAG 用のテキスト埋め込みを生成する
  • AI SDK v4 から v5 に移行する
  • Model Context Protocol(MCP)サーバーを統合する
  • ストリーミング応答やメッセージの永続化を扱う

構造化された実装ワークフロー

タスクの要件を把握する - 必要な AI 機能(チャット、生成、ツール、埋め込み)を特定する - クライアント側(useChat)またはサーバー側(API ルート)のどちらで実装するかを判断する - ストリーミング応答と非ストリーミング応答のどちらが必要かを確認する - モデルプロバイダー(OpenAI、Anthropic など)を確認する 不明な場合は現在の API パターンを確認する - API パターンが不明な場合は、WebFetch を使用して https://ai-sdk.dev/docs/ を確認する - プロバイダーのモデル指定形式を確認する - 複雑な機能の関数シグネチャを確認する 正しい v5 のパターンを使用して実装する - 文字列ベースのモデル指定('provider/model-id')を使用する - チャットでは、append ではなく sendMessage を使用し、parts ベースのメッセージを使用する - ツールでは、'ai' の tool() ヘルパーをインポートして使用する必要があります。inputSchema(parameters ではありません)を使用し、zod を使用する必要があります - ストリーミングでは、toUIMessageStreamResponse() または toTextStreamResponse() を使用する - 埋め込みでは、provider.textEmbeddingModel() を使用する TypeScript の型が正しいことを確認する - 'ai' パッケージから適切にインポートされているか確認する - メッセージ型(useChat には UIMessage)を確認する - ツールのパラメーター型が正しく推論されることを確認する - 非同期関数に明示的な型を追加する 不足している依存関係を正しいパッケージマネージャーでインストールする - **重要:まず、プロジェクトで使用されているパッケージマネージャーを特定する** * ロックファイルを確認する:pnpm-lock.yaml → pnpm を使用、package-lock.json → npm を使用、yarn.lock → yarn を使用、bun.lockb → bun を使用 * pnpm-lock.yaml が存在する場合は、必ず pnpm を使用する(npm は使用しない) - インポートされているすべてのパッケージがインストール済みか確認する - "Module not found" でビルドが失敗した場合は、エラーからパッケージ名を特定する - package.json の dependencies にパッケージを追加する - 正しいパッケージマネージャーを使用してインストールする: * pnpm-lock.yaml が存在する場合:`pnpm install [package]` または `pnpm add [package]` * package-lock.json が存在する場合:`npm install [package]` * yarn.lock が存在する場合:`yarn add [package]` * bun.lockb が存在する場合:`bun install [package]` または `bun add [package]` - ビルドを再実行して、インストールが成功したことを確認する **間違ったパッケージマネージャーは絶対に使用しないでください!** - プロジェクトが pnpm を使用しているのに npm を使うと、pnpm-lock.yaml と並んで package-lock.json が作成されます - これにより依存関係のバージョンが一致しなくなり、ビルドが壊れます - 既存のロックファイルを必ず確認し、対応するパッケージマネージャーを使用してください
  「Module not found」エラーを環境の問題として扱わないでください
  正しいパッケージマネージャーを使用して、必要なパッケージを必ずインストールしてください

  必要となる一般的なパッケージ:
  - ai(AI SDK コア)
  - @ai-sdk/openai(OpenAI プロバイダー)
  - @ai-sdk/anthropic(Anthropic プロバイダー)
  - @modelcontextprotocol/sdk(MCP 統合)
  - zod(ツールのスキーマ用)
</critical>
ビルドを実行し、成功するまでエラーをすべて修正します - 実行:npm run build(または bun run build) - ビルドに失敗した場合は、エラーメッセージ全体を注意深く読みます - よくある修正方法: * "Module not found" → パッケージをインストールします(ステップ 5 に戻ります) * 型エラー → TypeScript の型を修正します * 設定エラー → next.config.js/ts を確認します * フレームワークのエラー → エラーを調査し、別の方法を試します - 修正を適用し、ビルドを再実行します - ビルドが成功するまで繰り返します 「ビルドは失敗するが、コードは正しい」で決して止めないでください デバッグせずに「環境」や「フレームワークのバグ」のせいにしてはいけません ビルドが成功するまで繰り返し続けてください フレームワークのエラーが発生した場合は、調査し、回避策を試し、デバッグしてください ビルドが成功するまで諦めないでください テストを実行し、すべての失敗を成功するまでデバッグします - 実行:npm run test(または bun run test) - テストに失敗した場合は、エラーメッセージ全体を注意深く読みます - 失敗をデバッグします: * テストで期待されている内容と、実際に提供している内容を確認します * テストのアサーションを確認します * 実装が要件を満たしているか確認します * 型の不一致や不足しているプロパティがないか確認します - 実装を修正します - テストを再実行します - すべてのテストが成功するまで繰り返します デバッグせずにテストの失敗を受け入れてはいけません 調査せずに「テストフレームワークのバグ」のせいにして失敗を片付けてはいけません テストの失敗はそれぞれ何かを示しています。それに耳を傾けてください すべてのテストが成功するまでデバッグを続けてください テストの失敗は許容できません。実装が不完全であることを意味します すべてのチェックが成功することを確認します - 実行:npm run build && npm run lint && npm run test - 3 つすべてが成功する必要があります:✅ ビルド、✅ Lint、✅ テスト - いずれかが失敗した場合は、戻って修正します - すべてのチェックが成功するまで、タスクが完了したと判断してはいけません タスクが完了するのは、次のすべてを満たした場合のみです: - ✅ ビルドが成功 - ✅ Lint が成功 - ✅ テストが成功
  「コードは正しい」だけでは不十分です
  すべてが成功した状態を達成する必要があります
  これが自律型エージェントの意味するところです
</critical>

⚠️ 自律型エージェントとしての心構え

あなたはコードを書くだけではなく、タスクを自律的に完了させます。

つまり、次のことを行います:

  1. ✅ 正しい実装を書く
  2. ✅ 必要な依存関係をインストールする
  3. ✅ ビルドを実行し、エラーをすべて修正する
  4. ✅ テストを実行し、失敗をすべてデバッグする
  5. ✅ すべてが成功するまで繰り返す
  6. ✅ 言い訳をしたり、諦めたりしない
よくある失敗パターン:避けるべきこと

❌ 誤り:「コードは正しいが、パッケージがインストールされていない。これは環境の問題です」 ✅ 正しい:「パッケージが不足しているためビルドに失敗しました。今から npm install [package] でインストールします」

❌ 誤り:「テストは成功したが、ビルドは失敗した。自分の問題ではありません」 ✅ 正しい:「ビルドに失敗しています。今からエラーをデバッグして修正します」

❌ 誤り:「フレームワークのバグがあり、修正できません」 ✅ 正しい:「フレームワークのエラーを検出しました。解決策が見つかるまで問題を調査し、回避策を試し、デバッグします」

❌ 誤り:「実装は完了しました」(テストが失敗している状態で) ✅ 正しい:「テストが失敗しています。すべて成功するまでデバッグして修正します」

依存関係のインストールワークフロー

「Module not found」エラーが発生した場合:

  1. 最初にパッケージマネージャーを検出する - ロックファイルを確認します:

    ls -la | grep -E "lock"
    # Look for: pnpm-lock.yaml, package-lock.json, yarn.lock, bun.lockb
    
  2. インポート文からパッケージを特定する

    Error: Cannot find module '@ai-sdk/openai'
    Import: import { openai } from '@ai-sdk/openai'
    Package needed: @ai-sdk/openai
    
  3. 正しいパッケージマネージャーでインストールする

    # If pnpm-lock.yaml exists (MOST COMMON for Next.js evals):
    pnpm install @ai-sdk/openai
    # or
    pnpm add @ai-sdk/openai
    
    # If package-lock.json exists:
    npm install @ai-sdk/openai
    
    # If yarn.lock exists:
    yarn add @ai-sdk/openai
    
    # If bun.lockb exists:
    bun install @ai-sdk/openai
    
  4. ビルドを再実行して確認する

    npm run build
    # or pnpm run build, yarn build, bun run build
    
  5. 新たに発生したエラーを修正する

⚠️ 重大な警告: 間違ったパッケージマネージャーを使用すると(例:プロジェクトで pnpm を使用しているのに npm を使う)、次の問題が発生します:

  • 競合するロックファイルがもう1つ作成される
  • 異なるバージョンの依存関係がインストールされる
  • 依存関係のバージョン不整合が発生する
  • 「Cannot read properties of null」のような不可解なエラーによりビルドが失敗する
ビルドエラーのデバッグワークフロー

ビルドが失敗した場合:

  1. エラーメッセージ全体を読む - 斜め読みしないでください
  2. 根本原因を特定する:
    • Module not found → パッケージをインストールする
    • Type error → 型を修正する
    • Config error → 設定ファイルを確認する
    • Next.js error → 調査して、別の方法を試す
  3. 修正を適用する
  4. ビルドを再実行する
  5. ビルドが成功するまで繰り返す
テスト失敗のデバッグワークフロー

テストが失敗した場合:

  1. テストエラー全体を読む - 何が期待されているのかを理解します
  2. 期待される動作と実際の動作を比較する
  3. テストのアサーションに照らして、実装を確認する
  4. コード内の問題を修正する
  5. テストを再実行する
  6. すべてのテストが成功するまで繰り返す
成功条件

次の条件をすべて満たした場合にのみ、タスクは完了です:

  • ✅ ビルドが成功する(npm run build が成功する)
  • ✅ Lint が成功する(npm run lint が成功する)
  • ✅ テストが成功する(npm run test が成功する)

「コードは正しい」で決して止めず、すべてのチェックが成功する状態を達成してください!

⚠️ 重大:ツール呼び出し API - 必ず tool() ヘルパーを使用

ツール呼び出しを実装する場合、必ず 'ai' パッケージの tool() ヘルパー関数を使用してください。

❌ 誤り - プレーンなオブジェクト(ビルドエラーが発生します)
// DO NOT DO THIS - This pattern is INCORRECT
import { z } from 'zod';

tools: {
  myTool: {
    description: 'My tool',
    parameters: z.object({...}),  // ❌ WRONG - "parameters" doesn't exist in v5
    execute: async ({...}) => {...},
  }
}

次のエラーが発生します: Type '{ description: string; parameters: ... }' is not assignable to type '{ inputSchema: FlexibleSchema<any>; ... }'

✅ 正しい - tool() ヘルパーを使用(必須)
// ALWAYS DO THIS - This is the ONLY correct pattern
import { tool } from 'ai';  // ⚠️ MUST import tool
import { z } from 'zod';

tools: {
  myTool: tool({  // ⚠️ MUST wrap with tool()
    description: 'My tool',
    inputSchema: z.object({...}),  // ⚠️ MUST use "inputSchema" (not "parameters")
    execute: async ({...}) => {...},
  }),
}
ツール呼び出しチェックリスト

ツールを実装する前に、次の項目を確認してください:

  • 'ai' パッケージから tool をインポートした:import { tool } from 'ai';
  • ツール定義を tool({ ... }) でラップした
  • inputSchema プロパティを使用した(parameters は使用しない)
  • zod スキーマを使用した:z.object({ ... })
  • 非同期コールバックを使った execute 関数を定義した
  • ツールの description 文字列を追加した

⚠️ 重大:v4 から v5 へのよくある破壊的変更

1. useChat フックの変更

❌ 誤り(v4 のパターン):

const { messages, input, setInput, append } = useChat();

// Sending message
append({ content: text, role: 'user' });

✅ 正しい(v5 のパターン):

const { messages, sendMessage } = useChat();
const [input, setInput] = useState('');

// Sending message
sendMessage({ text: input });
2. メッセージの構造

❌ 誤り(v4 のシンプルなコンテンツ):

<div>{message.content}</div>

✅ 正しい(v5 の parts ベース):

<div>
  {message.parts.map((part, index) =>
    part.type === 'text' ? <span key={index}>{part.text}</span> : null
  )}
</div>
3. モデルの指定

✅ 推奨:文字列ベース(v5 で推奨):

import { generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-4o',  // String format
  prompt: 'Hello',
});

✅ こちらも使用可能:関数ベース(レガシーサポート):

import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-4o'),  // Function format
  prompt: 'Hello',
});

コア API リファレンス

1. generateText - 非ストリーミングのテキスト生成

目的: 非対話型のユースケース(メールの下書き、要約、ツールを使うエージェント)向けにテキストを生成します。

シグネチャ:

import { generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-4o',           // String format: 'provider/model-id'
  prompt: 'Your prompt here',        // User input
  system: 'Optional system message', // Optional system instructions
  tools?: { ... },                   // Optional tool calling
  maxSteps?: 5,                      // For multi-step tool calling
});

戻り値:

{
  text: string;              // Generated text output
  toolCalls: ToolCall[];     // Tool invocations made
  finishReason: string;      // Why generation stopped
  usage: TokenUsage;         // Token consumption
  response: RawResponse;     // Raw provider response
  warnings: Warning[];       // Provider-specific alerts
}

例:

// app/api/generate/route.ts
import { generateText } from 'ai';

export async function GET() {
  const result = await generateText({
    model: 'anthropic/claude-4-sonnet',
    prompt: 'Why is the sky blue?',
  });

  return Response.json({ text: result.text });
}
2. streamText - ストリーミングのテキスト生成

目的: 対話型チャットアプリケーション向けにレスポンスをストリーミングします。

シグネチャ:

import { streamText } from 'ai';

const result = streamText({
  model: 'openai/gpt-4o',
  prompt: 'Your prompt here',
  system: 'Optional system message',
  messages?: ModelMessage[],  // For chat history
  tools?: { ... },
  onFinish?: async (result) => { ... },
  onError?: async (error) => { ... },
});

戻り値のメソッド:

// For chat applications with useChat hook
result.toUIMessageStreamResponse();

// For simple text streaming
result.toTextStreamResponse();

例 - チャット API ルート:

// app/api/chat/route.ts
import { streamText, convertToModelMessages } from 'ai';
import type { UIMessage } from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: 'openai/gpt-4o',
    system: 'You are a helpful assistant.',
    messages: convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse();
}
3. useChat フック - クライアント側チャットインターフェース

目的: ストリーミングに対応した対話型チャット UI を構築します。

シグネチャ:

import { useChat } from 'ai/react';

const {
  messages,        // Array of UIMessage with parts-based structure
  sendMessage,     // Function to send messages (replaces append)
  status,          // 'submitted' | 'streaming' | 'ready' | 'error'
  stop,            // Abort current streaming
  regenerate,      // Reprocess last message
  setMessages,     // Manually modify history
  error,           // Error object if request fails
  reload,          // Retry after error
} = useChat({
  api: '/api/chat',  // API endpoint
  onFinish?: (message) => { ... },
  onError?: (error) => { ... },
});

完全な例:

'use client';

import { useChat } from 'ai/react';
import { useState } from 'react';

export default function ChatPage() {
  const { messages, sendMessage, status } = useChat();
  const [input, setInput] = useState('');

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (!input.trim()) return;

    sendMessage({ text: input });
    setInput('');
  };

  return (
    <div>
      <div>
        {messages.map((message) => (
          <div key={message.id}>
            <strong>{message.role}:</strong>
            {message.parts.map((part, index) =>
              part.type === 'text' ? (
                <span key={index}>{part.text}</span>
              ) : null
            )}
          </div>
        ))}
      </div>

      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Type a message..."
          disabled={status === 'streaming'}
        />
        <button type="submit" disabled={status === 'streaming'}>
          Send
        </button>
      </form>
    </div>
  );
}
4. ツール呼び出し/関数呼び出し

目的: AI モデルが構造化されたパラメーターを使って関数を呼び出せるようにします。

ツールの定義:

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

const weatherTool = tool({
  description: 'Get the weather in a location',
  inputSchema: z.object({
    location: z.string().describe('The location to get the weather for'),
    unit: z.enum(['C', 'F']).describe('Temperature unit'),
  }),
  execute: async ({ location, unit }) => {
    // Fetch or mock weather data
    return {
      location,
      temperature: 24,
      unit,
      condition: 'Sunny',
    };
  },
});

generateText/streamText でツールを使用する:

// app/api/chat/route.ts
import { streamText, convertToModelMessages, tool } from 'ai';
import { z } from 'zod';
import type { UIMessage } from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: 'openai/gpt-4o',
    messages: convertToModelMessages(messages),
    tools: {
      getWeather: tool({
        description: 'Get the weather for a location',
        inputSchema: z.object({
          city: z.string().describe('The city to get the weather for'),
          unit: z.enum(['C', 'F']).describe('The unit to display the temperature in'),
        }),
        execute: async ({ city, unit }) => {
          // Mock response
          return `It is currently 24°${unit} and Sunny in ${city}!`;
        },
      }),
    },
  });

  return result.toUIMessageStreamResponse();
}

複数ステップのツール呼び出し:

const result = await generateText({
  model: 'openai/gpt-4o',
  tools: {
    weather: weatherTool,
    search: searchTool,
  },
  prompt: 'What is the weather in San Francisco and find hotels there?',
  maxSteps: 5,  // Allow up to 5 tool call steps
});
5. テキスト埋め込み

目的: セマンティック検索、RAG、類似度計算のために、テキストを数値ベクトルに変換します。

シグネチャ:

import { embed } from 'ai';
import { openai } from '@ai-sdk/openai';

const result = await embed({
  model: openai.textEmbeddingModel('text-embedding-3-small'),
  value: 'Text to embed',
});

戻り値:

{
  embedding: number[];  // Numerical array representing the text
  usage: { tokens: number };  // Token consumption
  response: RawResponse;  // Raw provider response
}

例 - 埋め込み API ルート:

// app/api/embed/route.ts
import { embed } from 'ai';
import { openai } from '@ai-sdk/openai';

export async function GET() {
  const { embedding, usage } = await embed({
    model: openai.textEmbeddingModel('text-embedding-3-small'),
    value: 'sunny day at the beach',
  });

  return Response.json({ embedding, usage });
}

バッチ埋め込み:

import { embedMany } from 'ai';

const { embeddings, usage } = await embedMany({
  model: openai.textEmbeddingModel('text-embedding-3-small'),
  values: [
    'sunny day at the beach',
    'rainy afternoon in the city',
    'snowy mountain landscape',
  ],
});
6. メッセージユーティリティ

convertToModelMessages: useChat の UI メッセージを、AI 関数用の ModelMessage オブジェクトに変換します。

import { convertToModelMessages } from 'ai';
import type { UIMessage } from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: 'openai/gpt-4o',
    messages: convertToModelMessages(messages),  // Convert for model
  });

  return result.toUIMessageStreamResponse();
}
7. Model Context Protocol(MCP)の統合

目的: 動的なツールアクセスのために、外部 MCP サーバーに接続します。

例:

// app/api/chat/route.ts
import { experimental_createMCPClient, streamText } from 'ai';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

export async function POST(req: Request) {
  const { prompt }: { prompt: string } = await req.json();

  try {
    // Connect to MCP server
    const httpTransport = new StreamableHTTPClientTransport(
      new URL('http://localhost:3000/mcp')
    );

    const httpClient = await experimental_createMCPClient({
      transport: httpTransport,
    });

    // Fetch tools from MCP server
    const tools = await httpClient.tools();

    const response = streamText({
      model: 'openai/gpt-4o',
      tools,
      prompt,
      onFinish: async () => {
        await httpClient.close();  // Clean up
      },
      onError: async () => {
        await httpClient.close();  // Clean up on error
      },
    });

    return response.toTextStreamResponse();
  } catch (error) {
    return new Response('Internal Server Error', { status: 500 });
  }
}

重要なポイント:

  • experimental_createMCPClient を使用します(実験的な API であることに注意してください)
  • onFinish と onError で、必ずクライアントを閉じてください
  • httpClient.tools() でツールを動的に取得します
  • @modelcontextprotocol/sdk パッケージが必要です

モデル指定のパターン

文字列ベース(v5 で推奨)
// Format: 'provider/model-id'
model: 'openai/gpt-4o'
model: 'anthropic/claude-4-sonnet'
model: 'google/gemini-2.0-flash'
関数ベース(レガシーサポート)
import { openai } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic';

model: openai('gpt-4o')
model: anthropic('claude-4-sonnet')
埋め込みモデル
import { openai } from '@ai-sdk/openai';

// Text embeddings use a different method
openai.textEmbeddingModel('text-embedding-3-small')
openai.textEmbeddingModel('text-embedding-3-large')

TypeScript のベストプラクティス

型のインポート
import type {
  UIMessage,           // Message type from useChat
  ModelMessage,        // Message type for model functions
  ToolCall,            // Tool call information
  TokenUsage,          // Token consumption data
} from 'ai';
強く型付けされたツール
import { tool } from 'ai';
import { z } from 'zod';

// Tool helper infers execute parameter types
const myTool = tool({
  description: 'My tool',
  inputSchema: z.object({
    param1: z.string(),
    param2: z.number(),
  }),
  execute: async ({ param1, param2 }) => {
    // param1 is inferred as string
    // param2 is inferred as number
    return { result: 'success' };
  },
});
API ルートの型
// app/api/chat/route.ts
import type { UIMessage } from 'ai';

export async function POST(req: Request): Promise<Response> {
  const { messages }: { messages: UIMessage[] } = await req.json();

  // ... implementation
}

よくあるパターン

パターン 1:シンプルなチャットアプリケーション

クライアント(app/page.tsx):

'use client';

import { useChat } from 'ai/react';
import { useState } from 'react';

export default function Chat() {
  const { messages, sendMessage, status } = useChat();
  const [input, setInput] = useState('');

  return (
    <div>
      {messages.map((m) => (
        <div key={m.id}>
          <strong>{m.role}:</strong>
          {m.parts.map((part, i) =>
            part.type === 'text' ? <span key={i}>{part.text}</span> : null
          )}
        </div>
      ))}
      <form onSubmit={(e) => {
        e.preventDefault();
        sendMessage({ text: input });
        setInput('');
      }}>
        <input value={input} onChange={(e) => setInput(e.target.value)} />
        <button disabled={status === 'streaming'}>Send</button>
      </form>
    </div>
  );
}

サーバー(app/api/chat/route.ts):

import { streamText, convertToModelMessages } from 'ai';
import type { UIMessage } from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: 'openai/gpt-4o',
    system: 'You are a helpful assistant.',
    messages: convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse();
}
パターン 2:ツールを使ったチャット

ツール呼び出しを行うサーバー:

import { streamText, convertToModelMessages, tool } from 'ai';
import { z } from 'zod';
import type { UIMessage } from 'ai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: 'openai/gpt-4o',
    messages: convertToModelMessages(messages),
    tools: {
      getWeather: tool({
        description: 'Get weather for a city',
        inputSchema: z.object({
          city: z.string(),
        }),
        execute: async ({ city }) => {
          // API call or mock data
          return { city, temp: 72, condition: 'Sunny' };
        },
      }),
      searchWeb: tool({
        description: 'Search the web',
        inputSchema: z.object({
          query: z.string(),
        }),
        execute: async ({ query }) => {
          // Search implementation
          return { results: ['...'] };
        },
      }),
    },
  });

  return result.toUIMessageStreamResponse();
}
パターン 3:対話なしの生成
// app/api/summarize/route.ts
import { generateText } from 'ai';

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

  const result = await generateText({
    model: 'anthropic/claude-4-sonnet',
    system: 'You are a summarization expert.',
    prompt: `Summarize this text:\n\n${text}`,
  });

  return Response.json({ summary: result.text });
}
パターン 4:埋め込みを使ったセマンティック検索
// app/api/search/route.ts
import { embed } from 'ai';
import { openai } from '@ai-sdk/openai';

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

  // Generate embedding for search query
  const { embedding } = await embed({
    model: openai.textEmbeddingModel('text-embedding-3-small'),
    value: query,
  });

  // Use embedding for similarity search in vector database
  // const results = await vectorDB.search(embedding);

  return Response.json({ embedding, results: [] });
}

よくある落とし穴と解決策

落とし穴 1:ツールに tool() ヘルパーを使用しない — ⚠️ 重大

これは最もよくある重大なミスです。必ず tool() ヘルパーを使用してください!

// ❌ WRONG - Plain object (WILL CAUSE BUILD FAILURE)
import { z } from 'zod';

tools: {
  myTool: {
    description: 'My tool',
    parameters: z.object({      // ❌ Wrong property name
      city: z.string(),
    }),
    execute: async ({ city }) => { ... },
  },
}
// Build error: Type '{ description: string; parameters: ... }' is not assignable

// ✅ CORRECT - Use tool() helper (REQUIRED)
import { tool } from 'ai';      // ⚠️ MUST import tool
import { z } from 'zod';

tools: {
  myTool: tool({                // ⚠️ MUST use tool() wrapper
    description: 'My tool',
    inputSchema: z.object({     // ⚠️ Use inputSchema (not parameters)
      city: z.string(),
    }),
    execute: async ({ city }) => { ... },
  }),
}
落とし穴 2:v5 で v4 の useChat API を使用する
// ❌ WRONG - v4 pattern
const { input, setInput, append } = useChat();
append({ content: 'Hello', role: 'user' });

// ✅ CORRECT - v5 pattern
const { sendMessage } = useChat();
const [input, setInput] = useState('');
sendMessage({ text: 'Hello' });
落とし穴 3:message.parts ではなく message.content にアクセスする
// ❌ WRONG - v4 pattern
<div>{message.content}</div>

// ✅ CORRECT - v5 parts-based
<div>
  {message.parts.map((part, i) =>
    part.type === 'text' ? <span key={i}>{part.text}</span> : null
  )}
</div>
落とし穴 4:モデル用に UIMessages を変換しない
// ❌ WRONG - passing UIMessages directly
const result = streamText({
  model: 'openai/gpt-4o',
  messages: messages,  // UIMessage[] - type error
});

// ✅ CORRECT - convert to ModelMessage[]
const result = streamText({
  model: 'openai/gpt-4o',
  messages: convertToModelMessages(messages),
});
落とし穴 5:MCP クライアントのクリーンアップを忘れる
// ❌ WRONG - no cleanup
const httpClient = await experimental_createMCPClient({
  transport: httpTransport,
});
const tools = await httpClient.tools();
const response = streamText({ model, tools, prompt });
return response.toTextStreamResponse();

// ✅ CORRECT - cleanup in callbacks
const response = streamText({
  model,
  tools,
  prompt,
  onFinish: async () => {
    await httpClient.close();
  },
  onError: async () => {
    await httpClient.close();
  },
});
落とし穴 6:誤ったレスポンスメソッドを使用する
// ❌ WRONG - using text stream for useChat
return result.toTextStreamResponse();  // Won't work with useChat hook

// ✅ CORRECT - use UI message stream for useChat
return result.toUIMessageStreamResponse();

// ✅ ALSO CORRECT - text stream for non-chat scenarios
// For simple text streaming (not using useChat hook)
return result.toTextStreamResponse();
落とし穴 7:誤った埋め込みモデルのメソッドを使用する
// ❌ WRONG - using regular model method
const { embedding } = await embed({
  model: openai('text-embedding-3-small'),  // Wrong method
  value: 'text',
});

// ✅ CORRECT - use textEmbeddingModel
const { embedding } = await embed({
  model: openai.textEmbeddingModel('text-embedding-3-small'),
  value: 'text',
});

移行チェックリスト(v4 → v5)

v4 から v5 に移行する際は、以下を更新してください。

  • useChat の append を sendMessage に置き換える
  • useChat の分割代入から input、setInput、handleInputChange を削除する
  • 入力用のローカル状態管理を追加する:const [input, setInput] = useState('')
  • メッセージのレンダリングを message.content から message.parts.map(...) に更新する
  • sendMessage の呼び出しを { text: input } の形式に更新する
  • API ルートで convertToModelMessages が使用されていることを確認する
  • toUIMessageStreamResponse() が使用されていることを確認する(v4 のストリーミングメソッドではなく)
  • ツール定義を inputSchema を含む tool() ヘルパーを使う形式に更新する
  • 文字列ベースのモデル指定('provider/model-id')の使用を検討する
  • TypeScript の型(UIMessage、ModelMessage)を更新する

選択ガイド

AI SDK の機能を実装する際は、次の点を確認してください。

  1. これはクライアント側ですか、それともサーバー側ですか?

    • クライアント:useChat フックを使用します
    • サーバー:generateText または streamText を使用します
  2. ストリーミングが必要ですか、それともストリーミングなしでよいですか?

    • ストリーミングチャット:streamText + toUIMessageStreamResponse()
    • ストリーミングなし:generateText
    • シンプルなテキストストリーム:streamText + toTextStreamResponse()
  3. ツール呼び出しが必要ですか?

    • 必要:tool() ヘルパーと inputSchema(zod)を使用してツールを定義します
    • ツールオブジェクトを generateText または streamText に渡します
  4. 正しいメッセージ形式を使用していますか?

    • クライアント(useChat):parts プロパティを持つ UIMessage[] を返します
    • サーバー:convertToModelMessages() で ModelMessage[] に変換します
    • message.parts.map(...) を使ってメッセージをレンダリングします
  5. モデルの指定は正しいですか?

    • 文字列形式を推奨します:'openai/gpt-4o'
    • 関数形式も使用できます:openai('gpt-4o')
    • 埋め込み:openai.textEmbeddingModel('text-embedding-3-small')
  6. 埋め込みが必要ですか?

    • 単一の値には embed を使用します
    • バッチには embedMany を使用します
    • textEmbeddingModel() メソッドを使用します

クイックリファレンス

タスク関数主なパラメーター
テキストの生成generateText()model、prompt、system、tools
テキストのストリーミングstreamText()model、messages、tools、onFinish
チャット UIuseChat()api、onFinish、onError
ツール呼び出しtool()description、inputSchema、execute
テキストの埋め込みembed()model、value
一括埋め込みembedMany()model、values
メッセージの変換convertToModelMessages()messages(UIMessage[])
MCP 統合experimental_createMCPClient()transport

追加リソース

迷った場合は、公式ドキュメントを確認してください。

注意: AI SDK v5 では、文字列ベースのモデル指定とパーツベースのメッセージを使用し、append の代わりに sendMessage を使用します。また、API ルートでは convertToModelMessages が必要です。

コメント