この記事の要点
• Vercel AI SDKは主要AIプロバイダを統一インターフェイスで扱えるTypeScriptライブラリ
• ストリーミング・Tool Calling・構造化出力・RSC連携を一括提供
• Next.jsとの深い統合でフルスタックAIアプリを最速で構築可能
Vercel AI SDK は、Vercel が公開する TypeScript 向けの生成 AI 開発ライブラリです。OpenAI / Anthropic / Google / Mistral / xAI など主要プロバイダを統一インターフェイスで扱え、ストリーミング・Tool Calling・構造化出力・React Server Components 連携など、現代的な AI アプリ構築に必要な要素をひとまとめに提供してくれます。本記事では Vercel AI SDK の主要機能を、Next.js のサンプルとともに体系的に紹介します。
概要
flowchart TB
subgraph SDK["Vercel AI SDK"]
Core["@ai-sdk/* core"] --> Providers
Core --> APIs
subgraph Providers["プロバイダ"]
P1["@ai-sdk/openai"]
P2["@ai-sdk/anthropic"]
P3["@ai-sdk/google"]
P4["@ai-sdk/mistral"]
end
subgraph APIs["主要 API"]
A1["generateText"]
A2["streamText"]
A3["generateObject"]
A4["streamObject"]
A5["tool"]
end
UI["ai/react / ai/rsc"] --> Core
end
Vercel AI SDK は「コア」「プロバイダ」「UI バインディング」の 3 層構成になっており、フロントエンドからサーバーアクション、API ルートまで一貫した型で扱えるのが特長です。
主要機能
1. プロバイダ非依存の generateText / streamText
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
const { text } = await generateText({
model: openai("gpt-4o-mini"),
prompt: "TypeScript の型安全性のメリットを 3 つ挙げて",
});
console.log(text);
プロバイダを差し替えるだけで別モデルに切り替えられます。
import { anthropic } from "@ai-sdk/anthropic";
const { text } = await generateText({
model: anthropic("claude-3-5-sonnet-latest"),
prompt: "TypeScript の型安全性のメリットを 3 つ挙げて",
});
2. ストリーミング応答
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
const result = await streamText({
model: openai("gpt-4o-mini"),
prompt: "Promise と async/await の違いを説明して",
});
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}
3. 構造化出力 (generateObject)
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
const { object } = await generateObject({
model: openai("gpt-4o-mini"),
schema: z.object({
title: z.string(),
tags: z.array(z.string()).max(5),
summary: z.string().max(140),
}),
prompt: "TypeScript 入門記事のメタ情報を生成して",
});
console.log(object.title, object.tags);
zod スキーマを渡すだけで、型推論付きでオブジェクトを取得できます。
4. Tool Calling
import { generateText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
const result = await generateText({
model: openai("gpt-4o-mini"),
tools: {
getWeather: tool({
description: "指定都市の天気を取得",
parameters: z.object({ city: z.string() }),
execute: async ({ city }) => {
// 実際は外部 API を呼ぶ
return { city, tempC: 21, condition: "sunny" };
},
}),
},
prompt: "東京の天気を教えて",
});
console.log(result.toolResults);
5. React UI ヘルパー
"use client";
import { useChat } from "ai/react";
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: "/api/chat",
});
return (
<div>
<ul>
{messages.map((m) => (
<li key={m.id}>
<strong>{m.role}:</strong> {m.content}
</li>
))}
</ul>
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} placeholder="メッセージ" />
<button type="submit">送信</button>
</form>
</div>
);
}
// app/api/chat/route.ts
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = await streamText({
model: openai("gpt-4o-mini"),
messages,
});
return result.toDataStreamResponse();
}
ポイント: generateText/streamTextの切り替えだけでバッチ処理とリアルタイムストリーミングを使い分けられます。
実践サンプル: RAG API
import { streamText, embed } from "ai";
import { openai } from "@ai-sdk/openai";
async function search(query: string) {
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: query,
});
// pgvector / Qdrant 等を呼ぶ想定
return await vectorStore.search(embedding, { limit: 5 });
}
export async function POST(req: Request) {
const { question } = await req.json();
const docs = await search(question);
const context = docs.map((d) => `- ${d.text}`).join("\n");
const result = await streamText({
model: openai("gpt-4o-mini"),
system: "与えられたコンテキストのみを根拠に答え、引用元を必ず示すこと。",
prompt: `# Context\n${context}\n\n# Question\n${question}`,
});
return result.toDataStreamResponse();
}
比較表 (アプローチ比較)
| 項目 | Vercel AI SDK | LangChain.js | 自前ラッパ |
|---|---|---|---|
| 学習コスト | 低 | 中〜高 | 中 |
| 型安全性 | 高 (zod 連携) | 中 | 自分次第 |
| プロバイダ抽象化 | あり | あり | 自前 |
| UI バインディング | あり (React/Svelte 等) | なし | 自前 |
| ストリーミング | 一級サポート | 可能 | 自前 |
実践メモ: providerのmodel IDを環境変数で管理すると、OpenAI→Anthropicなどプロバイダ切り替えがコード変更なしで可能です。
ベストプラクティス
1. zod スキーマ駆動
generateObject は出力検証を兼ねるため、API レスポンスやフォーム生成など「型が大事」な場面で積極的に使う。
2. プロバイダ差し替え可能に書く
model を環境変数で切り替えられるよう、薄いファクトリ関数を用意しておく。
export function getModel() {
const name = process.env.AI_MODEL ?? "openai:gpt-4o-mini";
const [provider, model] = name.split(":");
if (provider === "anthropic") return anthropic(model);
if (provider === "mistral") return mistral(model);
return openai(model);
}
3. ストリーミングを基本に
UX が大きく改善するため、特別な理由がなければ streamText を採用。
4. Tool 実行の安全策
execute 内で外部 API を叩く際は、引数のバリデーション・タイムアウト・リトライを必ず実装。
5. ロギング
入力・出力・トークン数・所要時間をトレースに残し、コストと品質を継続評価する。
注意: ストリーミングレスポンスではエラーハンドリングのタイミングが異なります。try-catchだけでなくonErrorコールバックも併用しましょう。
注意点・落とし穴
- プロバイダ差: モデルによって Tool Calling のネスト深度や JSON 出力の安定度が異なります。
- コスト: ストリーミング中でもトークン課金は発生します。長文応答にはリミットを設定。
- エッジランタイム: 一部 Node API は使えないため、依存ライブラリの互換性を確認。
- 依存バージョン: SDK と各プロバイダパッケージのバージョン整合に注意。
- PII 取り扱い: 機微情報をそのまま外部 API に送らない設計を徹底。
導入手順
npm install ai @ai-sdk/openai zodOPENAI_API_KEYを.env.localに設定- サーバー側で
streamTextを使う API ルートを作成 - クライアント側で
useChatを使い UI を組む - zod スキーマを使った構造化出力を導入
- ログ・トレース・コスト計測を組み込む
- プロバイダ差し替え用の薄い抽象化を入れる
パフォーマンス観点
- ストリーミング遅延 (TTFB / TTFT) を計測
- レスポンス長の上限設定でコスト暴走を防ぐ
- キャッシュ可能な部分 (システムプロンプト等) を活用
- エッジランタイムでユーザー近接実行
FAQ
Q1. Next.js 専用ですか?
A. いいえ。Node.js / Bun / Deno / SvelteKit / Remix など TypeScript が動く環境で利用できます。
Q2. LangChain との違いは?
A. Vercel AI SDK はよりシンプルで型に強く、UI バインディングを含む点が特徴。エージェントフレームワーク全体を組むなら LangChain も検討対象です。
Q3. RAG はどう作る?
A. embed / embedMany でベクトル化し、好きなベクター DB と組み合わせます。検索結果をプロンプトに渡すだけで良いシンプルな構成です。
Q4. オンプレモデルは使えますか?
A. OpenAI 互換エンドポイントを提供する vLLM / Ollama などをカスタムプロバイダとしてラップすることで利用可能です。
Q5. 型安全性はどこまで担保される?
A. zod スキーマ + generateObject で、応答オブジェクトの型推論まで TypeScript で受けられます。
まとめ
Vercel AI SDK は「TypeScript で生成 AI アプリを書く」際のデファクトに近い存在です。プロバイダ抽象化、ストリーミング、構造化出力、Tool Calling、UI バインディングを統一インターフェイスで扱えるため、PoC から本番まで一貫したコードベースを保てます。まずは streamText と useChat の組み合わせで小さなチャットを作り、そこから RAG・エージェント・構造化出力へと段階的に拡張していくのがおすすめです。
マルチステップ Tool Calling
エージェントのように、LLM がツールを呼び、その結果を踏まえてさらに思考する流れも generateText の maxSteps (または同等オプション) で表現できます。
import { generateText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
const result = await generateText({
model: openai("gpt-4o-mini"),
maxSteps: 5,
tools: {
searchDocs: tool({
description: "社内ドキュメントを検索",
parameters: z.object({ query: z.string() }),
execute: async ({ query }) => {
return await searchVectorStore(query);
},
}),
createTicket: tool({
description: "サポートチケットを作成",
parameters: z.object({
title: z.string(),
body: z.string(),
priority: z.enum(["low", "mid", "high"]),
}),
execute: async (input) => {
return await ticketApi.create(input);
},
}),
},
prompt: "ログインできないという問い合わせを受けた。原因候補を調べてチケットを作って",
});
console.log(result.text);
React Server Components との統合
Vercel AI SDK には RSC 向けのストリーミング UI 構築機能 (ai/rsc) があり、サーバー側で生成途中の React コンポーネントを段階的にクライアントへ送ることができます。これを使うと、ストリーミングテキストだけでなく、ストリーミング UI が実現できます。
// app/actions.ts
"use server";
import { createStreamableUI } from "ai/rsc";
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
export async function ask(question: string) {
const ui = createStreamableUI(<div>考え中...</div>);
(async () => {
const result = await streamText({
model: openai("gpt-4o-mini"),
prompt: question,
});
let text = "";
for await (const chunk of result.textStream) {
text += chunk;
ui.update(<article>{text}</article>);
}
ui.done();
})();
return ui.value;
}
エラーハンドリングとリトライ
LLM API はネットワーク・レート制限・タイムアウトでエラーになることがあります。指数バックオフを挟んだリトライ層を作っておくと安心です。
async function withRetry<T>(fn: () => Promise<T>, retries = 3): Promise<T> {
let lastErr: unknown;
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (e) {
lastErr = e;
const delay = 500 * 2 ** i + Math.random() * 200;
await new Promise((r) => setTimeout(r, delay));
}
}
throw lastErr;
}
const { text } = await withRetry(() =>
generateText({ model: openai("gpt-4o-mini"), prompt: "Hello" }),
);
監視とトークン計測
generateText / streamText は usage 情報 (入出力トークン数) を返してくれます。ログに必ず残し、コスト・回帰検知に役立てましょう。
const result = await generateText({
model: openai("gpt-4o-mini"),
prompt: "Hello",
});
console.log({
promptTokens: result.usage.promptTokens,
completionTokens: result.usage.completionTokens,
totalTokens: result.usage.totalTokens,
finishReason: result.finishReason,
});
評価セットによる回帰テスト
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
const cases = [
{ input: "TypeScript の特徴は?", mustContain: ["型"] },
{ input: "React の useState は?", mustContain: ["状態"] },
];
for (const c of cases) {
const { object } = await generateObject({
model: openai("gpt-4o-mini"),
schema: z.object({ answer: z.string() }),
prompt: c.input,
});
const ok = c.mustContain.every((k) => object.answer.includes(k));
console.log(c.input, ok ? "PASS" : "FAIL");
}
エッジ環境での運用
Vercel Edge Functions / Cloudflare Workers などのエッジランタイムでも Vercel AI SDK は動作します。ユーザーに地理的に近いリージョンで実行することで TTFB を短縮でき、生成 AI 体験が滑らかになります。エッジ環境では Node 固有 API の利用に制限があるため、依存ライブラリの互換性を確認しましょう。
プロバイダ別の小ネタ
OpenAI
gpt-4o-miniはコスト効率が高く、PoC や軽量タスクに向く- 構造化出力は zod 経由で安定
Anthropic
- 長文要約・コードレビューに強い
- システムプロンプトをしっかり書くほど性能を発揮
- マルチモーダル入力 (画像) との組み合わせがしやすい
- 長コンテキスト処理に強み
Mistral
- 欧州データ主権要件に向く
- Codestral とのモデル切替で用途分担
キャッシュ戦略
完全に同一なプロンプトは KV キャッシュ的な仕組みでコスト削減できる場合があります。アプリ側でも「同じ入力の結果」を Redis などにキャッシュしておけば、定型応答系のレイテンシとコストを大幅に下げられます。
import { createClient } from "redis";
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
async function cachedAsk(prompt: string) {
const key = `ai:${prompt}`;
const hit = await redis.get(key);
if (hit) return hit;
const { text } = await generateText({
model: openai("gpt-4o-mini"),
prompt,
});
await redis.set(key, text, { EX: 60 * 60 });
return text;
}
テスト戦略
- 単体テストではプロバイダ呼び出しをモック
- 統合テストでは固定プロンプト + 期待文字列の包含チェック
- 評価セットによる回帰テストを CI に組み込み
- LLM as a Judge: モデル自身に出力品質を採点させる手法も併用可
既存アプリへの段階導入のコツ
- まずは 1 つの機能 (例: 要約ボタン) から導入
- サーバーサイドで
generateTextを試し、動作確認 - UX を改善するため
streamText+useChatに置き換え - 構造化が必要な箇所を
generateObjectに移行 - 共通ロジック (リトライ・ロギング・PII マスキング) をラップ
- プロバイダ抽象化レイヤーを追加し、モデル切替を容易に
- 評価セットを CI に組み込み、回帰検知を自動化