見出し画像

【Dify】生成AIで健康管理アプリ作ってみた!

きっかけ

先日、血液検査の結果「中性脂肪」の値が「767mg」という異常値を叩き出しました。
基準値が「30~149mg」なので、なんと基準値の最高値の約5倍です。
中性脂肪の値が高いと動脈硬化や脳梗塞、心筋梗塞のリスクが高く、急性膵炎になる可能性もあります。

シンプルに言って「このままではヤバイ!」となりました。

というわけで、せっかく生成AIを学んでいるので、
それを活用して日々の食事カロリーと体重を記録して、生活改善に役立てようとチャットボットを作成できる「Dify」というツールを使って、作成してみました。

Difyとは?

DifyについてAI(Claude)に質問してみました。

Difyとは

  • ノーコード開発: プログラミングの知識がなくても、視覚的なインターフェースを使ってAIアプリを作れます。

  • チャットボット作成: 質問に答えたり会話したりするAIチャットボットを簡単に作成できます。

  • テキスト生成: AIを使って文章や記事を自動生成するアプリを作ることができます。

なるほどですね。
「ノーコード」で「チャットボット」が作れるという特徴を使って実際にDifyで作った「健康管理アプリ」を見ていきましょう。

健康管理botの使い方

ウェブアプリを開くと「mode」の選択画面が開きます。

「カロリー計算」を選択し、「Start Chat」ボタンをクリック。

チャット入力欄の画像アップロードアイコンから、画像をアップロードします。

チャットボットが動き、写真からカロリーや栄養素をだいたいで回答してくれます。
食事内容や一言アドバイスもくれます。

チャットボットが回答してくれた内容は、スプレッドシートに記録されるようにしています。

Googleの「LookerStudio」というサービスを利用し、収集したデータを視覚化しています。
摂取カロリーが激しく波打っているのと、体重がちょっとずつ減っているのが一目でわかります。

LookerStudioの説明については下記サイトをご参照ください。

使ってみて変わったこと

大きく変わったポイントについて書いてみます。

1日の摂取カロリーを気にするようになった

日々の目標として1日の摂取カロリーを「2000kcal」と定めていました。
1食ずつの記録を即時確認できるので、主に夕食の内容を調整するようになりました。
そうすることでかなり、1日の摂取カロリー目標を守ることができるようになりました。

簡単に記録できて継続しやすかった

写真を送るだけという簡単な入力を採用したことで、サボることなく継続することができました。
食事のカロリー記録の精度に関しては、そこまで高くはないですが、
自分の利用目的がおおよそで把握したいという感じだったので、
気にせず続けることができました。

改善が見える化されて楽しい

上記の画像で体重が公開されてしまっているので若干恥ずかしいですが……
見ていただいてわかるように、このアプリを使うようになってから
少しずつ体重減少が確認できて、効果を実感してモチベーションが上がりました。
おかげで、朝の空腹時になるべく体重が少ないタイミングで計ることが日課になりました(笑)

「健康管理bot」の作り方

Difyのアカウント登録(無料)をしてログインしてダッシュボードを開きます。

「最初から作成」をクリック
※Difyは無料プランで10個までチャットボットを作ることができます。

表示されたポップアップで
「チャットボット」を選択
「Chatflow」を選択
アプリの名前を入力し、「作成する」ボタンをクリック

まずは全体の流れから説明します。

①「開始」:ここではモード選択をする機能を追加しています。
選択内容は「カロリー計算」「体重記録」

②「質問分類器」:選択されたモードがどっちかを判別して、フローを分岐させます。

③「カロリー計算LLM」:入力された画像をAIに解析させて、テキストを生成します。

④「回答」:③で生成されたテキストをチャット画面に出力します。

⑤「JSON生成LLM」:③で生成されたテキストをスプレッドシートへ記録するためのデータ形式(JSON)に整えます。

⑥「スプレッドシート記録」:⑤で生成したデータをスプレッドシート記録するためのプログラムに投げ込みます。(GoogleAppScript=GASを使用)

⑦「処理結果判定」:⑥で処理した結果を判定します。

⑧「回答(成功)」または「回答(失敗)」:⑦からで取得した結果によって、いずれかを呼び出します。その内容をチャット画面に出力します。

それでは、それぞれ詳細な設定内容について触れていきます。

①「開始」

「入力フィールド」の「+」ボタンをクリックして入力フィールドを追加します。
開いたポップアップで「選択」を選択し、「変数名」「ラベル名」を任意で設定し、「オプション」に「カロリー計算」「体重記録」という要素を追加します。


この選択肢は、チャット画面を開くと選択できるようになり、次の「②質問分類器」で利用します。

次に進む前に「sys.files」というフィールドについて説明します。
これは画面右上にある「機能」というボタンから「画像アップロード」をONにすると、利用できるようになります。

フィールド「sys.files」は実際にカロリー計算をする際に利用するので、この設定が必要になります。

②「質問分類器」

①「開始」から受け取った情報をもとにユーザーの質問内容を分類します。
まず「入力変数」に、①で設定した入力フィールド「mode」を指定します。
次に、クラスに「カロリー計算」「体重記録」を追加します。
①から直接分類できればいいのですが、現状はこの「質問分類器」ブロックをはさんで分岐させる方法か、「IF/ELSE」ブロックを使う方法で分岐させる必要があります。

③「カロリー計算LLM」

これ以降の説明は「カロリー計算」を選択された場合についてのみ行います。

ここにきてやっと「生成AIを使って画像解析し、テキスト生成する」という内容になります。
「モデル」:ここでは「claude-3-5-sonnet-20240620」というモデルを使用しています。全体的に性能が良いモデルなので最近はこれを好んで使っています。
「コンテキスト」:「SYSTEM」という箇所に下記のシステムプロンプト(※)を組み込みます。

あなたは一流の管理栄養士です。
画像の食事のカロリー計算を行ってください。
出力は下記の項目に分けてください。
・総摂取カロリー(kcal)
・脂質(mg)

・炭水化物および糖質(mg)

・ビタミンC(mg) 
・ビタミンA(mg) 
・その他のビタミン(mg) 
・ミネラル(mg)
・タンパク質(mg)
・食物繊維(mg)
・鉄分(mg)

なお、末尾でjson形式の出力は不要です。
「記録が完了しました!」という表記も不要です。

なぜか後続のブロックを先読みしてJSON化したり、記録の完了を宣言してしまったりと、余分なテキストが混じってしまうため、末尾2行のような指示を追加しています。(本来は不要なはず)
※システムプロンプト…生成AIモデルに前提となる指示をあらかじめ仕込んでおく機能のこと。これによりユーザーは聞きたいことのみを入力することで望んだ回答が得られやすくなる。

次に「SYSTEM」の先頭で「/(スラッシュ)」を入力し「コンテキスト」を選択してください。
これは、これまで入力された内容や、フローで生成された内容そのものをこのブロックで使用するために必要になります。

④「回答」

「③カロリー計算LLM」で得られた回答を、チャット画面に表示するためのブロックです。
「回答」の欄に「/(スラッシュ)」を入力し「カロリー計算LLM.text」を選択してください。

⑤「JSON生成LLM」

このブロックで生成されたテキストをJSON形式に変換します。
「SYSTEM」に以下のシステムプロンプトを入力します。

{{#llm.text#}}から下記のJSON形式に変換してtext形式で出力してください。



単一の値になるように抽出してください。
"約""mg"などの余分なテキストは含まないでください。
複数の値から構成されている項目は合計値を、
範囲の値(例:5,500-6,500)で構成されている項目は最大値(例:6,500)を使用するようにしてください。

重要:いかなる場合もJSON形式テキストを含めいないこと

{
 "timestamp": "日付時刻",
  "totalCalories": "{{#llm.text#}}の総摂取カロリー",

  "fat": "{{#llm.text#}}の脂質",

  "carbohydrates": "{{#llm.text#}}の炭水化物および糖質",

  "vitamin_c": "{{#llm.text#}}のビタミンC",


  "vitamin_a": "{{#llm.text#}}のビタミンA",

  "vitamin_other": "{{#llm.text#}}のその他のビタミン",

  "minerals": "{{#llm.text#}}のミネラル",

  "protein": "{{#llm.text#}}のタンパク質",

  "fiber": "{{#llm.text#}}の食物繊維",

  "iron": "{{#llm.text#}}の鉄分"

}

{{#llm.text#}}の部分は、入力欄で「/(スラッシュ)」を入力し、「カロリー計算LLM.text」を選択した内容で置き換えてください。(画像のようになればOKです)

⑥「スプレッドシート記録」

ここでは「HTTPリクエスト」というブロックを使用します。
HTTPリクエストとは、簡単に言うとweb上の通信を利用して、情報をやり取りする仕組みです。これを使い、別途用意したプログラム(GAS)を経由してスプレッドシートに情報を書き込みます。

前準備

1.記録用のスプレッドシートを準備

名前は任意でOKです。今回僕は「health_log」としました。
各列の名前はわかりやすいようにつけているだけなので、今回の手順では入力しなくても動作するようになっています。

【注意】シート名は「カロリー計算」としてください

2.GASを作成して公開する

作成したスプレッドシートのメニュー「拡張機能」から「Apps Script」を開きます。

すると下記のような画面が開きます。

以下のコードを入力してください。

function doPost(e) {
  try {
    var action = e.parameter.action;
      
    if (action == 'calorie') {
      return ContentService.createTextOutput(JSON.stringify(calorieRecord(e)))
        .setMimeType(ContentService.MimeType.JSON);
    } else if (action == 'weight') {
      return ContentService.createTextOutput(JSON.stringify(weightRecord(e)))
        .setMimeType(ContentService.MimeType.JSON);
    } else {
      return ContentService.createTextOutput(JSON.stringify({status: 'error', message: 'Invalid action'}))
        .setMimeType(ContentService.MimeType.JSON);
    }
  } catch(error) {
    return ContentService.createTextOutput(JSON.stringify({status: 'error', message: error.toString()}))
      .setMimeType(ContentService.MimeType.JSON);
  }
}

function calorieRecord(e) {
  // リクエストのJSONを解析
  var jsonData = JSON.parse(e.postData.contents);
  
  // スプレッドシートを開く
  var sheet = SpreadsheetApp.openById('XXXXXXXXXXXXXXXXXXXXXXX').getSheetByName('カロリー計算');
  
  // 現在の日時を取得
  var currentDate = new Date();
  
  // データを格納するオブジェクト
  var data = {
    timestamp: currentDate,
    totalCalories: jsonData.totalCalories || '',
    fat: jsonData.fat || '',
    carbohydrates: jsonData.carbohydrates || '',
    vitamin_a: jsonData.vitamin_a || '',
    vitamin_c: jsonData.vitamin_c || '',
    vitamin_other: jsonData.vitamin_other || '',
    minerals: jsonData.minerals || '',
    protein: jsonData.protein || '',
    fiber: jsonData.fiber || '',
    iron: jsonData.iron || ''
  };
  
  // 最終行を取得
  var lastRow = sheet.getLastRow();
  
  // 同一日時帯(1時間以内)のデータを検索
  var found = false;
  if (lastRow > 1) {
    var lastTimestamp = sheet.getRange(lastRow, 1).getValue();
    if (currentDate - lastTimestamp < 1000 * 60 * 60) { // 1時間以内
      found = true;
    }
  }
  
  if (found) {
    // 既存の行を更新
    sheet.getRange(lastRow, 1, 1, 11).setValues([[
      data.timestamp, data.totalCalories, data.fat, data.carbohydrates,
      data.vitamin_a, data.vitamin_c, data.vitamin_other, data.minerals, data.protein, data.fiber, data.iron
    ]]);
  } else {
    // 新しい行を追加
    sheet.appendRow([
      data.timestamp, data.totalCalories, data.fat, data.carbohydrates,
      data.vitamin_a, data.vitamin_c, data.vitamin_other, data.minerals, data.protein, data.fiber, data.iron
    ]);
  }
  
  return {status: 'success'};
}

function weightRecord(e) {
  // リクエストのJSONを解析
  var jsonData = JSON.parse(e.postData.contents);
  
  // スプレッドシートを開く
  var sheet = SpreadsheetApp.openById('XXXXXXXXXXXXXXXXXXXXXXX').getSheetByName('体重管理');
  
  // 現在の日時を取得
  var currentDate = new Date();
  
  // データを格納するオブジェクト
  var data = {
    timestamp: currentDate,
    weight: jsonData.weight || ''
  };
  
  // 最終行を取得
  var lastRow = sheet.getLastRow();
  
  // 同一日時帯(1時間以内)のデータを検索
  var found = false;
  if (lastRow > 1) {
    var lastTimestamp = sheet.getRange(lastRow, 1).getValue();
    if (currentDate - lastTimestamp < 1000 * 60 * 60) { // 1時間以内
      found = true;
    }
  }
  
  if (found) {
    // 既存の行を更新
    sheet.getRange(lastRow, 1, 1, 2).setValues([[
      data.timestamp, data.weight
    ]]);
  } else {
    // 新しい行を追加
    sheet.appendRow([data.timestamp, data.weight]);
  }
  
  return {status: 'success'};
}

コード内の

  // スプレッドシートを開く
  var sheet = SpreadsheetApp.openById('XXXXXXXXXXXXXXXXXXXXXXX').getSheetByName('カロリー計算');

の「XXXXX…」の部分はご自身のスプレッドシートのIDで置き換えてください。
スプレッドシートのIDは下記キャプチャの黒塗り下部分に記載があります。

では、プログラムをDifyから呼び出せるように公開(デプロイ)しましょう。

App Scriptの画面から「デプロイ」->「新しいデプロイ」に進みます。

次に「種類の選択」から「ウェブアプリ」を選択し、「デプロイ」ボタンをクリックします。

ウェブアプリのURLをコピーをして、前準備は完了に成増での、Difyに戻ります。


HTTPリクエストブロックの追加

「API」の入力欄を「POST」を選択して、上記でコピーしておいた「ウェブアプリのURL」を貼り付けます。
そのURLの末尾に「?action=calorie」と付け加えてください。

続いて「ボディ」の欄で「JSON」を選択し、入力欄にて「/(スラッシュ)」を入力し、「JSON生成LLM.text」を選択してください。

⑦「処理結果判定」

「IF/ELSE」ブロックを使用して、処理の結果を判定します。
「IF」に「success」という文字列を入力します。

これは、GASのスクリプトにアクセスして、処理が成功すると「success」という文字列を含む結果が返却されるため、それを利用して「成功」と「失敗」を判定しています。

⑧「回答(成功)」または「回答(失敗)」

ここで「回答」ブロックを2つ追加します。

失敗した場合はウェブアプリからの返却内容を出力するように設定しています。

「回答」の入力内容に
「/(スラッシュ)」を入力し「スプレッドシート記録.status_code」「スプレッドシート記録.body」を設定ます

以上で、Dify上でのチャットボットのワークフローの作成が完了しました。

それではアプリを起動してみましょう。

公開前でも右側の「デバッグとプレビュー」で、実際の動作を試すことが可能です。

実際に画像をアップして試してみてください。
Difyの仕様上、チャットテキストの入力が必須なので「実行」とか「run」とか入力して、右側の紙飛行機マークをクリックしてください。

おわりに

お手持ちの写真のカロリー計算はできましたでしょうか?

このチャットボットワークフローの作り方で、
「画像解析からのテキスト生成」
「生成テキストの内容から新たに処理を実行」
「ウェブ経由でスプレッドシート記録」
が可能になりますので、ほかのユースケースにも転用可能かと思います。
例えば、
・定点カメラで在庫状況を写真を撮り、残数を日々更新したり
・紙の書類を写真で取って、特定の情報を抜き出して記録しておく
などができると思います。
精度については要改善のため、これからもDifyを触って知見を深めていきたいと思います!

ちなみにこのアプリを利用したことと、生活習慣の改善で
「767mg」だった中性脂肪は、約1か月で「166mg」まで下がりました。

まだ正常値の最高値をちょっと超えているため、健康管理botを活用して、正常値まで下げていこうと思います。

ここまで読んでいただきありがとうございます。

この記事が気に入ったらサポートをしてみませんか?