【Next.js】Open AIのAPIでファイルをアップロードする方法。

今回は、Next.jsサイトからOpen AIのAPI(Upload file)で、ファイルをアップロードする方法を実装するのに時間がかかったのでその過程と最終コードをまとめます。(Vercelにデプロイする場合)
「Assistants APIでファイルを参照して回答生成する機能(Knowledge Retrieval)」を使うために調べて実装しました。
- 今回の目標: 「ブラウザで指定したファイルを、Open AIのFilesにアップロードしたい」
目次
最終的なコード
最終的に、「外部ストレージ(Firebase Storage)」と「Vercelの一時的なストレージ(/tmp)」などを使って実装しました。
- クライアントサイドで、入力ファイルを「Firebase Storage」に保存する
- クライアントサイドから、「API Route」を使用する(ファイルの保存先URLを渡す)
- API Routeで、保存先URLからファイルをダウンロード、Vercel上に一時保存する
- API Routeで、Vercel上のファイルをOpenAIのAPIにアップロードする
コード1: ファイルをアップロードするコンポーネント
"use client";
import React from "react";
import { storage } from "@/lib/firebase/firebaseConfig";
import { deleteObject, getDownloadURL, ref, uploadBytes } from "firebase/storage";
export default function FileUpload() {
    const [file, setFile] = React.useState<File|null>(null);
    // File Change ====================================================
    const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const files = e.target.files;
        if (files) {setFile(files[0]);}
    };
    // File Upload ====================================================
    const handleFileUpload = async () => {
        if (!file) return;
        const storageRef = ref(storage, 'uploads/' + file.name);  // Firebase Storageの参照パスを作成
        const snapshot = await uploadBytes(storageRef, file);     // Firebase Storageにファイルをアップロード
        const downloadUrl = await getDownloadURL(snapshot.ref);   // Firebase StorageからダウンロードURLを取得
        await fetch('/api/open-ai/files/create', {                // OpenAIにファイルをアップロード
            method: 'POST',
            body: JSON.stringify({ downloadUrl, file.name })
        });
        await deleteObject(storageRef);                           // Firebase Storageからファイルを削除
    };
    return (
        <div>
            <input type="file" onChange={handleFileChange}/>
            <button onClick={handleFileUpload}>UPLOAD FILE</button>
        </div>
    );
}コード2: API Route
import OpenAI from "openai";
import { NextRequest } from 'next/server';
import axios from 'axios';
import fs from 'fs';
import path from 'path';
export async function POST(request: NextRequest) {
    const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
    const { fileUrl, fileName } = await request.json(); // Body取得: 『messages』『response_format』
    // ファイルを一時的に保存するパス
    const tempFilePath = path.join('/tmp', fileName);
    try {
        // ファイルのダウンロード
        const responseFile = await axios({
            method: 'GET',
            url: fileUrl,
            responseType: 'stream'
        });
        // ファイルの保存
        responseFile.data.pipe(fs.createWriteStream(tempFilePath));
        // ファイルの保存が完了するまで待機
        await new Promise((resolve, reject) => {
            responseFile.data.on('end', resolve);
            responseFile.data.on('error', reject);
        });
        // ファイルをOpenAIにアップロード
        const response = await openai.files.create({
            purpose: 'assistants',
            file: fs.createReadStream(tempFilePath)
        });
        fs.unlinkSync(tempFilePath);  // ファイルを削除(一時保存フォルダから削除)
        return Response.json(response);
    } catch (error) {
        fs.unlinkSync(tempFilePath);  // ファイルを削除(一時保存フォルダから削除)
        return Response.json({ error: 'File upload failed' });
    }
}実装するまでの過程
実装1: クライアントサイド→Open AI

クライアントサイドからOpen AIのAPIで、そのままアップロードする実装をしました。
この実装の問題点として、クライアントサイドでOpen AIのAPIを使用すると、「APIキーが閲覧できてしまう問題」がありました。
対策として、「API Routeによりサーバーサイドでファイルをアップロードする」処理にしました。
実装2: クライアントサイド→API Route→Open AI

クライアントサイドからAPI Routeにファイルを渡し、API RouteからOpen AIにアップロードする実装にしました。
この実装の問題点として、クライアントサイド→API Routeへのファイルの受け渡しサイズ制限により「大きいサイズのファイルが対応できない問題」がありました。(PDFファイルなどはできない)
対策として、「一度外部ストレージ(Firebase Storage)に保存して、そのファイルをAPI Routeから読み取る」という形にしました。
実装3: クライアントサイド→Firebase Storage→API Route→Open AI
一度外部ストレージ(Firebase Storage)に保存して、そのファイルをAPI Routeから読み取る実装にしました。

この実装の課題点として、「”ファイルの保存先URL(Firebase Storageのパス)”から、”Open AIのAPI(Upload file)でアップロード可能な形式”に変換する処理が必要」でした。
対策として、「Firebase Storageの保存先URLからファイルをダウンロードし、それをVercelの一時的なストレージとして利用できる/tmpディレクトリに保存、そのファイルを読み取りOpen AIにアップロードする」という形にしました。
実装4: クライアントサイド→Firebase Storage→API Route→Vercel→Open AI

最終的になかなか複雑になってしまいましたが、動くようになりました。
おまけ
Firebase Storageの初期化
import { initializeApp, getApps } from "firebase/app";
import { getStorage } from "firebase/storage";
const firebaseConfig = {
    // TODO:認証情報を設置
    apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
    authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
    projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
    storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
    measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
};
if (!getApps()?.length) {
    initializeApp(firebaseConfig); // Firebaseアプリの初期化
}
export const storage = getStorage();Firebase StorageのCORS設定
[
  {
    "origin": ["https://example.vercel.app"],
    "responseHeader": ["Content-Type"],
    "method": ["GET", "POST", "PUT", "DELETE", "HEAD"],
    "maxAgeSeconds": 3600
  }
]$ gcloud auth login
$ gsutil cors set [CORS設定ファイルのパス] gs://[あなたのFirebase Storageバケット名]
$ gsutil cors get gs://the-ailab.appspot.comまとめ
Open AIのその他のAPIは、「API Route」や「Server Action」を使うことで比較的楽に実装できたのですが、今回のファイルのアップロードだけ少し複雑になってしまいました。
一応動く状態になったので、Assistants APIなどのファイル参照などに使っていきたいと思います。
最近の記事







