【Microsoft Fabric で健康管理】Power Apps × Microsoft Fabric × Azure Open AI (AOAI) で摂取カロリーを記録・可視化する

## はじめに

カロリーはだいたいの数値が出れば OK → GPT モデルの出番ということでこんなのを作ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/0da76fa1-127e-93e2-19af-e2ed06b28ccf.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/2ddc45c7-2dbf-8448-48be-8b4ba15da263.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/7af6546d-f6d7-0b23-b5bc-3103cabfb923.png)

## コンポーネント


- Azure Open AI : GPT-4o モデルのAPIを提供
- カメラアプリ: Power Apps で作成。写真の解析を AOAI にリクエスト→ Fabric に送信まで行う
- Microsoft Fabric 
    - Real-time Intelligence : 送信されたデータをリアルタイム分析用データベース(KQL データベース)に格納
    - Power BI : データベースへのクエリの発行、可視化の提供
        - [リアルタイムダッシュボード](https://learn.microsoft.com/ja-jp/training/modules/create-real-time-dashboards-microsoft-fabric/) で可視化を作成すれば Real-time Intelligence で完結します。これだと Power BI ライセンスは不要。


## 参考にしたもの

## 手順

1. Fabric の準備
1. Power Apps カスタムコネクタ(AOAI)の作成
1. Power Apps カスタムコネクタ(Fabricデータ送信)の作成
1. Power Apps カメラアプリの作成
1. データ変換とPower BI による可視化

### 前提条件

- GPT-4o デプロイが利用可能な Azure Open AI Service が必要です
- プレミアムコネクタが利用可能なPower Apps ライセンス（開発者ライセンスなど）
- Microsoft Fabric 容量（試用版など）
    - [開始方法](https://learn.microsoft.com/ja-jp/fabric/get-started/fabric-trial)
- (共有する場合）Power BI Pro 

### Fabric の準備

1 . [チュートリアルのワークスペース作成手順](https://learn.microsoft.com/ja-jp/fabric/data-engineering/tutorial-lakehouse-get-started#create-a-workspace) などを参考にワークスペースを作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/b717740c-efbe-6ec5-2301-52abb88ee85f.png)

2 . [チュートリアルのイベントハウス作成手順](https://learn.microsoft.com/ja-jp/fabric/real-time-intelligence/tutorial-1-resources)を参考にKQL データベースを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/9950800a-7673-b44b-bd3f-bd78b753d52a.png)

3 . イベントストリームを作成。名前は任意
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/a503af82-525d-2a8a-98d3-4e5a956e6474.png)

4 . ソースにカスタムアプリを追加(名前は任意）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/8b3cf0b7-a252-9ba8-cc00-c32d7974ee50.png)

5 . キー情報を記録します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/ceb715cf-d351-c606-38fb-a5e7af0c4d60.png)

6 . サービスバス名はサンプルコードから取得します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/6a99e69e-7214-496e-8a7e-8e5ccc89fa16.png)


7 . 以下のコードにより、tokenなどを取得します

```pyhon:python
#%%

import time
import urllib.parse
import hmac
import hashlib
import base64

sb_name='<記録した情報(サービスバス名)に置き換え>'
eh_name='<記録した情報(Event hub name)に置き換え>'
sas_name='<記録した情報(Shared access key Name)に置き換え>'
sas_key='<記録した情報(primary key)に置き換え>'


def get_auth_token(sb_name, eh_name, sas_name, sas_key):
    """
    Returns an authorization token dictionary 
    for making calls to Event Hubs REST API.
    """

    uri_str = f"https://{sb_name}.servicebus.windows.net/{eh_name}/" 
    uri = urllib.parse.quote_plus(uri_str)
    sas = sas_key.encode('utf-8')
    expiryNum = 60*60*24*365*3
    expiry = str(int(time.time() + expiryNum))
    string_to_sign = (uri + '\n' + expiry).encode('utf-8')
    signed_hmac_sha256 = hmac.HMAC(sas, string_to_sign, hashlib.sha256)
    signature = urllib.parse.quote(base64.b64encode(signed_hmac_sha256.digest()))
    return  {
             "uri": uri_str,
             "sb_name": sb_name,
             "eh_name": eh_name,
             "token":'SharedAccessSignature sr={}&sig={}&se={}&skn={}' \
                     .format(uri, signature, expiry, sas_name)
            }
# %%

result = get_auth_token(sb_name,eh_name,sas_name,sas_key)
print (f"result: {result}")


```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/5286d244-7d11-f058-78ef-313a27b3469d.png)

### Power Apps カスタムコネクタ(AOAI)の作成

1 . AOAIリソースで各種情報をメモ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/86c9b756-ae71-94dc-7fb1-f117e0c0c46a.png)


2 . デプロイの名前もメモしておきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/932bb5b5-0386-7be6-8606-599cf0a44e1f.png)

3 . [https://make.powerapps.com/](https://make.powerapps.com/) にアクセスして、すべて見るに移動
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/ccbaaf1a-60df-e265-eacd-d32c954f0f05.png)

4 . カスタムコネクタを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/175741df-c5a3-6160-853a-f554b99afa21.png)

5 . 一から作成を選択。名前は任意
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/66136d8b-f598-7e0e-c212-9ff65f1353b8.png)

6 .  ホスト名など入力。ベースURLは/openai/ 固定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/b8bac527-3f98-0b12-bda6-9951d0c5d342.png)

7 . セキュリティ

補足：[chat completion API docs 参考箇所](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/reference#chat-completions)：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/0dd8991d-758c-2413-41e0-a68a35bf11dd.png)

8 . 認証タイプとキーのラベルを設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/402e8505-4a67-d9f9-9656-28299bf28f8b.png)

補足：[chat completion API docs 参考箇所](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/reference#chat-completions)：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/86f2173e-82fa-9d8f-6f55-041d98b3b7ca.png)

9 . 新しいアクションを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/4509b41b-2333-ed23-6f15-8dd77385f41c.png)

10 . 全般はこのように入力。※説明はdocsから適宜もってきました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/cf96febd-8482-e5a0-c64b-580625758688.png)

11 . サンプルからのインポートを選択してインポート
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/7b3db6d2-4b6a-3990-cbf6-ce10581c01aa.png)
補足：[chat completion API docs 参考箇所](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/reference#chat-completions)：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/33106a75-98c9-42a7-626f-1975980c410d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/bbc7eee4-6da4-f253-6994-238770b61f25.png)

12 . 応答も入れます※docs そのままだとjsonと認識してくれないので改良
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/2774341d-37de-9128-76a0-4941568b8b58.png)

```json:json
{
  "id": "chatcmpl-6v7mkQj980V1yBec6ETrKPRqFjNw9",
  "object": "chat.completion",
  "created": 1679072642,
  "model": "gpt-35-turbo",
  "usage": {
    "prompt_tokens": 58,
    "completion_tokens": 68,
    "total_tokens": 126
  },
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "Yes, other Azure AI services also support customer managed keys.\r\n                    Azure AI services offer multiple options for customers to manage keys, such as\r\n                    using Azure Key Vault, customer-managed keys in Azure Key Vault or\r\n                    customer-managed keys through Azure Storage service. This helps customers ensure\r\n                    that their data is secure and access to their services is controlled."
      },
      "finish_reason": "stop",
      "index": 0
    }
  ]
}

```

補足：[chat completion API docs 参考箇所](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/reference#chat-completions)：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/727397cd-a3d9-8e02-14cd-0efc4aa49a70.png)

13 . 作成します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/2bb3ca93-8245-658e-11d6-dfad329e95c8.png)

14 . テストのために新しい接続を作成します。ポップアップでは、冒頭でメモした、AOAI リソースのAPIキーを入力します。なお、画面遷移してしまうので、URLもメモっておくとよいです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/982ebe80-139f-3912-cba4-ef9a592d2ad1.png)

15 . テスト用の情報を入力。未加工の本文にして、サンプルからのインポートで使った本文を入れるとよいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/196e076a-a806-922c-5068-af31a0e238e3.png)

16 . テストを実行すると、解析結果が返ってきます。AOAI用のカスタムアプリはこれでOK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/e079bdea-026e-57ab-f5d3-a36ee6bb15a0.png)

参考のswaggerはこちら

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/5d6cd95c-0736-ecb6-8782-7806f19822ac.png)


```yaml:

swagger: '2.0'
info:
  title: 置き換えてください
  description: ''
  version: '1.0'
host: 置換えてください.openai.azure.com
basePath: /openai/
schemes:
  - https
consumes: []
produces: []
paths:
  /deployments/{deployment-id}/chat/completions:
    post:
      responses:
        default:
          description: default
          schema:
            type: object
            properties:
              id:
                type: string
                description: id
              object:
                type: string
                description: object
              created:
                type: integer
                format: int32
                description: created
              model:
                type: string
                description: model
              usage:
                type: object
                properties:
                  prompt_tokens:
                    type: integer
                    format: int32
                    description: prompt_tokens
                  completion_tokens:
                    type: integer
                    format: int32
                    description: completion_tokens
                  total_tokens:
                    type: integer
                    format: int32
                    description: total_tokens
                description: usage
              choices:
                type: array
                items:
                  type: object
                  properties:
                    message:
                      type: object
                      properties:
                        role:
                          type: string
                          description: role
                        content:
                          type: string
                          description: content
                      description: message
                    finish_reason:
                      type: string
                      description: finish_reason
                    index:
                      type: integer
                      format: int32
                      description: index
                description: choices
      summary: Chat-completions
      operationId: Chat-completions
      description: GPT-35-Turbo と GPT-4 モデルを使用して、チャット メッセージの入力候補を作成します。
      parameters:
        - name: deployment-id
          in: path
          required: true
          type: string
        - name: api-version
          in: query
          required: false
          type: string
        - name: body
          in: body
          required: false
          schema:
            type: object
            properties:
              messages:
                type: array
                items:
                  type: object
                  properties:
                    role:
                      type: string
                      description: role
                    content:
                      type: array
                      items:
                        type: object
                        properties:
                          type:
                            type: string
                            description: type
                          text:
                            type: string
                            description: text
                          image_url:
                            type: object
                            properties:
                              url:
                                type: string
                                description: url
                              detail:
                                type: string
                                description: detail
                            description: image_url
                      description: content
                description: messages
definitions: {}
parameters: {}
responses: {}
securityDefinitions:
  API KEY:
    type: apiKey
    in: header
    name: api-key
security:
  - API KEY: []
tags: []


```

### Power Apps カスタムコネクタ(Fabricデータ送信)の作成

1 . 同様にFabric 用のカスタムコネクタも作ります。ホストやベースURLを入力※メモで記録したURIから値をもってきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/8881f694-3d50-2829-6d24-c4fabaec39e7.png)

2 . セキュリティは以下のようにします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/d32f9d5e-4fbf-3a5b-794a-d0492578158f.png)

3 . 全般は任意
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/efc789af-aadf-7bb9-2996-a463d73138f6.png)

4 . URLと本文を入力します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/8071aa4d-9644-f908-fbe2-d9a08d45f7be.png)


```

<メモしたURI部>messages?timeout=60&api-version=2014-01

```

```json:json

{
  "image": "test2",
  "result": ""
}

```


5 . 作成後、テスト画面に移動します。キーにはメモしたtoken部を利用ください。うまくいかない場合はアクションを削除して再度作成してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/bd834c50-4cd8-4b76-0744-361a25778a60.png)

```test用本文:test用

{
  "image": "test2",
  "result": "[]"
}


```

6 . 成功すると、Fabric のイベントストリームでデータが表示されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/ca85eeac-abea-8e4f-dfdc-3557e9a474cd.png)

参考のswaggerはこちら


```yaml:yaml
swagger: '2.0'
info:
  title: 置き換えてください
  description: ''
  version: '1.0'
host:  置き換えてください
basePath:  置き換えてください
schemes:
  - https
consumes: []
produces: []
paths:
  /messages:
    post:
      responses:
        default:
          description: default
          schema: {}
      summary: send-message
      description: send-message
      operationId: Send-message
      parameters:
        - name: timeout
          in: query
          required: false
          type: integer
        - name: api-version
          in: query
          required: false
          type: string
        - name: body
          in: body
          required: false
          schema:
            type: object
            properties:
              image:
                type: string
                description: image
              result:
                type: string
                description: result
definitions: {}
parameters: {}
responses: {}
securityDefinitions:
  SAS:
    type: apiKey
    in: header
    name: Authorization
security:
  - SAS: []
tags: []



```

### Power Apps カメラアプリの作成

1 . キャンバスアプリではじめます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/6d910155-4397-78d5-704c-ece381e242c1.png)

2 . スマホレイアウトの空のキャンバスを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/c74dbebc-ddd3-5909-848c-4beba59eacee.png)

3 . データを追加から、カスタムコネクタ2点を追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/6800e20e-e37b-db2f-53c2-d6201ebba14c.png)

4 . 画像の追加コントロールを追加して調整
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/ba01cdb9-08f3-1a08-ed43-8ef8b01c3cf5.png)

5 . ALTキー押しながらクリックして画像を読み込ませておくとよいでしょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/011620ca-b698-f196-67d5-c81d91df77fb.png)

6 . ボタンを追加して、OnSelect に以下コマンドをコネクタ名だけ変更して追加。いわゆるシステムプロンプトなどは適宜調整してください。私はテキスト入力コントロールを追加してカロリーをn倍するなどの調整ができるように使っています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/c19f5346-5064-b41e-ae36-516010dec2cd.png)

```

Set( ImageJSON, JSON( UploadedImage1.Image, JSONFormat.IncludeBinaryData ) );
ClearCollect(aoaiCollection, 
'<コネクタ名に置き換えてください>'.Chatcompletions("<デプロイ名に置換えてください>",
    {
        'api-version':"2024-02-01",
        max_tokens:800,
        messages:[
        {role:"system",content:[{type:"text",text:"あなたは画像からjson形式のデータを生成するAIです。json形式のデータのみを応答します。コードスニペットやデータ以外の情報を応答に含めてはいけません"}]},
        {role:"user",
            content:[
                {type:"text",text:"画像の中の料理のそれぞれの日本語料理名と日本語での説明、摂取カロリーを推定して。回答はシステムに取り込むため、以下のjson形式にしてください。
                [{food:"",kcal:0.0,description:""}]
                "},
                {type: "image_url",image_url:
                    {url:Substitute(ImageJSON,"""",""),detail: "auto"}
                }
            ]
        }
    ]
}).choices);
Set(resultText,Substitute(Substitute(First(aoaiCollection).message.content,"```",""),"json",""));


```

7 . 推論結果を確認するためのラベルを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/93002d90-db7a-207a-4c16-2b4ced5e3867.png)

8 . ALTキーを押しながら解析ボタンを押すとラベルに結果が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/e61aee27-e4f2-d221-e35d-e59afb2481f6.png)


9 . ボタンを追加して、OnSelect にFabricに送信する以下コマンドをコネクタ名だけ変更して追加。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/3a238fc2-2429-af01-96a8-282da0bb54e5.png)

```

<コネクタ名に置き換えてください>.Send-messages(
    {
        'api-version':"2014-01",
        image:Substitute(ImageJSON,"""",""),
        result: resultText,
        timeout:60}
        );
Notify(
    "Fabric にデータ送信しました",
    NotificationType.Success
);
```

10 .  ALTキーを押しながら送信ボタンを押して、Fabric 側でも確認ができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/f5713613-dd46-ba47-c2d8-dd0314d6d630.png)

これでアプリが完成しました

### データ変換とPower BI による可視化

1 . Fabric イベントストリームで、あて先をKQLデータベースで選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/7185dc93-115e-0f99-d209-8415d509c8df.png)

2 . Direct Ingestionを設定します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/215cb7c0-6bb7-178b-b522-6e02b5db6dc9.png)

3 . 送信先のテーブル名を入力します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/54281258-8dec-586e-8444-33e5a514e164.png)

4 . データが表示されたら終了
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/a2bc0f6b-7448-2080-b7ed-1ddd0862168b.png)

5 . 接続が構成されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/9e07c15a-a038-f713-2b9e-0efdd86d983c.png)

6 . イベントハウスに移動してテーブルを見つけたら、右クリックから100行選択をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/72c8530b-4c3a-f887-433c-7583360fc6ca.png)

7 . 自動でクエリ結果が表示されるのでKQL クエリセットとして保存
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/b94b25d2-1bff-5406-e5a1-b9bb6273b8d5.png)

8 . KQLを置き換えます。テーブル名は適宜変更してください。クエリ結果が表示されたらPower BI レポートを構築を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/fa226f7c-ee2c-92be-3005-e6f21585678c.png)


```kql:kql

['input-food']
| extend ingest_datetime = ingestion_time()
| mv-expand result
| project 
    ingest_datetime_JST = datetime_add('hour',9,ingest_datetime),
    image,
    food = tostring(result.food),
    decription=tostring(result.description),
    kcal = todecimal(result.kcal)


```

9 . 簡単に折れ線だけ作成してみます。が、作りにくいので、一旦保存して移動
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/bfcded3a-058e-6dd1-3e05-bb631ef10090.png)

10 . ワークスペース設定を変えておきます。（画像表示のためなので必須ではありません。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/9a5519f8-5344-a578-043e-12253a5c2fd8.png)

11 . データモデルを開きます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/e69324eb-66d1-97b9-1c10-bb2af8f19b01.png)

12　. image 列はカテゴリを画像のURLに変えておきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/9adbebfe-2fd3-255c-6e62-c95b38ab574b.png)

13　. レポートを開き、テーブルを追加して微調整したら完成です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281819/7ba28d99-af32-7247-4749-ff1fee3a25a3.png)


## 参考

- https://learn.microsoft.com/ja-jp/connectors/custom-connectors/define-blank
- https://learn.microsoft.com/ja-jp/connectors/custom-connectors/use-custom-connector-powerapps
- https://learn.microsoft.com/ja-jp/rest/api/servicebus/send-message-to-queue
- https://zenn.dev/microsoft/articles/azure-openai-latest-api-schema
-