Next.jsのRoute HandlersでOpenAI APIのストリーミングを試す
「Next.js」の「Route Handlers」でOpenAI APIのストリーミングを試したので、まとめました。
1. Next.js の Route Handlers
「Next.js」の「Route Handler」は、APIエンドポイントを定義してリクエストを処理するための機能です。Next.js v13.2からの新機能で、バックエンドのロジックをサーバーレスな方法で実装することができます。
2. プロジェクトの準備
プロジェクトの準備の手順は、次のとおりです。
(1) Next.jsのプロジェクトの作成。
npx create-next-app@latest my-next-app
cd my-next-app
(2) 依存パッケージのインストール。
npm install ai openai
(3) プロジェクトフォルダ(my-next-app)に「.env.local」を追加。
<OpenAI_APIキー>に自分の「OpenAI APIキー」を指定してください。
・.env.local
OPENAI_API_KEY=<OpenAI_APIキー>
(4) 動作確認。
以下のコマンドを実行後、ブラウザで「http://localhost:3000/」を開きます。
npm run dev
3. ストリーミングの実装
ストリーミングの実装手順は、次のとおりです。
(1) APIエンドポイントの追加。
公式ページのコードとほぼ同じになります。
・app/api/chat/route.ts
import OpenAI from "openai"
import { OpenAIStream, StreamingTextResponse } from "ai"
// OpenAI
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
})
// Edge環境
export const runtime = "edge"
// POST
export async function POST(req: Request) {
const { messages } = await req.json()
const response = await openai.chat.completions.create({
model: "gpt-4o",
stream: true,
messages,
})
const stream = OpenAIStream(response)
return new StreamingTextResponse(stream)
}
OpenAIStreamについては、以下を参照。
(2) ページの編集。
・app/page.tsx
"use client";
import React, { useState, FormEvent } from "react";
// メッセージ
interface Message {
role: string;
content: string;
}
// ホーム
export default function Home() {
const [input, setInput] = useState("");
const [output, setOutput] = useState("");
// Streamのフェッチ
async function fetchStream(messages: Message[]) {
try {
// API呼び出し
const response = await fetch("/api/chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ messages }),
});
if (!response.body) throw new Error("No response body");
// Readerの準備
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
// ストリーミング
let done = false;
let chunk = "";
let buffer = ""
while (!done) {
// 読み込み
const { value, done: doneReading } = await reader.read();
done = doneReading;
// チャンクに分割
chunk += decoder.decode(value, { stream: true });
let boundary = chunk.indexOf("\n");
while (boundary !== -1) {
buffer += chunk.slice(3, boundary-1);
chunk = chunk.slice(boundary + 1);
setOutput(buffer);
boundary = chunk.indexOf("\n");
}
}
} catch (error) {
console.log("Error:", error);
}
}
// 送信ボタン押下時に呼ばれる
const handleSubmit = async (event: FormEvent) => {
event.preventDefault();
try {
fetchStream([{ role: "user", content: input }]);
} catch (error) {
console.error("Error:", error);
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
/>
<button type="submit">Send</button>
</form>
<div>{output}</div>
</div>
);
}
出力は、ストリーミングで表示されます。
この記事が気に入ったらサポートをしてみませんか?