TypeScript × AI開発 — 型安全なAIアプリケーション構築
AIアプリケーション開発においてTypeScriptの型システムは強力な武器になる。APIレスポンスの型定義、ストリーミング処理の型安全な実装、エラーハンドリングの網羅性チェックなど、型があることで実行時エラーを大幅に減らせる。本記事では、Anthropic SDKとVercel AI SDKを中心に、型安全なAIアプリケーションの構築手法を実践的に解説する。
Anthropic SDK for TypeScriptの基本設定
Anthropic公式のTypeScript SDKは、APIレスポンスの型定義が充実しており、型推論だけでほとんどのケースをカバーできる。
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
async function sendMessage(prompt: string): Promise<string> {
const response = await client.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
});
const textBlock = response.content.find((block) => block.type === "text");
if (!textBlock || textBlock.type !== "text") {
throw new Error("No text content in response");
}
return textBlock.text;
}
環境変数の型安全な管理
AI開発ではAPIキーや設定値を環境変数で管理することが多い。zodを使って環境変数のバリデーションと型付けを同時に行う。
import { z } from "zod";
const envSchema = z.object({
ANTHROPIC_API_KEY: z.string().min(1),
AI_MODEL: z
.enum(["claude-sonnet-4-20250514", "claude-opus-4-20250514"])
.default("claude-sonnet-4-20250514"),
MAX_TOKENS: z.coerce.number().int().min(1).max(8192).default(1024),
TEMPERATURE: z.coerce.number().min(0).max(1).default(0.7),
});
type Env = z.infer<typeof envSchema>;
構造化出力と型バリデーション
AIからの出力をアプリケーションで安全に使うには、レスポンスの構造をバリデーションする必要がある。zodスキーマを定義し、AIの出力をパースすることで型安全な構造化データを得られる。
const ProductReviewSchema = z.object({
sentiment: z.enum(["positive", "negative", "neutral"]),
score: z.number().min(0).max(100),
summary: z.string().max(200),
keyPoints: z.array(z.string()).min(1).max(5),
recommendation: z.boolean(),
});
type ProductReview = z.infer<typeof ProductReviewSchema>;
async function analyzeReview(reviewText: string): Promise<ProductReview> {
const response = await client.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
system: "レビューを分析してJSON形式で出力してください。",
messages: [{ role: "user", content: reviewText }],
});
const text =
response.content[0].type === "text" ? response.content[0].text : "";
const jsonMatch = text.match(/\{[\s\S]*\}/);
if (!jsonMatch) throw new Error("No JSON found in AI response");
return ProductReviewSchema.parse(JSON.parse(jsonMatch[0]));
}
バリデーションエラーのリトライ戦略
AIの出力がスキーマに合致しない場合、エラー内容をフィードバックしてリトライする仕組みを実装する。
async function analyzeWithRetry(
reviewText: string,
maxRetries: number = 3
): Promise<ProductReview> {
let lastError: Error | null = null;
for (let i = 0; i < maxRetries; i++) {
try {
return await analyzeReview(reviewText);
} catch (error) {
lastError = error as Error;
console.warn("Validation failed on attempt", i + 1);
}
}
throw lastError || new Error("All retries failed");
}
ストリーミングレスポンスの型安全な実装
チャットUIなどではストリーミング出力が必須になる。Anthropic SDKのストリーミングAPIは型安全なイベントハンドリングを提供している。
async function streamResponse(
prompt: string,
onChunk: (text: string) => void,
onComplete: (fullText: string, usage: {
inputTokens: number;
outputTokens: number;
}) => void
): Promise<void> {
const stream = await client.messages.stream({
model: "claude-sonnet-4-20250514",
max_tokens: 2048,
messages: [{ role: "user", content: prompt }],
});
let fullText = "";
for await (const event of stream) {
if (
event.type === "content_block_delta" &&
event.delta.type === "text_delta"
) {
fullText += event.delta.text;
onChunk(event.delta.text);
}
}
const finalMessage = await stream.finalMessage();
onComplete(fullText, {
inputTokens: finalMessage.usage.input_tokens,
outputTokens: finalMessage.usage.output_tokens,
});
}
Vercel AI SDKとの統合
Vercel AI SDKは、複数のAIプロバイダーを統一的なインターフェースで扱える抽象化レイヤーである。Next.jsとの統合が特に強力で、Server ActionsやRoute Handlersとシームレスに連携できる。
// app/api/chat/route.ts (Next.js App Router)
import { anthropic } from "@ai-sdk/anthropic";
import { streamText } from "ai";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: anthropic("claude-sonnet-4-20250514"),
system: "あなたは親切なアシスタントです。日本語で回答してください。",
messages,
maxTokens: 2048,
});
return result.toDataStreamResponse();
}
クライアント側の実装
"use client";
import { useChat } from "@ai-sdk/react";
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading } =
useChat({ api: "/api/chat" });
return (
<div className="max-w-2xl mx-auto p-4">
<div className="space-y-4">
{messages.map((m) => (
<div key={m.id} className="p-3 rounded-lg">{m.content}</div>
))}
</div>
<form onSubmit={handleSubmit} className="flex gap-2 pt-4">
<input value={input} onChange={handleInputChange}
className="flex-1 border rounded-lg px-4 py-2" />
<button type="submit" disabled={isLoading}
className="bg-blue-500 text-white px-4 py-2 rounded-lg">送信</button>
</form>
</div>
);
}
エラーハンドリングのパターン
AI API呼び出しでは、レート制限、タイムアウト、無効なレスポンスなど、様々なエラーが発生しうる。TypeScriptのDiscriminated Unionを使って、エラーを型安全に分類・処理する。
type AIError =
| { type: "rate_limit"; retryAfter: number }
| { type: "timeout"; elapsedMs: number }
| { type: "auth_error"; message: string }
| { type: "unknown"; error: Error };
function classifyError(error: unknown): AIError {
if (error instanceof Anthropic.RateLimitError) {
return { type: "rate_limit", retryAfter: 60 };
}
if (error instanceof Anthropic.AuthenticationError) {
return { type: "auth_error", message: error.message };
}
return { type: "unknown", error: error as Error };
}
async function handleAICall<T>(fn: () => Promise<T>): Promise<T> {
try {
return await fn();
} catch (error) {
const classified = classifyError(error);
switch (classified.type) {
case "rate_limit":
await new Promise((r) => setTimeout(r, classified.retryAfter * 1000));
return handleAICall(fn);
case "auth_error":
throw new Error("APIキーを確認してください");
default:
throw classified.type === "unknown" ? classified.error : new Error(classified.type);
}
}
}
Tool Use(Function Calling)の型定義
Claude APIのTool Use機能をTypeScriptで型安全に実装することで、AIが外部関数を呼び出すエージェントパターンを構築できる。
interface WeatherTool {
name: "get_weather";
input: { city: string; unit?: "celsius" | "fahrenheit" };
}
interface SearchTool {
name: "search_web";
input: { query: string; limit?: number };
}
type ToolCall = WeatherTool | SearchTool;
async function executeTool(tool: ToolCall): Promise<string> {
switch (tool.name) {
case "get_weather":
return "Weather data for " + tool.input.city;
case "search_web":
return "Search results for " + tool.input.query;
default:
const _exhaustive: never = tool;
throw new Error("Unknown tool");
}
}
比較: 主要AI SDK
| 特徴 | Anthropic SDK | Vercel AI SDK | LangChain.js |
|---|---|---|---|
| 型安全性 | 高(公式型定義) | 高(Zodスキーマ統合) | 中(ジェネリクスベース) |
| ストリーミング | ネイティブ対応 | React Hooks統合 | アダプター経由 |
| マルチプロバイダー | Claude専用 | 複数対応 | 複数対応 |
| バンドルサイズ | 軽量 | 中程度 | 大きい |
| 学習コスト | 低い | 中程度 | 高い |
関連記事
- Anthropic API完全ガイド - APIの認証設定やモデル選択の詳細
- Vercel × AIデプロイ - Next.js + AI SDKのデプロイ方法
- AIエージェントの出力バリデーション - AI出力の品質担保手法
- AIエージェント向けプロンプトエンジニアリング - システムプロンプト設計の極意
Agentive 編集部
AIエージェントを実際に使い倒す個人開発者。サイト制作の自動化を実践しながら、その知見を発信しています。