vercel-ai-sdk について
Vercel AI SDK v5 を使う開発者向けに、チャット、テキスト生成、ツール呼び出し、MCP 連携の実装方法を案内します。v5 の API パターン、依存関係の管理、ビルドとテストの反復確認を扱います。
主な機能
- 01チャットとストリーミング応答の実装
- 02ツール呼び出しと埋め込み生成
- 03v4 から v5 への移行ガイド
- 04依存関係の導入とパッケージマネージャーの確認
- 05ビルド、Lint、テストのデバッグ
活用シーン
- 01useChat によるチャット UI の実装
- 02生成またはストリーミング API の作成
- 03セマンティック検索用の埋め込み生成
- 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>
⚠️ 自律型エージェントとしての心構え
あなたはコードを書くだけではなく、タスクを自律的に完了させます。
つまり、次のことを行います:
- ✅ 正しい実装を書く
- ✅ 必要な依存関係をインストールする
- ✅ ビルドを実行し、エラーをすべて修正する
- ✅ テストを実行し、失敗をすべてデバッグする
- ✅ すべてが成功するまで繰り返す
- ✅ 言い訳をしたり、諦めたりしない
よくある失敗パターン:避けるべきこと
❌ 誤り:「コードは正しいが、パッケージがインストールされていない。これは環境の問題です」 ✅ 正しい:「パッケージが不足しているためビルドに失敗しました。今から npm install [package] でインストールします」
❌ 誤り:「テストは成功したが、ビルドは失敗した。自分の問題ではありません」 ✅ 正しい:「ビルドに失敗しています。今からエラーをデバッグして修正します」
❌ 誤り:「フレームワークのバグがあり、修正できません」 ✅ 正しい:「フレームワークのエラーを検出しました。解決策が見つかるまで問題を調査し、回避策を試し、デバッグします」
❌ 誤り:「実装は完了しました」(テストが失敗している状態で) ✅ 正しい:「テストが失敗しています。すべて成功するまでデバッグして修正します」
依存関係のインストールワークフロー
「Module not found」エラーが発生した場合:
-
最初にパッケージマネージャーを検出する - ロックファイルを確認します:
ls -la | grep -E "lock" # Look for: pnpm-lock.yaml, package-lock.json, yarn.lock, bun.lockb -
インポート文からパッケージを特定する
Error: Cannot find module '@ai-sdk/openai' Import: import { openai } from '@ai-sdk/openai' Package needed: @ai-sdk/openai -
正しいパッケージマネージャーでインストールする
# 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 -
ビルドを再実行して確認する
npm run build # or pnpm run build, yarn build, bun run build -
新たに発生したエラーを修正する
⚠️ 重大な警告: 間違ったパッケージマネージャーを使用すると(例:プロジェクトで pnpm を使用しているのに npm を使う)、次の問題が発生します:
- 競合するロックファイルがもう1つ作成される
- 異なるバージョンの依存関係がインストールされる
- 依存関係のバージョン不整合が発生する
- 「Cannot read properties of null」のような不可解なエラーによりビルドが失敗する
ビルドエラーのデバッグワークフロー
ビルドが失敗した場合:
- エラーメッセージ全体を読む - 斜め読みしないでください
- 根本原因を特定する:
- Module not found → パッケージをインストールする
- Type error → 型を修正する
- Config error → 設定ファイルを確認する
- Next.js error → 調査して、別の方法を試す
- 修正を適用する
- ビルドを再実行する
- ビルドが成功するまで繰り返す
テスト失敗のデバッグワークフロー
テストが失敗した場合:
- テストエラー全体を読む - 何が期待されているのかを理解します
- 期待される動作と実際の動作を比較する
- テストのアサーションに照らして、実装を確認する
- コード内の問題を修正する
- テストを再実行する
- すべてのテストが成功するまで繰り返す
成功条件
次の条件をすべて満たした場合にのみ、タスクは完了です:
- ✅ ビルドが成功する(
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 の機能を実装する際は、次の点を確認してください。
-
これはクライアント側ですか、それともサーバー側ですか?
- クライアント:
useChatフックを使用します - サーバー:
generateTextまたはstreamTextを使用します
- クライアント:
-
ストリーミングが必要ですか、それともストリーミングなしでよいですか?
- ストリーミングチャット:
streamText+toUIMessageStreamResponse() - ストリーミングなし:
generateText - シンプルなテキストストリーム:
streamText+toTextStreamResponse()
- ストリーミングチャット:
-
ツール呼び出しが必要ですか?
- 必要:
tool()ヘルパーとinputSchema(zod)を使用してツールを定義します - ツールオブジェクトを
generateTextまたはstreamTextに渡します
- 必要:
-
正しいメッセージ形式を使用していますか?
- クライアント(useChat):
partsプロパティを持つUIMessage[]を返します - サーバー:
convertToModelMessages()でModelMessage[]に変換します message.parts.map(...)を使ってメッセージをレンダリングします
- クライアント(useChat):
-
モデルの指定は正しいですか?
- 文字列形式を推奨します:
'openai/gpt-4o' - 関数形式も使用できます:
openai('gpt-4o') - 埋め込み:
openai.textEmbeddingModel('text-embedding-3-small')
- 文字列形式を推奨します:
-
埋め込みが必要ですか?
- 単一の値には
embedを使用します - バッチには
embedManyを使用します textEmbeddingModel()メソッドを使用します
- 単一の値には
クイックリファレンス
| タスク | 関数 | 主なパラメーター |
|---|---|---|
| テキストの生成 | generateText() | model、prompt、system、tools |
| テキストのストリーミング | streamText() | model、messages、tools、onFinish |
| チャット UI | useChat() | api、onFinish、onError |
| ツール呼び出し | tool() | description、inputSchema、execute |
| テキストの埋め込み | embed() | model、value |
| 一括埋め込み | embedMany() | model、values |
| メッセージの変換 | convertToModelMessages() | messages(UIMessage[]) |
| MCP 統合 | experimental_createMCPClient() | transport |
追加リソース
迷った場合は、公式ドキュメントを確認してください。
- メインドキュメント:https://ai-sdk.dev/docs
- API リファレンス:https://ai-sdk.dev/docs/reference
- 例:https://ai-sdk.dev/examples
注意: AI SDK v5 では、文字列ベースのモデル指定とパーツベースのメッセージを使用し、append の代わりに sendMessage を使用します。また、API ルートでは convertToModelMessages が必要です。