GPT-4o モデルに curl コマンドで画像をプロンプトとして送信する方法

結論、画像を [Data URI スキーム](https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)にして `url` パラメータとして送信します。

```bash
OPENAI_API_KEY=sk-proj-xxxxxxx

FILENAME=001.jpg
DATA_URI='data:image/jpeg;base64,'$(base64 -w0 ${FILENAME})

SYSTEM_PROMPT='このシステムは画像の内容を分析して、その説明を生成します。分析結果を日本語で回答します。'
USER_PROMPT='画像の中には何が映っていますか？'

RESULT=$(curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" -d @- << _EOJ_
{
  "model": "gpt-4o",
  "messages": [
    {
      "role": "system",
      "content": "${SYSTEM_PROMPT}"
    },
    {
      "role": "user",
      "content": [
        { "type": "text", "text": "${USER_PROMPT}" },
        { "type": "image_url", "image_url": { "url": "${DATA_URI}" } }
      ]
    }
  ],
  "max_tokens": 300
}
_EOJ_
)
echo "$RESULT"
```

実行例 (echo には追加で jq を利用しています)

```bash
❯ OPENAI_API_KEY=sk-proj-xxxxxxx
❯ FILENAME=001.jpg
❯ DATA_URI='data:image/jpeg;base64,'$(base64 -w0 ${FILENAME})
❯ SYSTEM_PROMPT='このシステムは画像の内容を分析して、その説明を生成します。分析結果を日本語で回答します。'
❯ USER_PROMPT='画像の中には何が映っていますか？'
❯ RESULT=$(curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" -d @- << _EOJ_
{
  "model": "gpt-4o",
  "messages": [
    {
      "role": "system",
      "content": "${SYSTEM_PROMPT}"
    },
    {
      "role": "user",
      "content": [
        { "type": "text", "text": "${USER_PROMPT}" },
        { "type": "image_url", "image_url": { "url": "${DATA_URI}" } }
      ]
    }
  ],
  "max_tokens": 300
}
_EOJ_
)
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  135k  100  1071  100  134k    111  14246  0:00:09  0:00:09 --:--:--   235
❯ echo $RESULT | jq .
{
  "id": "chatcmpl-9PjP4Gt4yeusAqrsqvAn5NIzem90x",
  "object": "chat.completion",
  "created": 1715919758,
  "model": "gpt-4o-2024-05-13",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "画像には、東京羽田空港の夜景が映っています。滑走路や駐機場に飛行機が駐機している様子が確認できます。画像上部には「LIVE CAM 東京 羽田空港」と表示されています。\n\nまた、画像の左側には温湿度計が置かれており、温度が24.6℃、 湿度が52％と表示されています。右下には日時が表示されており、「2024-05-16 22:30:00」となっています。"
      },
      "logprobs": null,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 1151,
    "completion_tokens": 117,
    "total_tokens": 1268
  },
  "system_fingerprint": "fp_927397958d"
}
```

## 解説

OpenAI の GPT-4o は、[マルチモーダル](https://www.aist.go.jp/aist_j/magazine/20231129.html)モデルであり、画像をプロンプトとして渡すことができます。具体的には `url` というパラメータに、対象となる画像をセットします。[Quick Start の curl サンプルコード](https://platform.openai.com/docs/guides/vision/quick-start?lang=curl) では Wikimedia Commons の URL が使われています。

ローカルの画像は [Uploading base 64 encoded images](https://platform.openai.com/docs/guides/vision/uploading-base-64-encoded-images)にもあるように、Data URI として送信できるのですが、サンプルコードが Python のみです。

そこで curl で送る方法を残しておいたというのが、この記事の趣旨になります。

### Bash 上での 画像の Data URI 化

`base64` 等、Base64 エンコーディングができるコマンドがあれば、Data URI 化できます。以下は `001.jpg` ファイルを `base64` コマンドを使って Data URI 化しています。

```bash
FILENAME=001.jpg
DATA_URI='data:image/jpeg;base64,'$(base64 -w0 ${FILENAME})
```

* PNG なら `data:image/png;base64,` です
* base64 コマンドの注意点としては、デフォルトは 76 バイトで改行されます。 `-w 0` として改行が起こらないように指定することです(もしかすると `-b 0 ` の場合もあります)

## 金額

API は実行毎に課金が発生します。1920x1080の画像を gpt-4oで扱うと、一回当たり0.005525 USD(約0.9円)です。実行例のように、実行結果を変数に格納しておくことをおススメします。

解像度に対する金額は [Pricing のページ](https://openai.com/api/pricing/)で確認できます。ちなみに gpt-4-turbo より、**gpt-4oの方が安価です。**

## あとがき

~~シェル芸の記事になってしまった~~ → シェル芸の部分を分離。参考記事をご覧くださいな。

### 参考記事

* [Bashで「Argument list too long」が発生した時の対策](https://qiita.com/ma2shita/items/00e73e435e539d08c8b7)
* [Bashのコマンド置換＋ヒアドキュメントで「warning: command substitution: 1 unterminated here-document」が発生した時の対策](https://qiita.com/ma2shita/items/4bea737538392f0478a8)


_EoT_