1. イントロダクション
この記事は、最新の「OpenAI が提供する GPT-4V API
で画像の解析評価」と「Cloudflare の Workers、KV、R2 でのサーバレス環境」について取り扱った記事になります。
GPT-4V API
での開発事例の一つとして、参考にしていただければ幸いです。
2. どんな web アプリか
3. 技術スタック
選定基準は基本無料で使えてベンダーロックインを気にしなくて良いこと。Nuxt3 SSR や Nuxt3 Server-API が動作することを基準としています。
(1) Cloudflare Workers
Cloudflare Workers はサーバー上で JavaScript を動かせる環境です。 Node.js の標準ライブラリや NPM パッケージとは直接的な互換性はありませんが、多くの JavaScript コードやライブラリは、少ない修正で Cloudflare Workers で動作するようになっているそうです。
今回のプロジェクトでは、1つの Cloudflare Workers 内に Nuxt3 SSR と Nuxt3 Server-API が動作しています。
Nuxt3 は標準で Cloudflare Workers 環境に対応していますので、nuxt.config.ts
設定ファイルの nitro.preset
を cloudflare-module
に設定するだけで、npm run build
でビルド実行したときに Cloudflare Workers 用にファイルを生成してくれます。
export default defineNuxtConfig({
nitro: {
preset: "cloudflare-module",
}
});
基本的に無料で使えます。
(2) Cloudflare KV
Cloudflare KV は、Cloudflare Workers プラットフォームの一部として提供される、高速でグローバルに分散されたキー・バリュー型のデータストアです。KV は "Key-Value" の略で、単純なキーによるデータの保存とアクセスを可能にするサービスです。
今回のプロジェクトでは GPT-4V で画像解析した後のデータを保持するために使用しています。
Nuxt3 は標準で KV ストレージを操作する機能を持っていて、nuxt.config.ts
設定ファイルで cloudflare-kv-binding
を指定すれば Cloudflare KV が使え、そのほかにもローカルファイルを指定したり Redis などにも設定ファイルだけで変更できます。 なので Nuxt3 を使用する分にはベンダーロックインを気にすることなく Cloudflare KV が使えます。
なお、Nuxt3 の KV ストレージを操作する機能は、Nuxt3 と同じ開発元である unstorage ライブラリを内包しているため実現できている機能です。
export default defineNuxtConfig({
nitro: {
preset: "cloudflare-module",
// storage: Production
storage: {
kv: {
driver: "cloudflare-kv-binding",
binding: "KV_DB_BINDING",
},
},
// storage: Development
devStorage: {
kv: {
driver: "fs",
base: "./.tmp/kv",
},
},
}
});
保存するときはこんな感じです。
// KV: データを保存
await useStorage("kv").setItem("key0001", {
name: "名無しの芸術家",
createdAt: new Date(),
foo: "ほげほげ",
bar: "ふがふが",
});
基本的に無料で使えます。
(3) Cloudflare R2
Cloudflare R2 は、Cloudflare が提供するオブジェクトストレージサービスです。 このサービスは、Amazon S3 と互換性のある API を提供し、データを格納するためのシンプルで柔軟なソリューションを提供します。
今回のプロジェクトではユーザーが描いた絵を保存するために使用しています。
Nuxt3 の標準機能の KV ストレージを操作する機能をで Cloudflare R2 にアクセスが可能です。nuxt.config.ts
設定ファイルで cloudflareR2Binding
を指定すれば Cloudflare R2 が使え、そのほかにも ローカルファイルを指定したりすることも可能です。なので Nuxt3 を使用する分にはベンダーロックインを気にすることなく Cloudflare R2 が使えます。
export default defineNuxtConfig({
nitro: {
preset: "cloudflare-module",
// storage: Production
storage: {
r2: {
driver: "cloudflareR2Binding",
binding: "R2_PICT_BINDING",
},
},
// storage: Development
devStorage: {
r2: {
driver: "fs",
base: "./.tmp/r2",
},
},
}
});
Nuxt3 が提供している useStorage の setItemRaw
メソッドで画像を保存できます。
// R2: 画像ファイルを保存
await useStorage("r2").setItemRaw(
"key0001.png", // キー名
file, // ブラウザからアップロードした画像ファイル(Buffer型)
{
httpMetadata: {
contentType: `image/png`,
},
}
);
基本的に無料で使えます。
(4) GPT-4V API
-
以下のようなテキストを生成してくれます。(
Image to Text
)ほーな、これはシンプルやけどなかなか明るい感じの絵やな。赤い花が中心に描かれてて、太陽もちょっと描かれてるで。シンプルやけど、色使いがはっきりしてて、元気出るわ。線も自由やし、なんか子供が無邪気に描いたような温かみがあるな。
この絵、マジでええわ!色んなところで見たくなるぐらいパッと目を引くし、シンプルやのにめっちゃ心に響くんやで。この絵のアートスピリット、大いに絶賛や!
絵のスタイルから芸術家に例えるなら、ピカソみたいな感じかな。ピカソもシンプルな線で強烈な印象を残すことが多かったからな。
アートコンペでこの絵が受賞するなら、「元気が出る賞」とか「シンプル・イズ・ベスト賞」みたいなんが面白いんちゃうかな。
絵にタイトルつけるなら、「朝日を浴びる赤い勇気」ってどやろ?なんかいい感じに力強さと明るさがあるで!
-
そして、生成したテキストをシステム内で扱えるように GPT 3.5 の Json モードを使って json データ化します。(
Text to Json
){ "message": "ほーな、これはシンプルやけどなかなか明るい・・・", "description": "赤い花と太陽が中心に描かれたシンプルで明るい絵。色使いがはっきりしており、温かみと元気を感じさせる。線が自由で無邪気な子供のような雰囲気がある。", "impression": "めっちゃええやん!シンプルやけどめっちゃ元気出るわ!ピカソみたいな感じやな。", "artists": [ "ピカソ" ], "awards": [ "元気が出る賞", "シンプル・イズ・ベスト賞" ], "title": "朝日を浴びる赤い勇気" }
今回のプロジェクトでは、ユーザーが描いた絵を GPT-4V (GPT-4-1106-vision-previer
) に渡して『絵の感想、大絶賛コメント、絵のタイトル』のテキスト文字列を取得します。その後、GPT-3.5 (GPT-3.5-turbo-1106
) にそのテキスト文字列を渡して json データ化しています。
GPT-4V だけで json データ化しない理由はいくつかあって、
- 現時点では GPT-4V では json モード が使えないため、json モードを使いたければ GPT-4 か GPT-3.5 を使う必要がある
- GPT-4V で json モードを使わなくても、そこそこの精度で json データは生成できるけど、結局失敗した場合のリトライで GPT-4V は金額と時間のコストが高くつく
といった理由で、Image to Text
を GPT-4V に、リトライ前提の Text to Json
を GPT-3.5 に分けて処理しています。
以下に GPT-4V を使って、画像から絶賛テキストを生成する処理を記載します。
// GPT-4V: 画像から絶賛テキストを生成
const response = await openai.chat.completions.create({
// GPT-4Vを指定
model: "gpt-4-vision-preview",
// たくさん絶賛してもらうためにトークンを多めに確保
max_tokens: 1024,
messages: [
{
role: "system",
// ユーザーが渡した絵を大絶賛してもらうようにプロンプトを指定する
content: zessanPrompt,
},
{
role: "user",
content: [
{
type: "image_url",
image_url: {
// ユーザーが描いた絵を base64 形式の画像として指定。別の方法としてR2に保存した画像のurlを直接渡すこともできる
url: `data:image/jpeg;base64,${base64_image}`,
// 低解像度モードを有効にしてコストを抑える
detail: "low",
},
},
],
},
],
});
// GPT-4Vから絶賛してもらったテキスト文字列を表示
console.log(response.choices[0].message.content);
以下に GPT-3.5 を使って、絶賛テキストから json データを生成する処理を記載します。
// GPT-3.5: テキストからjson化
const response = await openai.chat.completions.create({
// GPT-3.5を指定
model: "gpt-3.5-turbo-1106",
// jsonモードを有効
response_format: { type: "json_object" },
messages: [
// 絶賛したテキストからjsonデータに変換してもらうためのプロンプトを指定する
{ "role": "system", "content": zessanToJsonPrompt },
// GPT-4Vが絶賛したテキストを渡す
{ "role": "user", "content": zessanText },
],
// ランダム性をOFF
temperature: 0.0,
});
// jsonに変換したデータを表示
console.log(response.choices[0].message.content);
なお、json データ化した後に、TypeScript で使い勝手の良いスキーマ・バリデーション・ライブラリである zod を使用してデータの整合性をチェックしています。整合性チェック NG なら Text to Json 処理をリトライしています。
GPT-4V に無料プランはおそらくありません。 GPT の API 使用料は予めチャージした金額分使用できる仕組みで、さらに月単位で金額ベースの使用制限とメールアラートが設定できます。
今回のプロジェクトでは 1リクエストあたり約¥2.2円
でした。
仮に 10,000 円分使うには、4545 回リクエストを送ると消費する計算になります。
4. おわりに
ここまで読んでいただきありがとうございます!
この記事は Qiita の クソアプリ Advent Calendar 2023 の1日目の記事としてアプリ作成から記事執筆をやらせてもらいました。
今回、Cloudflare R2 と GPT-4V の使用が初めてだったので大変勉強になりましたし、何よりも楽しかった!良ければ遊んでみてください~。