Vercel AI SDK完全ガイド — AIアプリ開発の新標準
Vercel AI SDKとは
Vercel AI SDKは、AIアプリケーション開発のための包括的なTypeScriptライブラリです。
特徴
- マルチプロバイダー対応: OpenAI、Anthropic、Google、Meta等
- ストリーミング: リアルタイムレスポンス
- 型安全: TypeScriptファーストで完全な型推論
- React統合: チャットUIコンポーネント内蔵
- エッジランタイム対応: Next.js App Router完全対応
- RAG/関数呼び出し: 高度なAI機能を簡単に実装
2026年現在、AIアプリ開発のデファクトスタンダードになっています。
インストール
Next.jsプロジェクト
npx create-next-app@latest my-ai-app
cd my-ai-app
npm install ai @ai-sdk/openai @ai-sdk/anthropic
既存プロジェクトに追加
npm install ai
プロバイダー別SDK:
# OpenAI
npm install @ai-sdk/openai
# Anthropic (Claude)
npm install @ai-sdk/anthropic
# Google (Gemini)
npm install @ai-sdk/google
# すべて
npm install @ai-sdk/openai @ai-sdk/anthropic @ai-sdk/google
基本的な使い方
シンプルなテキスト生成
import { generateText } from 'ai'
import { openai } from '@ai-sdk/openai'
const { text } = await generateText({
model: openai('gpt-4-turbo'),
prompt: 'TypeScriptの利点を3つ教えて',
})
console.log(text)
ストリーミング
import { streamText } from 'ai'
import { openai } from '@ai-sdk/openai'
const result = await streamText({
model: openai('gpt-4-turbo'),
prompt: 'Reactについて詳しく説明して',
})
for await (const chunk of result.textStream) {
process.stdout.write(chunk)
}
プロバイダー切り替え
import { anthropic } from '@ai-sdk/anthropic'
const { text } = await generateText({
model: anthropic('claude-3-5-sonnet-20241022'),
prompt: 'プロンプトエンジニアリングのコツは?',
})
Next.js App Routerとの統合
API Route(基本)
app/api/chat/route.ts:
import { streamText } from 'ai'
import { openai } from '@ai-sdk/openai'
export const runtime = 'edge'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = await streamText({
model: openai('gpt-4-turbo'),
messages,
})
return result.toDataStreamResponse()
}
フロントエンド(useChat)
app/page.tsx:
'use client'
import { useChat } from 'ai/react'
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit } = useChat()
return (
<div className="flex flex-col h-screen max-w-2xl mx-auto p-4">
<div className="flex-1 overflow-y-auto space-y-4">
{messages.map(m => (
<div
key={m.id}
className={`p-4 rounded-lg ${
m.role === 'user' ? 'bg-blue-100 ml-auto' : 'bg-gray-100'
} max-w-[80%]`}
>
<div className="font-bold mb-1">
{m.role === 'user' ? 'You' : 'AI'}
</div>
<div className="whitespace-pre-wrap">{m.content}</div>
</div>
))}
</div>
<form onSubmit={handleSubmit} className="mt-4 flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="メッセージを入力..."
className="flex-1 p-2 border rounded"
/>
<button
type="submit"
className="px-4 py-2 bg-blue-500 text-white rounded"
>
送信
</button>
</form>
</div>
)
}
これだけで完全なチャットUIが動きます。
高度な機能
システムプロンプト
export async function POST(req: Request) {
const { messages } = await req.json()
const result = await streamText({
model: openai('gpt-4-turbo'),
system: 'あなたは親切なプログラミング講師です。初心者にもわかりやすく説明してください。',
messages,
})
return result.toDataStreamResponse()
}
温度・トークン数設定
const result = await streamText({
model: openai('gpt-4-turbo'),
messages,
temperature: 0.7, // 0-2、高いほど創造的
maxTokens: 1000, // 最大トークン数
topP: 0.9, // nucleus sampling
})
複数プロバイダー対応
import { openai } from '@ai-sdk/openai'
import { anthropic } from '@ai-sdk/anthropic'
import { google } from '@ai-sdk/google'
const models = {
'gpt-4': openai('gpt-4-turbo'),
'claude': anthropic('claude-3-5-sonnet-20241022'),
'gemini': google('gemini-1.5-pro'),
}
export async function POST(req: Request) {
const { messages, model = 'gpt-4' } = await req.json()
const result = await streamText({
model: models[model],
messages,
})
return result.toDataStreamResponse()
}
フロントエンド:
const { messages, input, handleInputChange, handleSubmit } = useChat({
body: { model: 'claude' } // プロバイダー切り替え
})
ストリーミング状態管理
'use client'
import { useChat } from 'ai/react'
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat()
return (
<div>
{/* メッセージ表示 */}
{messages.map(m => (
<div key={m.id}>{m.content}</div>
))}
{/* ローディング表示 */}
{isLoading && (
<div className="flex items-center gap-2">
<div className="animate-spin h-4 w-4 border-2 border-blue-500 rounded-full border-t-transparent" />
<span>考え中...</span>
</div>
)}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
<button disabled={isLoading}>送信</button>
</form>
</div>
)
}
関数呼び出し(Tools)
AIに外部ツールを使わせる機能です。
天気取得例
import { streamText, tool } from 'ai'
import { openai } from '@ai-sdk/openai'
import { z } from 'zod'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = await streamText({
model: openai('gpt-4-turbo'),
messages,
tools: {
getWeather: tool({
description: '指定された都市の天気を取得します',
parameters: z.object({
city: z.string().describe('都市名(例: Tokyo)'),
}),
execute: async ({ city }) => {
// 実際のAPI呼び出し
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${process.env.WEATHER_API_KEY}`
)
const data = await response.json()
return {
temperature: data.main.temp,
description: data.weather[0].description,
}
},
}),
},
})
return result.toDataStreamResponse()
}
ユーザーが「東京の天気は?」と聞くと、AIが自動的にgetWeatherツールを呼び出します。
複数ツール
tools: {
getWeather: tool({
description: '天気を取得',
parameters: z.object({
city: z.string(),
}),
execute: async ({ city }) => {
// 天気取得
},
}),
searchWeb: tool({
description: 'Web検索',
parameters: z.object({
query: z.string(),
}),
execute: async ({ query }) => {
// Web検索
},
}),
calculateMath: tool({
description: '数式計算',
parameters: z.object({
expression: z.string(),
}),
execute: async ({ expression }) => {
// 計算
},
}),
}
AIが状況に応じて適切なツールを選択します。
RAG(検索拡張生成)
ベクトル検索 + AI生成
import { embed } from 'ai'
import { openai } from '@ai-sdk/openai'
// ドキュメントを埋め込み
const { embedding } = await embed({
model: openai.embedding('text-embedding-3-small'),
value: 'TypeScriptの型システムについて',
})
// ベクトルDBで類似検索(例: Pinecone、Supabase等)
const results = await vectorDB.search(embedding, { topK: 5 })
// 検索結果を使ってAI生成
const { text } = await generateText({
model: openai('gpt-4-turbo'),
prompt: `
以下のドキュメントを参考に質問に答えてください。
ドキュメント:
${results.map(r => r.content).join('\n\n')}
質問: TypeScriptの型システムについて教えて
`,
})
useChat + RAG
app/api/chat/route.ts:
export async function POST(req: Request) {
const { messages } = await req.json()
// 最新メッセージから検索
const lastMessage = messages[messages.length - 1].content
// ベクトル検索
const { embedding } = await embed({
model: openai.embedding('text-embedding-3-small'),
value: lastMessage,
})
const docs = await vectorDB.search(embedding, { topK: 3 })
// コンテキストに追加
const result = await streamText({
model: openai('gpt-4-turbo'),
system: `
以下のドキュメントを参考に質問に答えてください。
${docs.map(d => d.content).join('\n\n')}
`,
messages,
})
return result.toDataStreamResponse()
}
マルチモーダル(画像・音声)
画像入力
import { generateText } from 'ai'
import { openai } from '@ai-sdk/openai'
const { text } = await generateText({
model: openai('gpt-4-vision-preview'),
messages: [
{
role: 'user',
content: [
{ type: 'text', text: 'この画像に何が映っていますか?' },
{
type: 'image',
image: 'https://example.com/image.jpg',
// または Base64
// image: 'data:image/jpeg;base64,...'
},
],
},
],
})
画像生成(DALL-E)
import { experimental_generateImage as generateImage } from 'ai'
import { openai } from '@ai-sdk/openai'
const { image } = await generateImage({
model: openai.image('dall-e-3'),
prompt: '未来都市の風景、サイバーパンク風',
size: '1024x1024',
})
// image.url または image.base64
音声認識(Whisper)
import { experimental_generateTranscription as generateTranscription } from 'ai'
import { openai } from '@ai-sdk/openai'
const { text } = await generateTranscription({
model: openai.transcription('whisper-1'),
audio: audioFileBuffer,
})
useChat高度な使い方
初期メッセージ
const { messages } = useChat({
initialMessages: [
{
id: '1',
role: 'system',
content: 'あなたは親切なアシスタントです',
},
{
id: '2',
role: 'assistant',
content: 'こんにちは!何かお手伝いできることはありますか?',
},
],
})
カスタムヘッダー
const { messages } = useChat({
headers: {
'Authorization': `Bearer ${token}`,
'X-Custom-Header': 'value',
},
})
エラーハンドリング
const { messages, error } = useChat({
onError: (error) => {
console.error('エラー:', error)
},
})
if (error) {
return <div>エラーが発生しました: {error.message}</div>
}
メッセージ送信完了イベント
const { messages } = useChat({
onFinish: (message) => {
console.log('完了:', message)
// ログ保存、通知など
},
})
プログラムからメッセージ送信
const { append } = useChat()
// ボタンクリックで送信
<button onClick={() => append({ role: 'user', content: 'こんにちは' })}>
挨拶
</button>
useCompletion(単発生成)
チャット形式でなく、単発の生成に使います。
API Route
app/api/completion/route.ts:
import { streamText } from 'ai'
import { openai } from '@ai-sdk/openai'
export async function POST(req: Request) {
const { prompt } = await req.json()
const result = await streamText({
model: openai('gpt-4-turbo'),
prompt,
})
return result.toDataStreamResponse()
}
フロントエンド
'use client'
import { useCompletion } from 'ai/react'
export default function CompletionPage() {
const { completion, input, handleInputChange, handleSubmit } = useCompletion({
api: '/api/completion',
})
return (
<div>
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
<button type="submit">生成</button>
</form>
<div>{completion}</div>
</div>
)
}
エッジランタイム最適化
Cloudflare Workers
import { streamText } from 'ai'
import { openai } from '@ai-sdk/openai'
export default {
async fetch(request: Request, env: Env) {
const { messages } = await request.json()
const result = await streamText({
model: openai('gpt-4-turbo', {
apiKey: env.OPENAI_API_KEY,
}),
messages,
})
return result.toDataStreamResponse()
},
}
Vercel Edge Functions
export const runtime = 'edge'
export const preferredRegion = 'iad1' // 最寄りリージョン
export async function POST(req: Request) {
// 処理
}
実践例: AIチャットボット
完全な実装例:
app/api/chat/route.ts:
import { streamText, tool } from 'ai'
import { openai } from '@ai-sdk/openai'
import { z } from 'zod'
export const runtime = 'edge'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = await streamText({
model: openai('gpt-4-turbo'),
system: `
あなたは親切なカスタマーサポートAIです。
以下のルールを守ってください:
- 丁寧な言葉遣い
- 簡潔でわかりやすい説明
- わからないことは正直に伝える
`,
messages,
tools: {
searchDocs: tool({
description: 'ドキュメントを検索',
parameters: z.object({
query: z.string(),
}),
execute: async ({ query }) => {
// 実際のドキュメント検索
const results = await searchDocumentation(query)
return results
},
}),
createTicket: tool({
description: 'サポートチケット作成',
parameters: z.object({
title: z.string(),
description: z.string(),
priority: z.enum(['low', 'medium', 'high']),
}),
execute: async ({ title, description, priority }) => {
// チケット作成
const ticket = await createSupportTicket({ title, description, priority })
return ticket
},
}),
},
maxTokens: 1000,
temperature: 0.7,
})
return result.toDataStreamResponse()
}
app/page.tsx:
'use client'
import { useChat } from 'ai/react'
import { useState } from 'react'
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat()
const [showSuggestions, setShowSuggestions] = useState(true)
const suggestions = [
'使い方を教えて',
'料金プランを知りたい',
'トラブルシューティング',
]
return (
<div className="flex flex-col h-screen max-w-4xl mx-auto">
{/* ヘッダー */}
<header className="p-4 border-b bg-white">
<h1 className="text-xl font-bold">カスタマーサポート</h1>
</header>
{/* メッセージエリア */}
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.length === 0 && showSuggestions && (
<div className="space-y-2">
<p className="text-gray-600">よくある質問:</p>
{suggestions.map((s, i) => (
<button
key={i}
onClick={() => {
handleInputChange({ target: { value: s } } as any)
setShowSuggestions(false)
}}
className="block w-full text-left p-3 border rounded hover:bg-gray-50"
>
{s}
</button>
))}
</div>
)}
{messages.map(m => (
<div
key={m.id}
className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-[80%] p-4 rounded-lg ${
m.role === 'user'
? 'bg-blue-500 text-white'
: 'bg-gray-100 text-gray-900'
}`}
>
<div className="whitespace-pre-wrap">{m.content}</div>
</div>
</div>
))}
{isLoading && (
<div className="flex justify-start">
<div className="bg-gray-100 p-4 rounded-lg">
<div className="flex items-center gap-2">
<div className="animate-spin h-4 w-4 border-2 border-gray-400 rounded-full border-t-transparent" />
<span className="text-gray-600">考え中...</span>
</div>
</div>
</div>
)}
</div>
{/* 入力エリア */}
<form onSubmit={handleSubmit} className="p-4 border-t bg-white">
<div className="flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="メッセージを入力..."
className="flex-1 p-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading || !input.trim()}
className="px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
>
送信
</button>
</div>
</form>
</div>
)
}
まとめ
Vercel AI SDKは2026年現在、AIアプリ開発の最有力ライブラリです。
メリット
- 生産性: チャットUIが数行で実装可能
- 柔軟性: 複数プロバイダー対応、簡単に切り替え
- 型安全: TypeScriptで完全な型推論
- パフォーマンス: エッジランタイム対応、ストリーミング
- 高機能: RAG、関数呼び出し、マルチモーダル
ユースケース
- チャットボット
- ドキュメント検索(RAG)
- コード生成ツール
- カスタマーサポート
- AIアシスタント
Next.jsとの相性が最高で、数時間で本格的なAIアプリが作れます。