AnthropicのClaude APIは、GPT系のモデルと比較してコンテキストの理解力が高く、長文の指示に対しても忠実に応答してくれます。今回は週末の2日間を使い、Claude APIを活用したAIチャットアプリをゼロから構築しました。この記事では、APIのセットアップからストリーミング実装、プロンプトエンジニアリング、エラーハンドリング、Vercelへのデプロイまでの全工程を実際のコードとともに解説します。
Claude APIのセットアップ
まず、Anthropicの公式サイト(console.anthropic.com)でアカウントを作成し、APIキーを発行します。APIキーは「sk-ant-」から始まる文字列で、絶対にソースコードに直書きしてはいけません。環境変数として管理します。
SDKのインストールはnpmで行います。2024年後半からAnthropicはTypeScript SDKを大幅に改善しており、型補完が非常に充実しています。
# SDKのインストール
npm install @anthropic-ai/sdk
# 環境変数ファイルの作成
echo "ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxx" > .env.local環境変数はNext.jsの場合、サーバーサイドのAPIルートからのみアクセスします。クライアントコンポーネントからAPIキーを直接呼び出すと、ブラウザ経由で漏洩するリスクがあります。
ストリーミングレスポンスの実装
チャットアプリの体験を大きく左右するのがストリーミングです。通常のレスポンスはAPIが全文を生成し終わるまでユーザーは待ち続けますが、ストリーミングを使うと文字が流れるように表示されます。Claude APIはServer-Sent Events(SSE)形式のストリーミングに対応しています。
// app/api/chat/route.ts
import Anthropic from '@anthropic-ai/sdk'
import { NextRequest } from 'next/server'
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
})
export async function POST(req: NextRequest) {
const { messages } = await req.json()
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
try {
const response = await client.messages.create({
model: 'claude-opus-4-5',
max_tokens: 1024,
stream: true,
messages,
})
for await (const event of response) {
if (
event.type === 'content_block_delta' &&
event.delta.type === 'text_delta'
) {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({ text: event.delta.text })}\n\n`)
)
}
}
controller.enqueue(encoder.encode('data: [DONE]\n\n'))
controller.close()
} catch (error) {
controller.error(error)
}
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
})
}クライアント側では、fetch APIのReaderを使ってストリームを読み取ります。React Stateと組み合わせることで、テキストが流れるようなUXを実現できます。
// components/ChatInterface.tsx
'use client'
import { useState } from 'react'
interface Message {
role: 'user' | 'assistant'
content: string
}
export function ChatInterface() {
const [messages, setMessages] = useState<Message[]>([])
const [input, setInput] = useState('')
const [isStreaming, setIsStreaming] = useState(false)
const sendMessage = async () => {
if (!input.trim() || isStreaming) return
const userMessage: Message = { role: 'user', content: input }
const newMessages = [...messages, userMessage]
setMessages(newMessages)
setInput('')
setIsStreaming(true)
// アシスタントメッセージの枠を先に追加
setMessages((prev) => [...prev, { role: 'assistant', content: '' }])
try {
const res = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages: newMessages }),
})
const reader = res.body?.getReader()
const decoder = new TextDecoder()
if (!reader) return
while (true) {
const { done, value } = await reader.read()
if (done) break
const chunk = decoder.decode(value)
const lines = chunk.split('\n').filter((l) => l.startsWith('data: '))
for (const line of lines) {
const data = line.replace('data: ', '')
if (data === '[DONE]') break
const parsed = JSON.parse(data) as { text: string }
setMessages((prev) => {
const updated = [...prev]
updated[updated.length - 1] = {
role: 'assistant',
content: updated[updated.length - 1].content + parsed.text,
}
return updated
})
}
}
} finally {
setIsStreaming(false)
}
}
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 mb-4">
{messages.map((msg, i) => (
<div
key={i}
className={`p-3 rounded-lg ${
msg.role === 'user'
? 'bg-blue-100 ml-8'
: 'bg-gray-100 mr-8'
}`}
>
{msg.content}
</div>
))}
</div>
<div className="flex gap-2">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && sendMessage()}
className="flex-1 border rounded-lg px-3 py-2"
placeholder="メッセージを入力..."
/>
<button
onClick={sendMessage}
disabled={isStreaming}
className="bg-blue-600 text-white px-4 py-2 rounded-lg disabled:opacity-50"
>
送信
</button>
</div>
</div>
)
}プロンプトエンジニアリングの基本
Claude APIを効果的に使うには、systemプロンプトの設計が重要です。Claude 3以降のモデルはsystemプロンプトをとても忠実に守ります。以下のポイントを押さえることで、アプリの品質が大幅に向上します。
- ペルソナを明確に定義する(例:「あなたはTypeScriptの専門家エンジニアです」)
- 出力フォーマットを具体的に指定する(JSON形式、マークダウン形式など)
- 禁止事項をリストアップする(例:「架空の情報を作らない」)
- few-shotサンプルを提供する(理想の入出力ペアを2〜3個)
- 思考の連鎖(Chain of Thought)を促す(例:「ステップバイステップで考えてください」)
エラーハンドリングとレート制限
Claude APIには1分あたりのリクエスト数とトークン数に制限があります。無料枠ではAPIコールが少ないため、本格的なアプリでは有料プランへの移行が必要です。エラーが発生した際の適切なハンドリングも重要です。
// lib/claude-client.ts
import Anthropic from '@anthropic-ai/sdk'
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
maxRetries: 3, // 自動リトライ(デフォルト2回)
timeout: 60000, // 60秒タイムアウト
})
export async function callClaude(
messages: Anthropic.MessageParam[],
systemPrompt?: string
): Promise<string> {
try {
const response = await client.messages.create({
model: 'claude-opus-4-5',
max_tokens: 2048,
system: systemPrompt,
messages,
})
const content = response.content[0]
if (content.type !== 'text') {
throw new Error('Unexpected response type')
}
return content.text
} catch (error) {
if (error instanceof Anthropic.APIError) {
// レート制限エラー
if (error.status === 429) {
throw new Error('APIのレート制限に達しました。しばらく後にお試しください。')
}
// 認証エラー
if (error.status === 401) {
throw new Error('APIキーが無効です。')
}
// その他のAPIエラー
throw new Error(`APIエラー: ${error.message}`)
}
throw error
}
}⚠️ 注意
APIキーは絶対にGitHubなどのパブリックリポジトリにコミットしないでください。誤ってコミットした場合は、Anthropicコンソールで即座に無効化し、新しいキーを発行してください。
Vercelへのデプロイ
Next.jsアプリのデプロイ先としてVercelは最適です。GitHubリポジトリと連携すれば、プッシュのたびに自動デプロイされます。環境変数はVercelダッシュボードの「Settings → Environment Variables」から設定します。
- 1Vercelアカウントを作成し、GitHubと連携する
- 2リポジトリをインポートし、プロジェクトを作成する
- 3Settings → Environment Variables で ANTHROPIC_API_KEY を追加する
- 4Deployボタンを押して本番デプロイ完了
Vercel Hobbyプランは個人プロジェクトに十分なスペックを提供しています。APIルートはServerless Functionsとして実行され、コールドスタートも考慮した設計が必要です。
実装で学んだ教訓
2日間でのプロトタイプ開発を通じて、いくつかの重要な知見を得ました。特にストリーミングの実装は初めてだったため、SSEの仕様を理解するのに時間がかかりました。しかし一度理解すれば、他のAI APIでも応用できる汎用的なスキルです。
- システムプロンプトの設計はアプリ品質の7割を決める
- ストリーミングはUXを劇的に改善するが実装コストは高くない
- エラーハンドリングは最初から丁寧に実装する
- APIのレスポンス速度はモデルサイズに大きく依存する(Haikuが最速)
- コストはmax_tokensとmodel選択で大幅に変わる
💡 ヒント
Claudeのモデルは用途に合わせて選択しましょう。claude-haiku-4-5はコスト最優先、claude-sonnet-4-5はバランス型、claude-opus-4-5は最高品質が必要な場合に使います。プロトタイプ段階ではHaikuで動作確認し、本番でSonnetに切り替えるのがおすすめです。