Power Apps & GPT-4oを使って超高速で画像解析アプリを作る！

# GPT-4o凄すぎる！！
出たときから騒ぎまくっていましたが、`GPT-4o`の登場で興奮しっぱなしの私です。

先日[こちらのQiitaの記事](https://qiita.com/sakasegawa/items/45947b0a1c1abb963d0a)を拝読し、「Power Appsでやったらどうなるだろう🧐」という思いが抑えられず、作ってみたら**超高速で画像解析アプリが作成できました！**

https://qiita.com/sakasegawa/items/45947b0a1c1abb963d0a

あらためて記事を出してくださったことに感謝いたします！
まずは初弾！ということで`GPT-4o`を使って、
**Power Appsで画像解析アプリを作成する方法** を書いていきます！

# まずは見た目（Power Apps）から！
まずは`Power Apps`でサクっと画面を作ります！
PowerPoint感覚で作れることが強みですからね！

![スクリーンショット 2024-05-17 231737.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/317d6eb9-dc21-dd64-24d0-8d017cae77ee.png)

最低限のもので構成しています。

```
画面
├─ ScreenContainer - スクリーン全体
├─ HeaderContainer
│  └─ Header - ヘッダーコントロール
├─ BottomContainer
│  ├─ SidebarContainer
│  │  └─ Badge - バッジコントロール
│  ├─ InfoContainer
│  │  ├─ InfoButton - インフォボタン
│  │  └─ lblInfo - インフォボタンの隣のラベル Add Picture for GPT-4o
│  ├─ ImageContainer
│  │  ├─ AddPicture - 画像の追加
│  │  └─ Image - イメージコントロール
│  └─ ButtonContainer
│     └─ Button - ボタンコントロール
└─ MainContainer
   └─ Container
      ├─ LogoImage - ドーナツ🍩のロゴ
      ├─ MainLabel - テキストラベル
      └─ TextInput - 結果出力用のテキスト入力
```

機能は`Button`の`OnSelect`プロパティに、`Power Automate`から値を取得するのみ。
※ 詳細は後述

# Power Automate
`GPT-4o`のAPIを呼び出すことが目的です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/a5df4eff-a67d-b576-8db0-259e8d8689ba.png)

ポイントになるのは、APIを呼び出すための`Body`の作り方です。
私は`作成`アクションで定義をしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/ba43ad98-36e7-d735-049f-04af2f8b6192.png)

[冒頭に紹介した記事](https://qiita.com/sakasegawa/items/45947b0a1c1abb963d0a)をもとに、API用のBodyを組み立てます。


[OpenAI](https://cookbook.openai.com/examples/how_to_call_functions_with_chat_models)の資料を見ると、プロンプトの中で戻り値を定義できることがわかります。

```json:作成
{
  "model": "gpt-4o",
  "messages": [
    {
      "role": "system",
      "content": "提供された画像を分析し、被写体が何か推定してください。"
    },
    {
      "role": "user",
      "content": [
        {
          "type": "image_url",
          "image_url": {
            "url": "@{triggerBody()?['text']}",
            "detail": "high"
          }
        },
        {
          "type": "text",
          "text": "画像から何が書かれているか推定してください。回答は日本語でしてください。## JSON Schema {\"type\": \"object\",\"properties\": { \"observe\": {\"type\": \"string\", \"description\": \"被写体についての推論\"}},\"required\": [\"observe\"],}"
        }
      ]
    }
  ],
  "temperature": 1,
  "response_format": {
    "type": "json_object"
  }
}
```

`GPT-4o`を呼び出す`URI`は一定ですが、モデルとプロンプトは本文で定義します。
`messages`の`text`プロパティに注目すると

```plaintext:messages[1]?[content][1]
画像から何が書かれているか推定してください。回答は日本語でしてください。## JSON Schema {\"type\": \"object\",\"properties\": { \"observe\": {\"type\": \"string\", \"description\": \"被写体についての推論\"}},\"required\": [\"observe\"],}
```

上記の中で、JSONのShemaを定義して戻り値を設定しています。
つまり、この文章によって、機能をバインドできているということです！

あとは、`解析のための画像をどう渡すか`が課題になります。
今回は`Power Apps`側で、画像を`JSON文字列`として評価、`JSON文字列`を`Power Automate`へ渡し、`GPT-4o`に渡す方法を採用しました。

# Power Appsからbase64文字列でPower Automateに渡す
とりあえず`base64文字列`でPower Automateに渡すと手っ取り早いということがわかったので、[界隈で著名である方の記事](https://mofumofupower.hatenablog.com/entry/2021/01/06/170240)を参考に、Power Appsのボタンコントロールの`OnSelect`に仕込みをいれます。

https://mofumofupower.hatenablog.com/entry/2021/01/06/170240


```plaintext:OnSelect
UpdateContext(
    {Response:'GPT-4o-Image-observe'.Run(
        Substitute(
            JSON(Image.Image,JSONFormat.IncludeBinaryData),
            """","")
        ).response
    }
)
```

`Response`に`GPT-4o`の戻り値を格納します。

1. [JSON関数](https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-json)で`base64文字列`をPower Appsで評価
1. Power Automateへ渡す

`JSONFormat.IncludeBinaryData`の引数が肝です。
> 結果には、イメージ、ビデオ、およびオーディオ クリップ列が含まれます。 この形式は、結果のサイズが大幅に増加し、アプリのパフォーマンスが低下する可能性があります。
>> [Microsoft learnより](https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-json)

`Substitute`で不要な文字列を消さないと、エラーになります。

# あらためてPower Automateへ！
Apps側で調整ができれば、`作成`アクションは前述のとおりです！

```plaintext:作成
{
  "model": "gpt-4o",
  "messages": [
    {
      "role": "system",
      "content": "提供された画像を分析し、被写体が何か推定してください。"
    },
    {
      "role": "user",
      "content": [
        {
          "type": "image_url",
          "image_url": {
            "url": "@{triggerBody()?['text']}",
            "detail": "high"
          }
        },
        {
          "type": "text",
          "text": "画像から何が書かれているか推定してください。回答は日本語でしてください。## JSON Schema {\"type\": \"object\",\"properties\": { \"observe\": {\"type\": \"string\", \"description\": \"被写体についての推論\"}},\"required\": [\"observe\"],}"
        }
      ]
    }
  ],
  "temperature": 1,
  "response_format": {
    "type": "json_object"
  }
}
```

■ HTTP

| キー | 値 |
|:-:|:-:|
| URI | https://api.openai.com/v1/chat/completions |
| Method | POST |

ヘッダー

| キー | 値 |
|:-:|:-:|
| Content-Type | application/json |
| Authorization | Bearer `@{outputs('シークレットの取得')?['body/value']}` |

:::note info
例では、Azure Key Vaultにて、APIのエンドポイントやキーを格納しています。
:::

`Body`は前述の`作成`アクションで作成した`JSON`です！

戻り値は

```
@{body('HTTPアクションの名前')?['observe']}
```

上記で取得できます！

# おわりに
**30分もかからずに画像解析アプリができました！！**
精度も高い！

まだまだ向上の余地あり！ですので続報にご期待ください！
よいPower & AI Lifeを！

読んでくださり、ありがとうございました！！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/b6b9be2e-a78e-4663-8111-fd6afb454bcb.png)