こちらの記事を参考に、OpenAIのVisonを使用して、画像データの解析結果をJSON形式で返却してもらいました。
実際に作ったもの
https://shelf-sheriff.vercel.app
本棚の内容から、いくつかの診断+オススメのジャンルを提案してくれるWebアプリです。
アドベンチャーゲーム風に進行します。
概要(API周りに着目)
クライアント側 | React | 画像データを読み取り&Base64エンコード |
サーバー側 | Python | データURLの形式でOpenAIのAPIにBase64画像データを渡す |
実装時に気をつけた点
- Base64エンコード
公式マニュアルにも書かれていますが、ローカルデータをVisonのモデルに渡すにはBase64でエンコードを行います。インターネットに転がっている画像を参照したい場合以外は、この方法になるということです。 - MAXトークン数
無尽蔵にトークンを使用されると、金銭面で嬉しくないのでトークン数を絞ります。
しかし、JSONの特性上、途中でデータが切れてしまうとデコードできないので、プロンプトで”○文字以内”と調整すると良いです。 - 画像形式
.png
,.jpeg
,.jpg
,.webp
,.gif
がサポート対象です。(公式) - 画像サイズ
1画像あたり20MBまでの制限があります。(公式)
あとは、実際にAPIを叩くと親切なエラー文が返ってくるので、適宜対応できるかと思います。
クライアント側のコード一部
//ファイルアップロード処理
const handleFileUpload = async (event) => {
const file = event.target.files[0];
if (file) {
setIsUploading(true);
try {
const base64 = await toBase64(file);
const apiEndpoint = process.env.REACT_APP_API_ENDPOINT;
const response = await axios.post(apiEndpoint, { image: base64 });
onComplete(response.data);
} catch (error) {
console.error("File upload failed:", error);
} finally {
setIsUploading(false);
}
}
};
// エンコード処理:画像ファイル->Base64データ
const toBase64 = (file) => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result.split(',')[1]);
reader.onerror = error => reject(error);
});
サーバー側のコード全文
from decimal import Decimal
import json, os, openai
def decimal_to_int(obj):
if isinstance(obj, Decimal):
return int(obj)
def lambda_handler(event, context):
openai.api_key = os.environ['API_Key']
base64_string = event["image"]
print(base64_string)
response = openai.ChatCompletion.create(
model="gpt-4-vision-preview",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "あなたはユーモアのあるプロファイリングを行うプロです。口調は口数の少ないミステリアスな探偵。この本棚の持ち主に対して以下内容を推測してください。-character:性格(ユーモアのある表現と根拠)(hint...少ない本こそ関心が深い可能性もあります) - interesting:興味のある分野と根拠 -recommend:これから興味を持つことをお勧めする意外な分野と根拠。character,interesting,recommendという3つのkeyに対してそれぞれ70字以上100字以下のvalueを紐づけたJSONデータを返却してください。jsonファイル出力結果はstart,endで終わるようにしてください。"},
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_string}",}},
],
}
],
max_tokens=500,
)
response_content = response["choices"][0]["message"]["content"]
response_content = response_content[5:-3] #文頭にあるstart,文末にあるendの文字を削除
response_content = json.loads(response_content)
print("Received response:" + json.dumps(response, default=decimal_to_int, ensure_ascii=False))
return response_content