はじめに
昨日、Power AppsでGPT-4oを用いた画像から中身を判別するアプリを作成しました。
驚きの手軽さで作れることができます。
今回は作成のもととなった記事に挑戦し、食材の写真から料理を提案するアプリをサクっと作っていきましょう!
流れ
昨日作成したPower Automate フローのプロンプトを変えれば、ほとんどできてしまいます。
プロンプトを変えます。
{
  "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\": {\"cooking\": {\"type\":\"string\", \"description\": \"提案される料理名\"}},{ \"recipe\": {\"type\": \"string\", \"description\": \"料理のレシピ\"}},{\"calorie\": {\"type\": \"string\", \"description\": \"カロリー\"}},{\"ingredient\": {\"type\": \"string\", \"description\": \"食材\"}},{\"hour\": {\"type\": \"string\", \"description\": \"調理にかかる時間\"}},\"required\": [\"cooking\",\"recipe\",\"calorie\",\"ingredient\",\"hour\"],}"
        }
      ]
    }
  ],
  "temperature": 1,
  "response_format": {
    "type": "json_object"
  }
}
上記の中のコチラ↓
## JSON Schema {\"type\": \"object\",\"properties\": {\"cooking\": {\"type\":\"string\", \"description\": \"提案される料理名\"}},{ \"recipe\": {\"type\": \"string\", \"description\": \"料理のレシピ\"}},{\"calorie\": {\"type\": \"string\", \"description\": \"カロリー\"}},{\"ingredient\": {\"type\": \"string\", \"description\": \"食材\"}},{\"hour\": {\"type\": \"string\", \"description\": \"調理にかかる時間\"}},\"required\": [\"cooking\",\"recipe\",\"calorie\",\"ingredient\",\"hour\"],}"
の箇所です。
propertiesに戻り値を追加します。
| キー | データ型 | 内容 | 
|---|---|---|
| cooking | string | 提案される料理名 | 
| recipe | string | 料理のレシピ | 
| calorie | string | カロリー | 
| ingredient | string | 食材 | 
| hour | string | 調理にかかる時間 | 
JSONのなかでJSONスキーマを定義するため、ダブルクオーテーション" のまえに、 エスケープ文字\ が入ります。
ひじょーに書きづらいですね…。
こういうのもGPT-4oにお願いしちゃいましょう!
すげー!
GPT-4oに渡すJSONの定義は、作成アクションで実施します。
作成アクションで記載する中でも、JSONの形式が異常であればエラーを表示するため、誤りを見つけやすいです。
中身の文字列が変わっただけで、やっていることは一緒ですね。
- 
PowerApps (V2) トリガーで、画像のbase64文字列を、Power Appsから受け取る
- 
作成アクションで、GPT-4oに渡す値を定義
- 
Azure Key VaultからAPIキーを取得
- 
GPT-4oにHTTP要求を送信
- 結果をPower Appsに返す
さらっと上手くいってしまう・・・。
Power Appsのデザインに時間を投入しましょう。
前回と戻り値が異なるため、JSON の解析アクションでスキーマを再定義する必要があります。
今回の例では下記の通りとなります。
{
    "type": "object",
    "properties": {
        "cooking": {
            "type": "string"
        },
        "recipe": {
            "type": "string"
        },
        "calorie": {
            "type": "string"
        },
        "ingredient": {
            "type": "string"
        },
        "Hour": {
            "type": "string"
        }
    }
}
Power Appsに戻す値も、設定しましょう。
{
  "type": "Response",
  "kind": "PowerApp",
  "inputs": {
    "schema": {
      "type": "object",
      "properties": {
        "cooking": {
          "title": "cooking",
          "type": "string",
          "x-ms-content-hint": "TEXT",
          "x-ms-dynamically-added": true
        },
        "recipe": {
          "title": "recipe",
          "type": "string",
          "x-ms-content-hint": "TEXT",
          "x-ms-dynamically-added": true
        },
        "calorie": {
          "title": "calorie",
          "type": "string",
          "x-ms-content-hint": "TEXT",
          "x-ms-dynamically-added": true
        },
        "ingredient": {
          "title": "ingredient",
          "type": "string",
          "x-ms-content-hint": "TEXT",
          "x-ms-dynamically-added": true
        },
        "hour": {
          "title": "hour",
          "type": "string",
          "x-ms-content-hint": "TEXT",
          "x-ms-dynamically-added": true
        }
      },
      "additionalProperties": {}
    },
    "statusCode": 200,
    "body": {
      "cooking": "@body('JSON_の解析')?['cooking']",
      "recipe": "@body('JSON_の解析')?['recipe']",
      "calorie": "@body('JSON_の解析')?['calorie']",
      "ingredient": "@body('JSON_の解析')?['ingredient']",
      "hour": "@body('JSON_の解析')?['Hour']"
    }
  },
  "runAfter": {
    "JSON_の解析": [
      "Succeeded"
    ]
  }
}
Power Apps
完成系はTwitterで紹介させていただきました。
デザイン整えられた!
— 出戻りガツオ🐟 Microsoft MVP (@DemodoriGatsuo) May 18, 2024
GPT-4oを使って、食材の写真から、料理名・カロリー・食材・かかる時間を提案してもらうアプリ!
低コストでこれが実現できるのは震えます!!#PowerApps #OpenAI #ChatGPT #GPT4o pic.twitter.com/aVgy0qAr7t
レイアウトの着想は、Power Apps loverの師匠Reza Dorrani氏のYouTubeで紹介されたアプリをオマージュしています。
コントロールのツリー図
Main
├─ grpComplete // 完了画面
│  ├─ btnComplete // ボタンで完了画面を閉じる
│  ├─ txtComplete
│  └─ backgroundComplete
├─ grpProgress // ロード画面
│  ├─ txtProgress
│  ├─ imgProgress
│  ├─ Progress
│  └─ backgroundProgress
└─ ScreenContainer
   ├─ HeaderContainer
   │  ├─ imgTop
   │  ├─ lblTitle
   │  ├─ Avatar
   │  └─ lblMyname
   ├─ BottomContainer
   │  └─  SidebarContainer
   │  └─ galMode // 写真のアップロードかカメラか選択
   │  │  ├─  shpSelected
   │  │  ├─ icoMenu
   │  │  ├─ lblMode
   │  │  └─ shpSelectedBackground
   │  ├─ conBadge
   │  │  └─ Badge
   │  ├─ InfoContainer
   │  │  ├─ InfoButton
   │  │  └─ lblInfo
   │  ├─ conPicture // galModeに応じて表示、非表示
   │  │  ├─ ImageContainer
   │  │  │  ├─ AddPicture
   │  │  │  └─ Image
   │  │  └─ ButtonPicture
   │  └─ conCamera // galModeに応じて表示、非表示
   │     ├─ CameraContainer
   │     └─ ButtonCamera
   └─ MainContainer
      ├─ conHeaderMain
      │  ├─ LogoImage
      │  ├─ lblHeaderMain
      │  ├─ lblCooking // 料理名を表示するテキストラベル
      │  └─ barHeaderMain
      ├─ conMainRecipe
      │  └─ txtRecipe // レシピを表示するテキスト入力
      ├─ conCalorie
      │  ├─ imgCalorie
      │  ├─ keyCalorie
      │  └─ txtCalorie // カロリーを表示するテキストラベル
      ├─ conIngredient
      │  ├─ imgIngredient
      │  ├─ keyIngredient
      │  └─ txtIngredient // 食材を表示するテキストラベル
      └─ conHour
         ├─ imgHour
         ├─ keyHour
         └─ txtHour // 時間を表示するテキストラベル
モダンコントロールでSharePointを選択。
これをもとに、コントロールのカラーに統一感を持たせます。
色の濃淡はApp.Theme.Colorsで表現しましょう
モダン コントロールのほとんどが、このテーマに沿って色の表現するため、非常に便利です。
今回はヘッダーを自作します。アバターが追加されましたね。
アイコンは下記のサイトから使わせていただいています。
ドーナツ🍩のアイコンは、PowerPointにてストック画像の色を変えて、挿入しています。
画像の追加とカメラからも画像認識をすることを見据えてコントロールを用意します。
メニューの切り替えは、ギャラリーで実施します。
写真を直接追加する方法とカメラから写真を送る方法で、コンテナー単位でコントロールを分けます。
ギャラリーのSelectedの値とVisibleを連動させ、切り替えを実現します。
(galMode.Selected.Mode="Attachment")
(galMode.Selected.Mode="Camera")
GPT-4oから
- 料理名
- レシピ
- カロリー
- 食材
- かかる時間
合計5つの値を受け取るため、受け皿を加えます。
Power Automateの戻り値を受け取り、それぞれのプロパティで値を設定。
   └─ MainContainer
      ├─ conHeaderMain
      │  ├─ LogoImage
      │  ├─ lblHeaderMain
      │  ├─ lblCooking // 料理名を表示するテキストラベル
      │  └─ barHeaderMain
      ├─ conMainRecipe
      │  └─ txtRecipe // レシピを表示するテキスト入力
      ├─ conCalorie
      │  ├─ imgCalorie
      │  ├─ keyCalorie
      │  └─ txtCalorie // カロリーを表示するテキストラベル
      ├─ conIngredient
      │  ├─ imgIngredient
      │  ├─ keyIngredient
      │  └─ txtIngredient // 食材を表示するテキストラベル
      └─ conHour
         ├─ imgHour
         ├─ keyHour
         └─ txtHour // 時間を表示するテキストラベル
あとはPower Automate実行中の待機画面です。
GPT-4oの強みで全然時間がかからないのですが、昨日と全く一緒では芸がないので、ロード画面と完了画面を入れます。
- 
ロード画面四角形に、テキストラベルとプログレスバーを入れる
- 
完了画面四角形に、テキストラベルとボタンを入れる
Main
├─ grpComplete // 完了画面
│  ├─ btnComplete // ボタンで完了画面を閉じる
│  ├─ txtComplete
│  └─ backgroundComplete
├─ grpProgress // ロード画面
│  ├─ txtProgress
│  ├─ imgProgress
│  ├─ Progress
│  └─ backgroundProgress
それぞれ作成します。
画面の真ん中にコントロールが配置されるようにするには、X、Yのプロパティを下記のように設定します。
(Parent.Width - Self.Width) / 2
(Parent.Height - Self.Height) / 2
ギャラリーの中のコントロールの場合は、
- (Parent.TemplateWidth - Self.Width) / 2
- (Parent.TemplateHeight - Self.Height) / 2
上記で定義できます。
しょっちゅう使いまわすので、覚えておいて損はないです。
これで、GPT-4oを呼び出すボタンコントロールに下記の式を設定すれば
/*
* ロード画面を表示する
*/
Set(varVisible1,true);
/*
* Power AutomateでGPT-4oを呼び出す
*/
UpdateContext({Response:'GPT-4o-Image-observe'.Run(Substitute(JSON(Image.Image,JSONFormat.IncludeBinaryData),"""",""))});
/*
* 完了画面を表示する
*/
Set(varVisible2,true);
ロード画面のグループ、完了画面のグループが順番に表示され、映えます!
Power Automateの昨日と異なるポイントが、ほとんどないためPower Appsの説明に重きを置きました。
ChatGPT用のカスタムコネクタを作ると、Power Automateを経由せずにできるので、さらに便利ですね。
カスタムコネクタの作り方は、Microsoftの吉野様のQiitaの記事が大変わかりやすいです。
おわりに
強力な機能が、こんなに簡単に使えて本当にいいのかと震えます。
今度は何を作ろう・・・。
ワクワクが止まりませんね!
皆様、良いPower lifeを!
















