GPT-4V(GPT-4 Turbo with Vision)をPythonから使う

## はじめに

Azure OpenAI ServiceでもGPT-4Vが使えるようになったとのことで早速試してみました。なおセットアップは以下の記事が参考になりました。

https://qiita.com/kccs_haruki-nukumi/items/16aca479532a53c11034#2-%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9%E4%BD%9C%E6%88%90%E3%83%A2%E3%83%87%E3%83%AB%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4--azure-openai-service

## 早速アクセスしてみる

まずは[Microsoftのチュートリアル](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/reference#example-request-2)を見てみます。

```bash
curl \
https://YOUR_RESOURCE_NAME.openai.azure.com/openai/deployments/YOUR_DEPLOYMENT_NAME/extensions/chat/completions?api-version=2023-12-01-preview \
  -H "Content-Type: application/json" \
  -H "api-key: YOUR_API_KEY" \
  -d '{"enhancements":{"ocr":{"enabled":true},"grounding":{"enabled":true}},"dataSources":[{"type":"AzureComputerVision","parameters":{"endpoint":" <Computer Vision Resource Endpoint> ","key":"<Computer Vision Resource Key>"}}],"messages":[{"role":"system","content":"You are a helpful assistant."},{"role":"user","content":[{"type":"text","text":"Describe this picture:"},{"type":"image_url","image_url":"https://learn.microsoft.com/azure/ai-services/computer-vision/media/quickstarts/presentation.png"}]}]}'
  ```

なお、このcURLの例は、`image_url`を指定する部分が間違っています。正しくは以下です。

```json
"messages" : [
    {
        "role": "system",
        "content": "You are a helpful assistant."
    }, {
        "role": "user",
        "content": [
            {"type": "text", "text": "Describe this picture:"},
            {"type": "image_url", "image_url": {
                "url": "https://learn.microsoft.com/azure/ai-services/computer-vision/media/quickstarts/presentation.png"
                }
            }
        ]
    }
]
```


これをGPT-4に「Pythonスクリプトになおして」と依頼するとスクリプトができます。

```
以下のcURLコマンドをPythonスクリプトに直してください。
APIキーやDEPLOY_NAMEは環境変数でセットできるようdotenv()を使用してください。
システムプロンプトとユーザープロンプト（"messages"の各値）を辞書型で受け取り、
結果を返すロジックを関数にまとめてください。

curl \
https://YOUR_RESOURCE_NAME.openai.azure.com/openai/deployments/YOUR_DEPLOYMENT_NAME/extensions/chat/completions?api-version=2023-12-01-preview \
  -H "Content-Type: application/json" \
  -H "api-key: YOUR_API_KEY" \
  -d '{"enhancements":{"ocr":{"enabled":true},"grounding":{"enabled":true}},"dataSources":[{"type":"AzureComputerVision","parameters":{"endpoint":" <Computer Vision Resource Endpoint> ","key":"<Computer Vision Resource Key>"}}],"messages":[{"role":"system","content":"You are a helpful assistant."},{"role":"user","content":[{"type":"text","text":"Describe this picture:"},{"type":"image_url","image_url":"https://learn.microsoft.com/azure/ai-services/computer-vision/media/quickstarts/presentation.png"}]}]}'

```

変換後のスクリプトが以下になります。変換後にリファクタリングしてプロンプト部分を値オブジェクトにしていますが、なんとなく分かると思います。

```python
from typing import Literal, Union, Tuple
from pydantic import BaseModel
import requests
import json
from dotenv import load_dotenv
import os
import requests
import json

# Clear the environment variables
os.environ.clear()

# Load the environment variables
load_dotenv()

# Replace these with your actual resource name, deployment name, and API key
COMPUTER_VISION_RESOURCE_ENDPOINT = os.getenv("COMPUTER_VISION_RESOURCE_ENDPOINT")
COMPUTER_VISION_RESOURCE_KEY = os.getenv("COMPUTER_VISION_RESOURCE_KEY")
OPENAI_API_BASE = os.getenv("OPENAI_API_BASE")
OPENAI_DEPLOY_NAME = os.getenv("OPENAI_DEPLOY_NAME")
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")

# ImageUrlクラスを定義
class ImageUrl(BaseModel):
    url: str

# 基底クラスとしてMessageContentを定義
class MessageContent(BaseModel):
    type: Literal['text', 'image_url']

# TextContentサブクラス
class TextContent(MessageContent):
    type: Literal['text']
    text: str

# ImageUrlContentサブクラス
class ImageUrlContent(MessageContent):
    type: Literal['image_url']
    image_url: ImageUrl

# MessageクラスのcontentフィールドをMessageContentのリストとして定義
class Message(BaseModel):
    role: Literal['system', 'user', 'assistant']
    content: Union[str, Tuple[TextContent, ImageUrlContent]]

# プロンプトを受け取り結果を辞書型で返す
def send_chat_request(system_prompt: Message, user_content: Message) -> dict:

    # Construct the request payload
    payload = {
        "enhancements": {
            "ocr": {
                "enabled": True
            },
            "grounding": {
                "enabled": True
            }
        },
        "dataSources": [
            {
                "type": "AzureComputerVision",
                "parameters": {
                    "endpoint": COMPUTER_VISION_RESOURCE_ENDPOINT,
                    "key": COMPUTER_VISION_RESOURCE_KEY
                }
            }
        ],
        "messages": [
            system_prompt.model_dump(),
            user_content.model_dump(),
        ],
        "max_tokens": 1024,
        "stream": False,
    }

    # Send the request to the API
    response = requests.post(
        f"{OPENAI_API_BASE}/openai/deployments/{OPENAI_DEPLOY_NAME}/extensions/chat/completions?api-version=2023-12-01-preview",
        headers={
            "Content-Type": "application/json",
            "api-key": OPENAI_API_KEY
        },
        data=json.dumps(payload)
    )

    return response.json()

if __name__ == "__main__":
    system_prompt = Message(role="system", content="You are a helpful assistant.")
    user_content = Message(role="user", content=[
        TextContent(
            type='text',
            text="Describe this picture:"
        ),
        ImageUrlContent(
            type='image_url',
            image_url=ImageUrl(
                url="https://learn.microsoft.com/azure/ai-services/computer-vision/media/quickstarts/presentation.png"
        )),
    ])

    response = send_chat_request(system_prompt, user_content)
    print(json.dumps(response, indent=4))

    # Check the response
    if response.get("status_code") == 200:
        print("Success:", json.dumps(response, indent=4))
    else:
        print("Error:", response.get("status_code"), response.get("text"))
```

リクエストのJSONがかなり複雑になっているのが分かります。いままでのリクエストJSONと互換性を保つための苦肉の作だとは思われます。

`if __name__ == "__main__":`以下でプロンプトや画像の編集ができます（画像はチュートリアルのままですが、ブラウザから見られる状態であればどこでも良いようです）。

`enhancements`の`ocr`を`True`にすると、本文中の文字列をOCRします。また、`grounding`を`True`にすると画像を物体検出し、物体名と物体位置をAIの回答に使用します（具体的にどのように使用しているかはよくわかっていません）。

テストスクリプトの質問が英語になっていますが、これを日本語にすると`grounding`が出来ないようです。Azure AI Visionが日本語に対応していないことと関係あるかもしれません。どこが日本語に対応しているか微妙なので、試験は日本語と英語の両方でやってみる必要があるかもしれません。

## requiment.txt
すぐ動かせるように、`requiment.txt`の中身を書いておきます。

```
load_dotenv
pydantic
requests
```

とはいっても３行です。