created’ShelfSheriff’

こちらの記事を参考に、OpenAIのVisonを使用して、画像データの解析結果をJSON形式で返却してもらいました。

実際に作ったもの

https://shelf-sheriff.vercel.app

Github

本棚の内容から、いくつかの診断+オススメのジャンルを提案してくれる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

Leave a Reply

Your email address will not be published. Required fields are marked *