mcpField
V
vercel-ai-sdk

vercel-ai-sdk

github.com/op0ai開発17196 回インストール41 週間前に更新
リポジトリ

vercel-ai-sdk について

TypeScript アプリで AI 機能を開発する方向けの Vercel AI SDK v6 実装ガイドです。チャット、エージェント、ツール、構造化出力、ストリーミング、MCP 統合に加え、現行 API と検証手順を扱います。

主な機能

  1. 01チャットとストリーミング応答のパターン
  2. 02v6 API によるツール呼び出し
  3. 03ToolLoopAgent の実装ガイド
  4. 04Output ヘルパーによる構造化出力
  5. 05埋め込みとセマンティック検索のガイド
  6. 06MCP サーバー統合のガイド

活用シーン

  1. 01useChat によるチャットインターフェースの実装
  2. 02AI 応答をストリーミングする API ルートの構築
  3. 03エージェント型アプリケーションへのツール追加
  4. 04構造化出力コードの v6 移行

MCP 対応

Vercel AI SDK アプリケーションへの Model Context Protocol サーバー統合方法を説明します。

インストール

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

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

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
agents.md
core-functions.md
mcp.md
middleware.md
tools.md
ui-hooks.md
workflows.md

詳細

Vercel AI SDK v6 実装ガイド

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

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

  • useChat フックを使用して AI チャットインターフェースを実装する
  • AI の応答を生成またはストリーミングする API ルートを作成する
  • ToolLoopAgent を使用してエージェント型アプリケーションを構築する
  • ツール呼び出し/関数呼び出し機能を追加する
  • Output.object()、Output.array() などを使用して構造化出力を生成する
  • セマンティック検索や RAG 用のテキスト埋め込みを生成する
  • AI SDK v5 から v6 に移行する
  • Model Context Protocol(MCP)サーバーを統合する
  • キャッシュ、ロギング、ガードレール用のミドルウェアを実装する
  • ワークフローパターン(逐次、並列、ルーティングなど)を構築する
  • ストリーミング応答やメッセージの永続化を扱う

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

タスクの要件を理解します - 必要な AI 機能(チャット、生成、エージェント、ツール、埋め込み)を特定する - クライアント側(useChat)とサーバー側(API ルート)のどちらで実装するかを判断する - ストリーミング応答と非ストリーミング応答のどちらが必要かを確認する - モデルプロバイダー(Anthropic、OpenAI など)を確認する - 構造化出力(Output.object、Output.array など)が必要かを判断する 不明な点がある場合は、現在の API パターンを確認します - API パターンが不明な場合は、WebFetch を使用して https://ai-sdk.dev/docs/ を確認する - プロバイダーのモデル指定形式を確認する - 複雑な機能の関数シグネチャを確認する 正しい v6 パターンを使用して実装します - プロバイダー関数のモデル指定を使用する:anthropic('claude-sonnet-4-5') - チャットでは、sendMessage を使用し(append は使用しない)、parts ベースのメッセージを使う - ツールでは、'ai' から tool() ヘルパーを必ずインポートして使用し、inputSchema を必ず使用し(parameters は使用しない)、zod を必ず使用する - 構造化出力には、Output.object()、Output.array()、Output.choice()、Output.json() を使用する - ストリーミングには、toUIMessageStreamResponse() または toTextStreamResponse() を使用する - エージェントには、ToolLoopAgent クラスと createAgentUIStreamResponse() を使用する - 埋め込みには、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 が作成されます - これにより依存関係のバージョンが一致しなくなり、ビルドが失敗します - 既存のロックファイルを必ず確認し、対応するパッケージマネージャーを使用してください
  NEVER accept "Module not found" errors as environment issues
  YOU must install the required packages with the CORRECT package manager

  Common packages needed:
  - ai (core AI SDK)
  - @ai-sdk/openai (OpenAI provider)
  - @ai-sdk/anthropic (Anthropic provider)
  - @ai-sdk/mcp (MCP integration)
  - @modelcontextprotocol/sdk (MCP client SDK)
  - zod (for tool schemas)
</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 が成功する - ✅ テストが成功する
  "Code is correct" is NOT enough
  You must achieve FULL PASSING status
  This is what it means to be an autonomous agent
</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/anthropic'
    Import: import { anthropic } from '@ai-sdk/anthropic'
    Package needed: @ai-sdk/anthropic
    
  3. 正しいパッケージマネージャーでインストールする

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

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

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

  • 競合するロックファイルがもう1つ作成される
  • 異なるバージョンの依存関係がインストールされる
  • 依存関係のバージョンが一致しなくなる
  • 「Cannot read properties of null」のような分かりにくいエラーでビルドが失敗する
ビルドエラーのデバッグ手順

ビルドに失敗した場合:

  1. エラーメッセージ全体を読む — 斜め読みしない
  2. 根本原因を特定する:
    • モジュールが見つからない → パッケージをインストールする
    • 型エラー → 型を修正する
    • 設定エラー → 設定ファイルを確認する
    • Next.js のエラー → 調査して、異なる方法を試す
  3. 修正を適用する
  4. ビルドを再実行する
  5. ビルドが成功するまで繰り返す
テスト失敗のデバッグ手順

テストに失敗した場合:

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

次の条件を満たしたときに限り、タスクは完了です。

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

「コードは正しい」で決して止めず、すべて合格した状態を達成してください。

⚠️ 重大な v6 の変更:構造化出力

v6 では、generateObject と streamObject は非推奨です。 代わりに Output ヘルパーを使って generateText/streamText を使用してください。

❌ 誤り — 非推奨の v5 パターン
// DO NOT USE - DEPRECATED in v6
import { generateObject } from "ai";

const result = await generateObject({
  model: anthropic("claude-sonnet-4-5"),
  schema: z.object({
    sentiment: z.enum(["positive", "neutral", "negative"]),
  }),
  prompt: "Analyze sentiment",
});
✅ 正解 — v6 の Output パターン
import { generateText, Output } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";

const { output } = await generateText({
  model: anthropic("claude-sonnet-4-5"),
  output: Output.object({
    schema: z.object({
      sentiment: z.enum(["positive", "neutral", "negative"]),
      topics: z.array(z.string()),
    }),
  }),
  prompt: "Analyze this feedback...",
});

// Access typed output
console.log(output.sentiment); // 'positive' | 'neutral' | 'negative'
console.log(output.topics); // string[]
Output ヘルパーの型
ヘルパー用途例
Output.object()型付きオブジェクトを生成するOutput.object({ schema: z.object({...}) })
Output.array()型付き配列を生成するOutput.array({ schema: z.string() })
Output.choice()列挙値を生成するOutput.choice({ choices: ['A', 'B', 'C'] })
Output.json()非構造化 JSONOutput.json()

⚠️ 重大な注意:ツール呼び出し 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 v6
    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 文字列を追加している

⚠️ v6 の新機能:エージェント型アプリケーション向けの ToolLoopAgent

エージェントの定義
import { ToolLoopAgent, tool, stepCountIs } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";

const myAgent = new ToolLoopAgent({
  model: anthropic("claude-sonnet-4-5"),
  instructions: "You are a helpful assistant that can search and analyze data.",
  tools: {
    getData: tool({
      description: "Fetch data from API",
      inputSchema: z.object({
        query: z.string(),
      }),
      execute: async ({ query }) => {
        // Implement data fetching
        return { result: "data for " + query };
      },
    }),
    analyzeData: tool({
      description: "Analyze fetched data",
      inputSchema: z.object({
        data: z.string(),
      }),
      execute: async ({ data }) => {
        return { analysis: "Analysis of " + data };
      },
    }),
  },
  stopWhen: stepCountIs(20), // Stop after 20 steps max
});

// Non-streaming execution
const { text, toolCalls } = await myAgent.generate({
  prompt: "Find and analyze user data",
});

// Streaming execution
const stream = myAgent.stream({ prompt: "Find and analyze user data" });
for await (const chunk of stream) {
  // Handle streaming chunks
}
エージェントの API ルートへの統合
// app/api/agent/route.ts
import { createAgentUIStreamResponse } from "ai";
import { myAgent } from "@/agents/my-agent";

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

  return createAgentUIStreamResponse({
    agent: myAgent,
    uiMessages: messages,
  });
}
エージェントの設定オプション
パラメーター目的例
model使用する AI モデルanthropic('claude-sonnet-4-5')
instructionsシステムプロンプト'You are a helpful assistant.'
tools使用可能なツール{ toolName: tool({...}) }
stopWhen終了条件stepCountIs(20)
toolChoiceツールの使用モード'auto'、'required'、'none'
output構造化出力スキーマOutput.object({...})
prepareStepステップごとの動的な調整ステップ設定を返す関数
prepareCall実行時オプションの注入RAG などに使用する非同期関数

⚠️ 重要:v5 から v6 への主な破壊的変更

1. useChat フックの変更

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

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

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

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

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

// Sending message
sendMessage({ text: input });

// New in v6: Handle tool outputs
addToolOutput({ toolCallId: 'xxx', result: { ... } });
2. メッセージ構造

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

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

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

<div>
  {message.parts.map((part, index) =>
    part.type === 'text' ? <span key={index}>{part.text}</span> : null
  )}
</div>
3. レスポンスメソッド

❌ 誤り(v5):

return result.toDataStreamResponse();

✅ 正しい(v6):

return result.toUIMessageStreamResponse();
4. モデルの指定
import { anthropic } from "@ai-sdk/anthropic";
import { openai } from "@ai-sdk/openai";

// Use provider functions (direct provider access)
model: anthropic("claude-sonnet-4-5");
model: anthropic("claude-opus-4-5");
model: anthropic("claude-haiku-4-5");
model: openai("gpt-4o");
model: openai("gpt-4o-mini");
5. Vercel AI Gateway

目的: Vercel AI Gateway を使用して、複数のプロバイダーにわたるモデルへの統一アクセス、レート制限、キャッシュ、オブザーバビリティを実現します。

インポート:

import { gateway } from "ai";

Gateway 経由で使用可能な Anthropic モデル:

model: gateway("anthropic/claude-sonnet-4-5");
model: gateway("anthropic/claude-haiku-4-5");
model: gateway("anthropic/claude-opus-4-5");

Gateway を使用する場合:

  • レート制限とキャッシュが必要な本番アプリケーション
  • 統一インターフェイスを必要とするマルチプロバイダーアプリケーション
  • オブザーバビリティと分析機能が必要なアプリケーション
  • 自動リトライとエラー処理を利用したい場合

プロバイダーを直接使用する場合:

  • 開発/テスト環境
  • Gateway 経由では利用できない、プロバイダー固有の機能が必要な場合
  • API 呼び出しを直接制御したい場合

例:

import { generateText, gateway } from "ai";

const result = await generateText({
  model: gateway("anthropic/claude-sonnet-4-5"),
  prompt: "Hello, world!",
});

比較:

// Option 1: Direct provider
import { anthropic } from "@ai-sdk/anthropic";
model: anthropic("claude-sonnet-4-5");

// Option 2: Gateway (recommended for production)
import { gateway } from "ai";
model: gateway("anthropic/claude-sonnet-4-5");

コア API リファレンス

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

目的: 対話を必要としない用途(メールの下書き、要約、ツールを使用するエージェント)向けにテキストを生成します。

シグネチャ:

import { generateText, Output } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';

const result = await generateText({
  model: anthropic('claude-sonnet-4-5'),
  prompt: 'Your prompt here',
  system: 'Optional system message',
  tools?: { ... },
  maxSteps?: 5,
  output?: Output.object({ schema: z.object({...}) }),
});

戻り値:

{
  text: string;              // Generated text output
  output?: T;                // Typed structured output (if Output specified)
  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";
import { anthropic } from "@ai-sdk/anthropic";

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

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

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

シグネチャ:

import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';

const result = streamText({
  model: anthropic('claude-sonnet-4-5'),
  prompt: 'Your prompt here',
  system: 'Optional system message',
  messages?: ModelMessage[],
  tools?: { ... },
  onChunk?: (chunk) => { ... },
  onStepFinish?: (step) => { ... },
  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 { anthropic } from "@ai-sdk/anthropic";
import type { UIMessage } from "ai";

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

  const result = streamText({
    model: anthropic("claude-sonnet-4-5"),
    system: "You are a helpful assistant.",
    messages: convertToModelMessages(messages),
  });

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

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

シグネチャ:

import { useChat } from '@ai-sdk/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
  clearError,      // Clear error state
  addToolOutput,   // Submit tool results (NEW in v6)
  resumeStream,    // Resume interrupted stream (NEW in v6)
} = useChat({
  api: '/api/chat',
  id?: 'chat-id',
  messages?: initialMessages,
  onToolCall?: async (toolCall) => { ... },
  onFinish?: (message) => { ... },
  onError?: (error) => { ... },
  sendAutomaticallyWhen?: (messages) => boolean,
  resume?: true,
});

完全な例:

'use client';

import { useChat } from '@ai-sdk/react';
import { useState } from 'react';

export default function ChatPage() {
  const { messages, sendMessage, status, addToolOutput } = useChat({
    onToolCall: async ({ toolCall }) => {
      // Handle client-side tool execution
      if (toolCall.name === 'confirm') {
        const result = await showConfirmDialog(toolCall.args);
        addToolOutput({ toolCallId: toolCall.id, result });
      }
    },
  });
  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) => {
              switch (part.type) {
                case 'text':
                  return <span key={index}>{part.text}</span>;
                case 'tool-call':
                  return <div key={index}>Tool: {part.name}</div>;
                default:
                  return 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"),
  }),
  outputSchema: z.object({
    temperature: z.number(),
    condition: z.string(),
  }),
  execute: async ({ location, unit }) => {
    // Fetch or mock weather data
    return {
      temperature: 24,
      condition: "Sunny",
    };
  },
});

generateText/streamText でのツールの使用:

// app/api/chat/route.ts
import { streamText, convertToModelMessages, tool } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
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: anthropic("claude-sonnet-4-5"),
    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 }) => {
          // API call or mock data
          return `It is currently 24°${unit} and Sunny in ${city}!`;
        },
      }),
    },
    toolChoice: "auto", // 'auto' | 'required' | 'none' | { type: 'tool', toolName: 'xxx' }
  });

  return result.toUIMessageStreamResponse();
}

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

const result = await generateText({
  model: anthropic("claude-sonnet-4-5"),
  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, embedMany } from "ai";
import { openai } from "@ai-sdk/openai";

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

// Batch embeddings
const batchResult = await embedMany({
  model: openai.textEmbeddingModel("text-embedding-3-small"),
  values: ["Text 1", "Text 2", "Text 3"],
});

戻り値:

{
  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 POST(req: Request) {
  const { text } = await req.json();

  const { embedding, usage } = await embed({
    model: openai.textEmbeddingModel("text-embedding-3-small"),
    value: text,
  });

  return Response.json({ embedding, usage });
}
6. ミドルウェア

目的: ロギング、キャッシュ、ガードレール、RAG などのために、モデルの動作をインターセプトして変更します。

組み込みミドルウェア:

import {
  extractReasoningMiddleware,
  simulateStreamingMiddleware,
  defaultSettingsMiddleware,
  wrapLanguageModel,
} from "ai";

// Extract reasoning from models like Claude
const modelWithReasoning = wrapLanguageModel({
  model: anthropic("claude-sonnet-4-5"),
  middleware: extractReasoningMiddleware({ tagName: "thinking" }),
});

// Apply default settings
const modelWithDefaults = wrapLanguageModel({
  model: anthropic("claude-sonnet-4-5"),
  middleware: defaultSettingsMiddleware({
    temperature: 0.7,
    maxOutputTokens: 1000,
  }),
});

カスタムミドルウェア:

import { LanguageModelMiddleware, wrapLanguageModel } from "ai";

// Logging middleware
const loggingMiddleware: LanguageModelMiddleware = {
  transformParams: async ({ params }) => {
    console.log("Request params:", params);
    return params;
  },
  wrapGenerate: async ({ doGenerate, params }) => {
    const result = await doGenerate();
    console.log("Response:", result);
    return result;
  },
};

// Caching middleware
const cache = new Map<string, string>();
const cachingMiddleware: LanguageModelMiddleware = {
  wrapGenerate: async ({ doGenerate, params }) => {
    const cacheKey = JSON.stringify(params.prompt);
    if (cache.has(cacheKey)) {
      return { text: cache.get(cacheKey)! };
    }
    const result = await doGenerate();
    cache.set(cacheKey, result.text);
    return result;
  },
};

// RAG middleware
const ragMiddleware: LanguageModelMiddleware = {
  transformParams: async ({ params }) => {
    const relevantDocs = await vectorSearch(params.prompt);
    return {
      ...params,
      prompt: `Context: ${relevantDocs}\n\nQuery: ${params.prompt}`,
    };
  },
};

// Apply multiple middleware
const enhancedModel = wrapLanguageModel({
  model: anthropic("claude-sonnet-4-5"),
  middleware: [loggingMiddleware, cachingMiddleware, ragMiddleware],
});
7. Model Context Protocol(MCP)の統合

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

インストール:

bun add @ai-sdk/mcp @modelcontextprotocol/sdk

HTTP トランスポート(本番環境):

import { createMCPClient } from "@ai-sdk/mcp";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { streamText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

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

  const httpTransport = new StreamableHTTPClientTransport(
    new URL("https://mcp-server.example.com/mcp"),
    { headers: { Authorization: `Bearer ${process.env.MCP_TOKEN}` } }
  );

  const mcpClient = await createMCPClient({ transport: httpTransport });

  try {
    const tools = await mcpClient.tools();

    const response = streamText({
      model: anthropic("claude-sonnet-4-5"),
      tools,
      prompt,
      onFinish: async () => {
        await mcpClient.close();
      },
      onError: async () => {
        await mcpClient.close();
      },
    });

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

Stdio トランスポート(開発環境):

import { createMCPClient } from "@ai-sdk/mcp";
import { Experimental_StdioMCPTransport } from "@ai-sdk/mcp";

const stdioTransport = new Experimental_StdioMCPTransport({
  command: "npx",
  args: [
    "-y",
    "@modelcontextprotocol/server-filesystem",
    "/path/to/allowed/dir",
  ],
});

const mcpClient = await createMCPClient({ transport: stdioTransport });

重要なポイント:

  • onFinish と onError で、必ずクライアントを閉じてください
  • mcpClient.tools() を使って、ツールを動的に取得します
  • 本番環境では HTTP/SSE、開発環境では Stdio を使用します
  • 複数の MCP クライアントは、ツールコレクションをマージして組み合わせられます
8. メッセージユーティリティ

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: anthropic("claude-sonnet-4-5"),
    messages: convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse();
}

ワークフローパターン

1. 順次(チェーン)パターン
async function sequentialWorkflow(input: string) {
  // Step 1: Generate initial content
  const { text: draft } = await generateText({
    model: anthropic("claude-sonnet-4-5"),
    prompt: `Write marketing copy for: ${input}`,
  });

  // Step 2: Evaluate quality
  const { output: evaluation } = await generateText({
    model: anthropic("claude-sonnet-4-5"),
    output: Output.object({
      schema: z.object({
        score: z.number().min(1).max(10),
        feedback: z.string(),
      }),
    }),
    prompt: `Evaluate this copy: ${draft}`,
  });

  // Step 3: Improve if needed
  if (evaluation.score < 7) {
    const { text: improved } = await generateText({
      model: anthropic("claude-sonnet-4-5"),
      prompt: `Improve this copy based on feedback:\n\nCopy: ${draft}\n\nFeedback: ${evaluation.feedback}`,
    });
    return improved;
  }

  return draft;
}
2. 並列パターン
async function parallelReview(code: string) {
  const [securityReview, performanceReview, maintainabilityReview] =
    await Promise.all([
      generateText({
        model: anthropic("claude-sonnet-4-5"),
        prompt: `Review for security issues:\n\n${code}`,
      }),
      generateText({
        model: anthropic("claude-sonnet-4-5"),
        prompt: `Review for performance issues:\n\n${code}`,
      }),
      generateText({
        model: anthropic("claude-sonnet-4-5"),
        prompt: `Review for maintainability:\n\n${code}`,
      }),
    ]);

  return {
    security: securityReview.text,
    performance: performanceReview.text,
    maintainability: maintainabilityReview.text,
  };
}
3. ルーティングパターン
async function routeQuery(query: string) {
  // Classify the query
  const { output: classification } = await generateText({
    model: anthropic("claude-sonnet-4-5"),
    output: Output.choice({
      choices: ["technical", "billing", "general"] as const,
    }),
    prompt: `Classify this customer query: ${query}`,
  });

  // Route to appropriate handler
  switch (classification) {
    case "technical":
      return handleTechnicalQuery(query);
    case "billing":
      return handleBillingQuery(query);
    default:
      return handleGeneralQuery(query);
  }
}
4. オーケストレーター・ワーカーパターン
async function implementFeature(requirement: string) {
  // Orchestrator: Break down the task
  const { output: plan } = await generateText({
    model: anthropic("claude-sonnet-4-5"),
    output: Output.object({
      schema: z.object({
        tasks: z.array(
          z.object({
            type: z.enum(["frontend", "backend", "database"]),
            description: z.string(),
          })
        ),
      }),
    }),
    prompt: `Break down this feature into tasks: ${requirement}`,
  });

  // Workers: Execute tasks in parallel
  const results = await Promise.all(
    plan.tasks.map((task) =>
      generateText({
        model: anthropic("claude-sonnet-4-5"),
        prompt: `Implement this ${task.type} task: ${task.description}`,
      })
    )
  );

  return results.map((r) => r.text);
}
5. 評価者・オプティマイザーパターン
async function optimizeOutput(input: string, maxIterations = 3) {
  let output = await generateText({
    model: anthropic("claude-sonnet-4-5"),
    prompt: input,
  });

  for (let i = 0; i < maxIterations; i++) {
    const { output: evaluation } = await generateText({
      model: anthropic("claude-sonnet-4-5"),
      output: Output.object({
        schema: z.object({
          isGood: z.boolean(),
          improvements: z.array(z.string()),
        }),
      }),
      prompt: `Evaluate this output: ${output.text}`,
    });

    if (evaluation.isGood) break;

    output = await generateText({
      model: anthropic("claude-sonnet-4-5"),
      prompt: `Improve based on: ${evaluation.improvements.join(", ")}\n\nOriginal: ${output.text}`,
    });
  }

  return output.text;
}

メッセージパートの種類(v6)

| パートの種類 | 説明 | プロパティ | | ----------- | -------------------- | ------------------------------------------ | ---------- | -------- | --------------- | | text | テキストコンテンツ | text, isStreaming | | tool-call | ツールの呼び出し | name, args, state ('input-streaming' | 'invoking' | 'output' | 'output-error') | | reasoning | モデルの思考 | text, isStreaming | | file | ファイル添付 | mediaType, url または data | | source | RAG ソース参照 | url または documentId、title | | step | ワークフローの境界 | ステップの境界を示します | | data | カスタムデータ | 任意のカスタムペイロード |

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 type { InferAgentUIMessage } from "ai";

// Type-safe messages from agent
type MyAgentMessage = InferAgentUIMessage<typeof myAgent>;
強い型付けのツール
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(),
  }),
  outputSchema: z.object({
    result: z.string(),
  }),
  execute: async ({ param1, param2 }) => {
    // param1 is inferred as string
    // param2 is inferred as number
    return { result: "success" };
  },
});

よく使われるパターン

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

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

'use client';

import { useChat } from '@ai-sdk/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 { anthropic } from "@ai-sdk/anthropic";
import type { UIMessage } from "ai";

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

  const result = streamText({
    model: anthropic("claude-sonnet-4-5"),
    system: "You are a helpful assistant.",
    messages: convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse();
}
パターン 2:構造化出力を使ったチャット
import { streamText, convertToModelMessages, Output } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
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: anthropic("claude-sonnet-4-5"),
    messages: convertToModelMessages(messages),
    output: Output.object({
      schema: z.object({
        response: z.string(),
        sentiment: z.enum(["positive", "neutral", "negative"]),
        confidence: z.number().min(0).max(1),
      }),
    }),
  });

  return result.toUIMessageStreamResponse();
}
パターン 3:複数のツールを使うエージェント
import {
  ToolLoopAgent,
  tool,
  stepCountIs,
  createAgentUIStreamResponse,
} from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";

const researchAgent = new ToolLoopAgent({
  model: anthropic("claude-sonnet-4-5"),
  instructions:
    "You are a research assistant that can search and analyze information.",
  tools: {
    webSearch: tool({
      description: "Search the web for information",
      inputSchema: z.object({
        query: z.string().describe("Search query"),
      }),
      execute: async ({ query }) => {
        // Implement web search
        return { results: ["..."] };
      },
    }),
    analyze: tool({
      description: "Analyze collected information",
      inputSchema: z.object({
        data: z.string().describe("Data to analyze"),
      }),
      execute: async ({ data }) => {
        return { analysis: "..." };
      },
    }),
    summarize: tool({
      description: "Summarize findings",
      inputSchema: z.object({
        findings: z.array(z.string()),
      }),
      execute: async ({ findings }) => {
        return { summary: "..." };
      },
    }),
  },
  stopWhen: stepCountIs(10),
});

// API Route
export async function POST(request: Request) {
  const { messages } = await request.json();
  return createAgentUIStreamResponse({
    agent: researchAgent,
    uiMessages: messages,
  });
}
パターン 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:非推奨の generateObject/streamObject を使う
// ❌ WRONG - Deprecated in v6
import { generateObject } from 'ai';
const result = await generateObject({
  schema: z.object({...}),
  prompt: '...',
});

// ✅ CORRECT - Use Output with generateText
import { generateText, Output } from 'ai';
const { output } = await generateText({
  output: Output.object({ schema: z.object({...}) }),
  prompt: '...',
});
落とし穴 2:ツールに tool() ヘルパーを使わない
// ❌ WRONG - Plain object (WILL CAUSE BUILD FAILURE)
tools: {
  myTool: {
    description: 'My tool',
    parameters: z.object({...}),  // ❌ Wrong property name
    execute: async ({...}) => {...},
  },
}

// ✅ CORRECT - Use tool() helper (REQUIRED)
import { tool } from 'ai';
tools: {
  myTool: tool({
    description: 'My tool',
    inputSchema: z.object({...}),  // ⚠️ Use inputSchema
    execute: async ({...}) => {...},
  }),
}
落とし穴 3:v6 で v5 の useChat API を使う
// ❌ WRONG - v5 pattern
const { input, setInput, append } = useChat();
append({ content: "Hello", role: "user" });

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

// ✅ CORRECT - v6 parts-based
<div>
  {message.parts.map((part, i) =>
    part.type === 'text' ? <span key={i}>{part.text}</span> : null
  )}
</div>
落とし穴 5:誤ったレスポンスメソッドを使う
// ❌ WRONG - v5 method
return result.toDataStreamResponse();

// ✅ CORRECT - v6 method
return result.toUIMessageStreamResponse();
落とし穴 6:MCP クライアントのクリーンアップを忘れる
// ❌ WRONG - no cleanup
const mcpClient = await createMCPClient({ transport });
const tools = await mcpClient.tools();
const response = streamText({ model, tools, prompt });
return response.toTextStreamResponse();

// ✅ CORRECT - cleanup in callbacks
const response = streamText({
  model,
  tools,
  prompt,
  onFinish: async () => {
    await mcpClient.close();
  },
  onError: async () => {
    await mcpClient.close();
  },
});

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

v5 から v6 に移行する際は、次の項目を更新してください。

  • [ ] generateObject/streamObject を generateText/streamText + Output に置き換える
  • [ ] useChat の append を sendMessage に置き換える
  • [ ] useChat の分割代入から input、setInput、handleInputChange を削除する
  • [ ] 入力用のローカル状態管理を追加する:const [input, setInput] = useState('')
  • [ ] メッセージのレンダリングを message.content から message.parts.map(...) に更新する
  • [ ] sendMessage の呼び出しを { text: input } 形式に更新する
  • [ ] toDataStreamResponse() を toUIMessageStreamResponse() に置き換える
  • [ ] ツール定義を、tool() ヘルパーと inputSchema を使う形式に更新する
  • [ ] モデル ID を更新する(例:claude-sonnet-4-5)
  • [ ] エージェント型アプリケーションでは、ToolLoopAgent の使用を検討する
  • [ ] TypeScript の型(UIMessage、ModelMessage)を更新する
  • [ ] クライアント側ツールを使用する場合は、addToolOutput の処理を追加する
  • [ ] ロギング、キャッシュ、ガードレール用のミドルウェアの実装を検討する

判断ガイド

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

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

    • クライアント:useChat フックを使用します
    • サーバー:generateText または streamText を使用します
    • エージェント:createAgentUIStreamResponse と ToolLoopAgent を使用します
  2. ストリーミングと非ストリーミングのどちらが必要ですか?

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

    • はい:Output.object()、Output.array()、Output.choice()、または Output.json() を使用します
    • output パラメーターを介して generateText または streamText に渡します
  4. ツール呼び出しが必要ですか?

    • はい:tool() ヘルパーと inputSchema(zod)を使ってツールを定義します
    • ツールオブジェクトを generateText、streamText、または ToolLoopAgent に渡します
  5. エージェントを構築していますか?

    • はい:ToolLoopAgent クラスを使用します
    • 必要に応じて stopWhen、toolChoice、prepareStep を設定します
    • API ルートでは createAgentUIStreamResponse を使用します
  6. 正しいメッセージ形式を使用していますか?

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

    • 直接プロバイダーを指定:anthropic('claude-sonnet-4-5')
    • ゲートウェイ(本番環境):gateway('anthropic/claude-sonnet-4-5')
    • 埋め込み:openai.textEmbeddingModel('text-embedding-3-small')
  8. 埋め込みが必要ですか?

    • 単一の値には embed を使用します
    • バッチには embedMany を使用します
    • textEmbeddingModel() メソッドを使用します
  9. ミドルウェアが必要ですか?

    • ロギング:transformParams/wrapGenerate を使ったカスタムミドルウェア
    • キャッシュ:結果を保存するカスタムミドルウェア
    • RAG:コンテキストを挿入するカスタムミドルウェア
    • ガードレール:出力をフィルタリングするカスタムミドルウェア

クイックリファレンス

タスク関数主なパラメーター
テキスト生成generateText()model、prompt、system、tools、output
テキストのストリーミングstreamText()model、messages、tools、output、onFinish
チャット UIuseChat()api、onToolCall、onFinish、onError
エージェントの構築ToolLoopAgentmodel、instructions、tools、stopWhen
ツール呼び出しtool()description、inputSchema、outputSchema、execute
構造化出力Output.object()schema (zod)
テキスト埋め込みembed()model、value
バッチ埋め込みembedMany()model、values
メッセージ変換convertToModelMessages()messages (UIMessage[])
MCP 統合createMCPClient()transport
ミドルウェアの追加wrapLanguageModel()model、middleware
ゲートウェイモデルgateway()"provider/model-name"(例:"anthropic/claude-sonnet-4-5")

追加リソース

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

忘れないでください: AI SDK v6 では、プロバイダー関数によるモデル指定(または本番環境での gateway())、パーツベースのメッセージ、append の代わりに sendMessage、generateObject の代わりに Output ヘルパー、toDataStreamResponse の代わりに toUIMessageStreamResponse を使用し、API ルートで convertToModelMessages が必要です。

コメント