【第3回】フロントエンド編:Next.jsとVercel AI SDKで作るモダンなチャットUI
はじめに
LLMアプリケーション開発連載、第3回へようこそ。前回はPythonとLangChain、FastAPIを使って、独自のドキュメントを知識源とするRAG APIを構築しました。これでLLMアプリの「頭脳」と「知識」は準備万端です。
しかし、どんなに賢いAIでも、ユーザーが快適に使えるインターフェースがなければその価値は半減します。現代のLLMアプリのUXにおいて、ChatGPTのようなリアルタイムのストリーミング応答はもはや必須要件です。
本記事では、以下のモダンな技術スタックを組み合わせ、前回作成したFastAPIのRAG APIと連携する、ストリーミング対応のチャットUIを構築する全手順をハンズオン形式で解説します。
- Next.js (App Router): Reactのフレームワークとしてデファクトスタンダード。サーバーサイドレンダリングやAPIルート機能が強力。
- Vercel AI SDK: LLMとのインタラクションを驚くほど簡単にするためのReactフックとコンポーネントを提供。ストリーミング処理を抽象化してくれる。
それでは、ユーザーが「賢いAI」と直感的に対話できる、魅力的なフロントエンドを構築していきましょう!
1. なぜNext.jsとVercel AI SDKなのか?
1.1 Next.js (App Router) の優位性
- React Server Components (RSC): サーバー側でレンダリングを行うことで、初期表示速度を向上させ、クライアント側のJavaScriptバンドルサイズを削減します。これにより、パフォーマンスとSEOに優れたアプリケーションを構築できます。
- API Routes: Next.jsアプリケーション内でバックエンドAPIを簡単に構築できます。今回は、このAPIルートをFastAPIバックエンドへの「プロキシ」として利用し、フロントエンドとバックエンド間の安全かつ効率的な通信を実現します。
1.2 Vercel AI SDKの魔法
Vercel AI SDKは、LLMアプリケーションのフロントエンド開発における複雑さを劇的に軽減してくれます。
useChat
フック: チャットアプリケーションに必要な状態管理(メッセージリスト、入力値、ローディング状態など)を全て抽象化して提供してくれます。これにより、開発者はUIの構築に集中できます。- ストリーミングの簡素化: LLMからのストリーミング応答を、特別な処理なしにReactコンポーネントに直接流し込むことができます。ChatGPTのようなリアルタイムな応答を簡単に実現できます。
2. 【実践】開発環境のセットアップ
まず、Next.jsプロジェクトを作成し、必要なライブラリをインストールします。
mkdir llm-chat-frontend && cd llm-chat-frontend
# Next.jsプロジェクトの作成
npx create-next-app@latest . --ts --eslint --tailwind --app --src-dir
# Vercel AI SDKのインストール
npm install ai
3. バックエンドと通信するAPIプロキシの作成
セキュリティとCORS(Cross-Origin Resource Sharing)の問題を回避するため、Next.jsのAPI RouteをFastAPIバックエンドへのプロキシとして機能させます。
src/app/api/chat/route.ts
ファイルを作成します。
“`typescript:src/app/api/chat/route.ts
import { StreamingTextResponse } from ‘ai’;
export async function POST(req: Request) {
try {
// 環境変数からFastAPIバックエンドのURLを取得
// 本番環境では、このURLを適切に設定してください
const fastapiBackendUrl = process.env.FASTAPI_BACKEND_URL || ‘http://localhost:8000/ask’;
// フロントエンドからのリクエストボディを取得
const { messages } = await req.json();
const userMessage = messages[messages.length - 1].content; // 最新のユーザーメッセージ
// FastAPIバックエンドにリクエストを転送
const response = await fetch(fastapiBackendUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ question: userMessage }), // FastAPIの期待する形式に変換
});
if (!response.ok) {
// FastAPIからのエラーレスポンスを処理
const errorData = await response.json();
console.error('FastAPI Error:', errorData);
return new Response(JSON.stringify({ error: errorData.detail || 'Unknown error from FastAPI' }), { status: response.status });
}
// FastAPIからのストリーミング応答をVercel AI SDKのStreamingTextResponseでラップして返す
// response.bodyはReadableStreamなので、そのまま渡せる
return new StreamingTextResponse(response.body as ReadableStream);
} catch (error) {
console.error(‘Error in API route:’, error);
return new Response(JSON.stringify({ error: ‘Internal Server Error’ }), { status: 500 });
}
}
プロジェクトルートに`.env.local`ファイルを作成し、FastAPIバックエンドのURLを設定します。
```.env.local
FASTAPI_BACKEND_URL=http://localhost:8000/ask
4. Vercel AI SDKでチャットUIを構築する
src/app/page.tsx
を更新して、チャットインターフェースを作成します。
“`typescript:src/app/page.tsx
‘use client’; // クライアントコンポーネントとしてマーク
import { useChat } from ‘ai/react’;
import { useEffect, useRef } from ‘react’;
export default function Chat() {
// useChatフックは、デフォルトで /api/chat にリクエストを送信します。
// これは、上記で作成したNext.js API Routeに一致します。
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
const messagesEndRef = useRef
// メッセージが更新されたら自動スクロール
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: “smooth” });
}, [messages]);
return (
LLMチャット (Next.js + FastAPI + AI SDK)
{/* メッセージ表示エリア */}
<div className="flex-grow overflow-y-auto p-4 border rounded-lg bg-gray-50 mb-4">
{messages.length > 0 ? (
messages.map(m => (
<div
key={m.id}
className="whitespace-pre-wrap mb-4 p-3 rounded-lg shadow-sm"
style={{
backgroundColor: m.role === 'user' ? '#e0f7fa' : '#f0f0f0',
alignSelf: m.role === 'user' ? 'flex-end' : 'flex-start',
maxWidth: '90%',
marginLeft: m.role === 'user' ? 'auto' : '0',
marginRight: m.role === 'user' ? '0' : 'auto',
}}
>
<span className="font-semibold">{m.role === 'user' ? 'あなた: ' : 'AI: '}</span>
{m.content}
</div>
))
) : (
<div className="text-center text-gray-500">メッセージを入力してAIと会話を始めましょう...</div>
)}
<div ref={messagesEndRef} /> {/* 自動スクロールのターゲット */}
</div>
{/* 入力フォーム */}
<form onSubmit={handleSubmit} className="w-full">
<textarea
className="w-full p-3 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500 resize-none"
value={input}
placeholder={isLoading ? 'AIが応答中...' : 'メッセージを入力...'}
onChange={handleInputChange}
disabled={isLoading}
rows={3} // 初期表示の行数
/>
<button
type="submit"
className="mt-3 w-full bg-blue-600 text-white p-3 rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:bg-blue-300"
disabled={isLoading}
>
{isLoading ? '送信中...' : '送信'}
</button>
</form>
</div>
);
}
“`
5. 動作確認
-
FastAPIバックエンドの起動: 前回作成した
llm-rag-api
ディレクトリでFastAPIサーバーを起動します。bash
cd ../llm-rag-api # プロジェクトルートから移動
source venv/bin/activate
uvicorn main:app --reload --port 8000 -
Next.jsフロントエンドの起動:
llm-chat-frontend
ディレクトリでNext.js開発サーバーを起動します。bash
cd ../llm-chat-frontend # プロジェクトルートから移動
npm run dev
ブラウザで http://localhost:3000
にアクセスし、チャットインターフェースを試してみてください。メッセージを送信すると、FastAPIバックエンドからのストリーミング応答がリアルタイムで表示されるはずです。
まとめ
今回は、Next.jsとVercel AI SDKを組み合わせることで、複雑なストリーミング処理や状態管理を意識することなく、リッチなチャットUIを驚くほど簡単に構築できました。
バックエンド(Python/FastAPI)とフロントエンド(TypeScript/Next.js)を分離し、APIで連携させるモダンなアーキテクチャの利点を体験いただけたかと思います。
次回、【第4回】Function Calling実践編では、このアプリケーションをさらに進化させます。LLMに外部APIを呼び出す能力を与える「Function Calling」を実装し、単なるQ&Aボットから「AIエージェント」へと昇格させます。お楽しみに!
コメント