vercel-ai-sdk について
TypeScript アプリで AI 機能を開発する方向けの Vercel AI SDK v6 実装ガイドです。チャット、エージェント、ツール、構造化出力、ストリーミング、MCP 統合に加え、現行 API と検証手順を扱います。
主な機能
- 01チャットとストリーミング応答のパターン
- 02v6 API によるツール呼び出し
- 03ToolLoopAgent の実装ガイド
- 04Output ヘルパーによる構造化出力
- 05埋め込みとセマンティック検索のガイド
- 06MCP サーバー統合のガイド
活用シーン
- 01useChat によるチャットインターフェースの実装
- 02AI 応答をストリーミングする API ルートの構築
- 03エージェント型アプリケーションへのツール追加
- 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.mdcore-functions.mdmcp.mdmiddleware.mdtools.mdui-hooks.mdworkflows.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>
## ⚠️ 自律型エージェントの心構え
あなたは単にコードを書いているのではありません。タスクを自律的に完了させているのです。
つまり、次のことを行います。
- ✅ 正しい実装を書く
- ✅ 必要な依存関係をインストールする
- ✅ ビルドを実行し、すべてのエラーを修正する
- ✅ テストを実行し、すべての失敗をデバッグする
- ✅ すべてが合格するまで反復する
- ✅ 決して言い訳をしたり、諦めたりしない
避けるべきよくある失敗パターン
❌ 誤り:「コードは正しいが、パッケージがインストールされていない。これは環境の問題だ」 ✅ 正解:「パッケージが不足しているためビルドに失敗した。今すぐ 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/anthropic' Import: import { anthropic } from '@ai-sdk/anthropic' Package needed: @ai-sdk/anthropic -
正しいパッケージマネージャーでインストールする
# 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 -
ビルドを再実行して確認する
npm run build # or pnpm run build, yarn build, bun run build -
新たに発生したエラーを修正する
⚠️ 重大な警告: 誤ったパッケージマネージャー(たとえば、プロジェクトが pnpm を使用しているのに npm を使用する)を使うと、次の問題が発生します。
- 競合するロックファイルがもう1つ作成される
- 異なるバージョンの依存関係がインストールされる
- 依存関係のバージョンが一致しなくなる
- 「Cannot read properties of null」のような分かりにくいエラーでビルドが失敗する
ビルドエラーのデバッグ手順
ビルドに失敗した場合:
- エラーメッセージ全体を読む — 斜め読みしない
- 根本原因を特定する:
- モジュールが見つからない → パッケージをインストールする
- 型エラー → 型を修正する
- 設定エラー → 設定ファイルを確認する
- Next.js のエラー → 調査して、異なる方法を試す
- 修正を適用する
- ビルドを再実行する
- ビルドが成功するまで繰り返す
テスト失敗のデバッグ手順
テストに失敗した場合:
- テストエラー全体を読む — 何が期待されているのかを理解する
- 期待される動作と実際の動作を比較する
- テストのアサーションに照らして実装を確認する
- コード内の問題を修正する
- テストを再実行する
- すべてのテストが合格するまで繰り返す
成功の条件
次の条件を満たしたときに限り、タスクは完了です。
- ✅ ビルドが成功する(
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() | 非構造化 JSON | Output.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 の機能を実装する際は、次の点を確認してください。
-
クライアント側ですか、それともサーバー側ですか?
- クライアント:
useChatフックを使用します - サーバー:
generateTextまたはstreamTextを使用します - エージェント:
createAgentUIStreamResponseとToolLoopAgentを使用します
- クライアント:
-
ストリーミングと非ストリーミングのどちらが必要ですか?
- ストリーミングチャット:
streamText+toUIMessageStreamResponse() - 非ストリーミング:
generateText - シンプルなテキストストリーム:
streamText+toTextStreamResponse()
- ストリーミングチャット:
-
構造化出力が必要ですか?
- はい:
Output.object()、Output.array()、Output.choice()、またはOutput.json()を使用します outputパラメーターを介してgenerateTextまたはstreamTextに渡します
- はい:
-
ツール呼び出しが必要ですか?
- はい:
tool()ヘルパーとinputSchema(zod)を使ってツールを定義します - ツールオブジェクトを
generateText、streamText、またはToolLoopAgentに渡します
- はい:
-
エージェントを構築していますか?
- はい:
ToolLoopAgentクラスを使用します - 必要に応じて
stopWhen、toolChoice、prepareStepを設定します - API ルートでは
createAgentUIStreamResponseを使用します
- はい:
-
正しいメッセージ形式を使用していますか?
- クライアント(useChat):
partsプロパティを持つUIMessage[]を返します - サーバー:
convertToModelMessages()でModelMessage[]に変換します message.parts.map(...)を使ってメッセージをレンダリングします
- クライアント(useChat):
-
モデルの指定は正しいですか?
- 直接プロバイダーを指定:
anthropic('claude-sonnet-4-5') - ゲートウェイ(本番環境):
gateway('anthropic/claude-sonnet-4-5') - 埋め込み:
openai.textEmbeddingModel('text-embedding-3-small')
- 直接プロバイダーを指定:
-
埋め込みが必要ですか?
- 単一の値には
embedを使用します - バッチには
embedManyを使用します textEmbeddingModel()メソッドを使用します
- 単一の値には
-
ミドルウェアが必要ですか?
- ロギング:
transformParams/wrapGenerateを使ったカスタムミドルウェア - キャッシュ:結果を保存するカスタムミドルウェア
- RAG:コンテキストを挿入するカスタムミドルウェア
- ガードレール:出力をフィルタリングするカスタムミドルウェア
- ロギング:
クイックリファレンス
| タスク | 関数 | 主なパラメーター |
|---|---|---|
| テキスト生成 | generateText() | model、prompt、system、tools、output |
| テキストのストリーミング | streamText() | model、messages、tools、output、onFinish |
| チャット UI | useChat() | api、onToolCall、onFinish、onError |
| エージェントの構築 | ToolLoopAgent | model、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") |
追加リソース
迷ったときは、公式ドキュメントを確認してください。
- メインドキュメント:https://ai-sdk.dev/docs
- エージェント:https://ai-sdk.dev/docs/agents
- API リファレンス:https://ai-sdk.dev/docs/reference
- サンプル:https://ai-sdk.dev/examples
忘れないでください: AI SDK v6 では、プロバイダー関数によるモデル指定(または本番環境での gateway())、パーツベースのメッセージ、append の代わりに sendMessage、generateObject の代わりに Output ヘルパー、toDataStreamResponse の代わりに toUIMessageStreamResponse を使用し、API ルートで convertToModelMessages が必要です。