Azure OpenAIの「GPT-4o」モデルとPower Appsで領収書読み取りアプリを作ってみる

# はじめに
[Microsoft Build 2024](https://build.microsoft.com/?culture=ja-jp&country=jp)、期待通り熱気が凄い！

[Power Automate](https://powerautomate.microsoft.com/en-us/blog/revolutionize-the-way-you-work-with-automation-and-ai/
)と[Power Apps](https://powerapps.microsoft.com/en-us/blog/power-apps-is-making-it-easier-for-developers-to-build-with-microsoft-copilot-and-each-other/
)のアップデートもめちゃくちゃ熱いですが、私はやはり
**[Azure OpenAIで「GPT-4o」のGA！](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/concepts/models#gpt-4o-and-gpt-4-turbo)** が胸熱🔥

https://powerapps.microsoft.com/en-us/blog/power-apps-is-making-it-easier-for-developers-to-build-with-microsoft-copilot-and-each-other/

https://powerautomate.microsoft.com/en-us/blog/revolutionize-the-way-you-work-with-automation-and-ai/

https://learn.microsoft.com/ja-jp/azure/ai-services/openai/concepts/models#gpt-4o-and-gpt-4-turbo

`GPT-4oは凄い`と言い続ける驚き屋になっていますが、`領収書読み取りアプリ`をテーマに、`GPT-4o`の読み取り性能を見ていきましょう。

**■ 驚き屋 実績**

https://qiita.com/DEmodoriGatsuO/items/aa67084625a4468814dd

https://qiita.com/DEmodoriGatsuO/items/4c4734b78973970e0d3d

:::note info
[Power Apps & GPT-4oを使って超高速で画像解析アプリを作る！](https://qiita.com/DEmodoriGatsuO/items/4c4734b78973970e0d3d) は、かつてないほど読んでいただきました！
大感謝です🐟✨
:::

:::note warn
記事は`2024.05.23`時点のものです
恐ろしいほど、技術進歩が速いため、すぐに陳腐化する恐れがあります
:::

# Azure OpenAI 「GPT-4o」GA!
[Microsoft Build 2024](https://build.microsoft.com/?culture=ja-jp&country=jp)の目玉発表で、今話題の「GPT-4o」が[Azure OpenAIでも使用可能](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/concepts/models#gpt-4o-and-gpt-4-turbo)であることが、発表されました。

`GPT-4o`は、`OpenAI`の最新のモデルであり、**文字列と画像に対応する凄まじいモデルです。**

GPT3の時から驚きっぱなしですが、間違いなくこのモデルは、それ以上の凄まじい性能を持ち合わせています。

**■ 主な比較ポイント**

| 項目| GPT-4o | GPT-4 | GPT-3.5 |
|---|---|---|---|
| 音声モードの応答速度 | **最速232ミリ秒、平均320ミリ秒**| 平均5.4秒| 平均2.8秒|
| 音声モードの仕組み | **直接処理可能** | 音声をテキストに変換後、テキストを処理 | 音声をテキストに変換後、テキストを処理 |
| テキスト性能（英語とコード） | **GPT-4 Turboと同等** | 高い | すごかった |
| テキスト性能（非英語） | **凄い** | 高い | すごかった |
| API 速度 | **凄い** | 標準 | 標準 |
| API コスト | **GPT-4より50%安い**| 標準 | 安い |

**とにかく凄いんです！まさに革命！**
これが`Azure OpenAI`でも使えるのであれば、`デプロイ`まったなし！

なお対応している`リージョン`は限定されています。

| リージョン | 日本語 |
|---|---|
| eastus | 米国東部 |
| eastus2 | 米国東部2 |
| northcentralus | 米国中北部 |
| southcentralus | 米国南中部 |
| westus | 米国西部 |
| westus3 | 米国西部3 |

https://learn.microsoft.com/ja-jp/azure/ai-services/openai/concepts/models#gpt-4o-and-gpt-4-turbo

とりあえず`デプロイ`しましょう！
そして、`Azure Key Vault`に、シークレットをイン！

# ソリューションでAzure Key Vault シークレットの環境変数を使用
Power Apps / Power Automateで、`エンドポイント`や`APIキー`の管理は、気になるところです。
なんとなく、`セキュアな入出力`をONにしても、「これで果たして大丈夫なのか・・・」と不安を感じます。

Power Apps / Power Automateで出来ること自体、管理が難しいことは否めません。
そこで活躍する[Azureのサービスが、Azure Key Vault](https://learn.microsoft.com/ja-jp/power-apps/maker/data-platform/environmentvariables-azure-key-vault-secrets)です。

`Power Platform`の環境に、`Azure Key Vault`を設定することで、ソリューションの環境変数に、機密情報を格納できます。

手順は[Microsoft learn](https://learn.microsoft.com/ja-jp/power-apps/maker/data-platform/environmentvariables-azure-key-vault-secrets)を参考にしてください。

https://learn.microsoft.com/ja-jp/power-apps/maker/data-platform/environmentvariables-azure-key-vault-secrets

**Azure ロールベースのアクセス制御** の場合、`Dataverse`に`キーコンテナ`の参照権限があり、
**Azure Key Vault** に、シークレットを管理できる権限を自分に付与できればクリア！

ソリューションに、シークレットを環境変数として設定できます。
シークレットとして設定した環境変数は[Microsoft Dataverse](https://learn.microsoft.com/ja-jp/connectors/commondataserviceforapps/#%E3%83%90%E3%82%A4%E3%83%B3%E3%83%89%E3%81%97%E3%81%A6%E3%81%84%E3%81%AA%E3%81%84%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B)コネクタで、`バインドしていないアクションを実行する`アクションから、値を参照することができます。

https://learn.microsoft.com/ja-jp/connectors/commondataserviceforapps/#%E3%83%90%E3%82%A4%E3%83%B3%E3%83%89%E3%81%97%E3%81%A6%E3%81%84%E3%81%AA%E3%81%84%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B

アクション名は、`RetrieveEnvironmentVariableSecretValue`。
`EnvironmentVariableName`に、環境変数の**名前** (表示名ではない)を設定することで、値を呼び出します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/10373b2f-a800-83d1-ab96-71347b4ab84f.png)

`セキュリティで保護された入力`と`セキュリティで保護された入力`、それぞれ設定することで、情報の安全性も向上します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/9aad8cd0-3524-61d6-c958-3c3214a039b4.png)

上記のアクションを採用して、`Azure OpenAI`の`エンドポイント`、`API キー`、`デプロイ名`を環境から呼び出し、リソースに`HTTP`要求を送信して、値を受け取るフローは、下記のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/5e45872c-fbae-6081-9090-fc0b4d158449.png)

:::note warn
 - プレミアムコネクタ使用可能であるライセンスが前提。
 - `応答`で値をPower Appsに渡します
     - 理由は、配列をPower Appsに戻すため
:::

# Power Apps
サクっとつくっちゃいましょう。

ツリー図は下記の通り

```plaintext:Power Apps コントロールのツリー図
Main
├─ grpComplete
│  ├─ btnComplete
│  ├─ txtComplete
│  └─ backgroundComplete
├─ grpProgress
│  ├─ txtProgress
│  ├─ imgProgress
│  ├─ Progress
│  └─ backgroundProgress
├─ ScreenContainer
│  └─ HeaderContainer
│     ├─ imgTop
│     ├─ lblTitle
│     └─ Avatar
└─ BottomContainer
│  └─ SidebarContainer
│     └─ conMenu
│        ├─ icoMenu
│        ├─ lblMode
│        ├─ conBadge
│        │  └─ Badge
│        ├─ InfoContainer
│        │  ├─ InfoButton
│        │  └─ lblInfo
│        └─ conPicture
│           ├─ ImageContainer
│           │  ├─ AddPicture
│           │  └─ Image
│           └─ ButtonPicture // ここに関数
└─ MainContainer
   ├─ conLogo // 読み取りセクションのヘッダー
   │  ├─ LogoImage
   │  ├─ lblDetail
   │  └─ barHeaderMain
   ├─ conDate // 領収書の購入日付
   │  ├─ BadgeDate
   │  └─ DatePickerCanvas
   ├─ conStore // 領収書の購入先
   │  ├─ BadgeStore
   │  └─ TextStore
   ├─ conAmount // 領収書の合計
   │  ├─ BadgeAmount
   │  └─ NumberAmount // モダン コントロールの数値入力
   └─ Table // モダン コントロールのテーブル
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/71f116c2-5b79-da78-969d-b578afb25e73.png)

今までどおりですね。
関数を設定するところは`ButtonPicture`。

```plaintext:OnSelect
Set(Response,'AzureOpenAI-GPT4oReceiptRead'.Run(Substitute(JSON(Image.Image,JSONFormat.IncludeBinaryData),"""","")));
```

詳細は[Power Apps & GPT-4oを使って超高速で画像解析アプリを作る！](https://qiita.com/DEmodoriGatsuO/items/4c4734b78973970e0d3d)を見てください！！
泣いて喜びます！

https://qiita.com/DEmodoriGatsuO/items/4c4734b78973970e0d3d

`グローバル変数`である`Response`にオブジェクトを返します。
`モダン コントロールのテーブル`に、明細を表示させたいため、`応答`で、値を取得しています。

# Power Automate
あとは**プロンプトの改修**で、事足ります。

`OpenAI`のAPIと異なり、`model`のパラメーターが不要です。

■ HTTP

| キー | 値 |
|---|---|
| URI | `@{body('GetSecret_ENDPOINT')?['EnvironmentVariableSecretValue']}openai/deployments/@{body('GetSecret_DEPLOYMENT')?['EnvironmentVariableSecretValue']}/chat/completions?api-version=2024-02-15-preview` |
| Method | POST |

ヘッダー

| キー | 値 |
|---|---|
| Content-Type | application/json |
| Authorization | `@{body('GetSecret_APIKEY')?['EnvironmentVariableSecretValue']}` |

今回は`system`プロンプトを除き、下記のように設定しました。

```json:HTTP送信、本文
{
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "image_url",
          "image_url": {
            "url": "@{triggerBody()?['text']}",
            "detail": "high"
          }
        },
        {
          "type": "text",
          "text": "Read the following receipt image and extract the relevant details. The details should be structured in a JSON format as follows: ## JSON Schema  { \"date\": \"\", \"total_amount\": \"\", \"store_name\": \"\", \"items\": [ { \"name\": \"\", \"quantity\": \"\", \"price\": \"\" } ] }"
        }
      ]
    }
  ],
  "max_tokens": 800,
  "temperature": 0.7,
  "frequency_penalty": 0,
  "presence_penalty": 0,
  "top_p": 0.95,
  "response_format": {
    "type": "json_object"
  }
}
```

`{triggerBody()?['text']}`に、`base64文字列`を送信します。

戻り値の設定は、

```plaintext:
 ## JSON Schema  { \"date\": \"\", \"total_amount\": \"\", \"store_name\": \"\", \"items\": [ { \"name\": \"\", \"quantity\": \"\", \"price\": \"\" } ] }"
```

上記で完了しています。
上記の中で、`total_amount`や、`quantity`、`price`といった属性は、データ型の評価が`string`か`number`、場合によって変わるため、`応答`アクションのスキーマを工夫します。

```json:Schema
{
    "type": "object",
    "properties": {
        "date": {
            "type": "string"
        },
        "total_amount": {
            "type": "string"
        },
        "store_name": {
            "type": "string"
        },
        "items": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "quantity": {
                        "type": [
                            "string",
                            "number",
                            "null"
                        ]
                    },
                    "price": {
                        "type": [
                            "string",
                            "number",
                            "null"
                        ]
                    }
                },
                "required": [
                    "name",
                    "quantity",
                    "price"
                ]
            }
        }
    }
}
```

値の評価を、`null`、`number`も`OK`に設定しています。
これによって、エラーを防ぐことにつながります。

# 結構、精度が良さげ
シンプルに`GPT-4o`に、画像を渡しているだけですが、意外と読み取れていることがわかります。
ポイントは事前準備が、ほぼないことです。

恐ろしい精度！
いくつか領収書を使って、どれだけのパターンに耐えうるか、調査してみたいと思います。

`OCR`ビジネスが、ひっくり返る可能性もありますね！ではまた！