【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などのファイル参照などに使っていきたいと思います。