見出し画像

Next.jsのRoute HandlersでOpenAI APIのストリーミングを試す

「Next.js」の「Route Handlers」でOpenAI APIのストリーミングを試したので、まとめました。

・next@14.2.4

1. Next.js の Route Handlers

「Next.js」の「Route Handler」は、APIエンドポイントを定義してリクエストを処理するための機能です。Next.js v13.2からの新機能で、バックエンドのロジックをサーバーレスな方法で実装することができます。

・Pages Router(v12以前)
Next.js v12以前のルーティング機能。
「/pages」直下に配置したファイル単位でページを生成。

・API Routes(v12以前)
Next.js v12以前のAPIエンドポイント機能。
「/pages/api」直下に配置したファイル単位でAPのエンドポイントを生成。

・App Router(v13以降)
従来のPages Routerに代わる新ルーティング機能。
「/app」直下のpage.jsxでページを生成。

・Route Handlers (v13.2以降)
従来のAPI Routesに代わる新APIエンドポイント機能。
「/app/api」直下のフォルダでAPIエンドポイントを生成。

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>
  );
}

出力は、ストリーミングで表示されます。



この記事が気に入ったらサポートをしてみませんか?