【忙しい人必見⚡】Difyを利用した次世代型ToDoリスト Part1(導入編)

## まじで最近忙しくないですか？？

皆さん最近忙しくないですか？
私はとても忙しいです。

当たり前と言われるかもしませんが、**仕事をして、勉強もして、家事もして**、とてもゆっくり過ごせるような日々ではありません。

<img width="70%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3597730/c1da4fd7-6bba-9e03-4a74-fd67632c338d.jpeg">

加えて、私は今月から新生活を送るために、新居に引っ越しをします。

今月といってもあと10日余り。。。
本当に新生活ができるのでしょうか。不安でたまりません。
そんな状況にパートナーも**イライラ**しています。

<img width="70%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3597730/b449ec10-815e-3ab9-e1c9-802369deac21.jpeg">

<font color="red"><strong>「仕事？勉強？そんな言い訳聞きたくな～い！！！」</strong></font>

## そうだToDoリストを作ろう
新生活の何が不安かというと、**今までと同等以上の生活を送ることができるか分からない**という点にあると思います。

ということでパートナーと**共同編集ができるToDoリスト**を作って**必要なものを洗い出す**ことにしました。

***

### LINEのノート機能案：×
まずは**LINEのノート機能**を利用してToDoリストを作成しました。

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3597730/3f890485-97da-a4e8-5568-6d4dff2750bb.jpeg">

ただ、作成してから気づいたのですが、**LINEのノート機能には共同編集機能がありませんでした。**

ということで**LINEのノート機能案はボツです。**

***

### Googleスプレッドシート案：△
次に共同編集ができるGoogleスプレッドシートを利用してToDoリストを作成しました。

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3597730/be877897-23cb-45a9-ba92-767d1e77aae1.jpeg">

ただ、PC画面では問題ないのですが、スマホ画面で編集しようとすると操作性が悪く、**イライラを解消するためのToDoリストに対してイライラするという本末転倒な状況になってしまいました。**
ということで**Googleスプレッドシート案もボツです。**

***

### AppSheet案：○
そして、Googleの**AppSheet**というサービスでToDoリストを作成してみました。

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3597730/8861a039-0791-6b43-340d-99891cb84381.jpeg">

以下のサイトを参考にして簡単に作成することができました。

Googleスプレッドシートをインポートするだけで、それなりのUI画面ができてきます。

さらに、AppSheetの編集画面で**直感的**にアプリ画面のUIをいじることができます。
初学者にとっては**直観的**に操作できることは大きなメリットです。

https://www.yoshidumi.co.jp/collaboration-lab/appsheet-tasks

ということで、めでたしめでたし。
**-完-**

**とはなりませんでした。**

## イライラは解消されなかった
上記でToDoリストがAppSheetでできました。
ただ実際、何を購入するかは事前に**調査**をする必要があります。

**「その調査をする時間がないんじゃんか。。。」**
ということに気が付きました。
<img width="70%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3597730/be3b08a7-a4f9-a88c-b854-2aeeb3ec03d1.jpeg">


## じゃあAIに提案してもらえばいいんじゃね？
ということで、**何を購入すればよいか、AIに提案しもらえば**貴重な時間を消費せずに、新生活の準備を進められるのではないかと考えました。

## 追加内容
以下の[Dify](https://cloud.dify.ai/apps)のワークフロー機能を使って、ToDoリストに追加した項目について、具体的に何を購入したらよいかを提案してもらえるようにしました。

<img width="100%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3597730/548b166b-cb58-124c-c861-5dfc70b152b3.png">

`Tempreture`というパラメータは0～1で設定可能ですが、
* 高いほど多様な出力
* 低いほど一貫性のある出力
が得られました。

結果として、デフォルトの`0.7`がちょうど良さそうでした。

<img width="100%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3597730/79c592ea-dec1-6d49-8360-a351820d6012.png">

### 具体的なフロー
* AppSheet(ToDoリスト)に新規項目追加
　↓
* AppSheetに紐づいたGoogle Apps Scriptを実行させ、Difyを呼び出し
　↓
* Difyで新規項目について何を購入すればよいか回答を生成させる
　↓
* 生成した回答をAppSheet(ToDoリスト)に反映させる

<img width="100%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3597730/dbe28309-6b15-7722-e7ea-05a780c53d7e.jpeg">

### Difyの設定
Difyのワークフローは以下の記事を参考に作成しました。

https://qiita.com/minorun365/items/4c5dba1de7977c386249

今回LLMは `Gemini 1.5 Pro`を選択し、LLMのプロンプトは以下のように設定しました。

```:プロンプト
## 命令
あなたは「引っ越しのプロ」です。
ユーザーは"{{#1721071307658.Input#}}"の購入を検討しています。
以下の条件を参考に、具体的にどのような物を購入すればよいか結果だけをシンプルに200文字以内でアドバイスしてください。

## 条件
・20代夫婦
・1LDK
・北欧好き
・ミニマリスト
・節約志向
```

### Google Apps Scriptの設定
Google Apps ScriptのコードおよびAppSheetとの連携は以下の記事を参考にしました。

https://qiita.com/cog1t0/items/5ad8da7a09ac2d29ab2a#gas%E3%81%AE%E6%BA%96%E5%82%99

ここまで読んでいただけた方ならお分かりかもしれませんが、他の方のプロダクトを参考にしまくって**何一つ自分で作っていません。**
[OSS(オープン・ソース・ソフトウェア)](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%BC%E3%83%97%E3%83%B3%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2)文化のありがたみをひしひしと感じています。

#### 完成したソースコード
<details><summary>←の三角マークを押すと確認することができます。</summary>

```js:Google Apps Scriptコード
function sendChildrenQuestion() {
  // スプレッドシートのIDは、スプレッドシートのURLに含まれています。
  // 例えば、URLが https://docs.google.com/spreadsheets/d/abcd1234efgh5678ijkl90mnopqrstuv の場合、
  // abcd1234efgh5678ijkl90mnopqrstuv がスプレッドシートのIDです
  const spreadsheetId = 'YOUR_SPREADSHEET_ID';  // TODO スプレッドシートのIDを書き換え
  const sheetName = 'シート1';

  const spreadsheet = SpreadsheetApp.openById(spreadsheetId);
  const sheet = spreadsheet.getSheetByName(sheetName);

  // B列のデータをすべて取得
  const data = sheet.getRange('B:B').getValues();

  // 最後の非空の行を特定
  let lastRow = data.length;
  while (lastRow > 0 && data[lastRow - 1][0] === '') {
    lastRow--;
  }

  // 最後の非空の行の質問を取得
  const question = sheet.getRange('B' + lastRow).getValue();

  const url = 'https://api.dify.ai/v1/workflows/run';  // リクエストを送るURL

  // リクエストのヘッダー情報
  const headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer {YOUR_DIFY_API_KEY}'  // TODO DifyワークフローのAPIキーで書き換え
  };

  // リクエストのボディデータ
  const payload = {
    'inputs': {Input:question},  // TODO Difyのワークフローの開始で設定した変数名
    'response_mode': 'blocking',
    'user': 'user123'
  };

  // オプションの設定
  const options = {
    'method': 'post',
    'headers': headers,
    'payload': JSON.stringify(payload),
    'muteHttpExceptions': true  // これをtrueにすると、エラーが発生しても例外がスローされません
  };

  // HTTP POSTリクエストの送信
  try {
    const response = UrlFetchApp.fetch(url, options);
    // Logger.log(response.getContentText());  // レスポンスの内容をログに出力
    const jsonResponse = response.getContentText()

    // JSONをオブジェクトに変換
    const responseObject = JSON.parse(jsonResponse);

    // 必要なデータを取得
    const taskId = responseObject.task_id;
    const workflowRunId = responseObject.workflow_run_id;
    const responseData = responseObject.data;
    const status = responseData.status;
    const outputs = responseData.outputs;
    const answer = outputs.text;

    // ログに出力
    Logger.log('Task ID: ' + taskId);
    Logger.log('Workflow Run ID: ' + workflowRunId);
    Logger.log('Status: ' + status);
    Logger.log('Outputs Text: ' + answer);

    // G列に返却された値を書き込み
    sheet.getRange('G' + lastRow).setValue(answer);
  } catch (e) {
    Logger.log('Error: ' + e.message);  // エラーメッセージをログに出力
  }

  // LINE Notify用の処理を追加
  const lineNotifyToken = 'YOUR_LINE_NOTIFY_TOKEN'; // TODO: LINE Notifyのトークンを設定してください
  const lineNotifyUrl = 'https://notify-api.line.me/api/notify';
  
  // B列（質問）とG列（回答）の最新の内容を取得
  const latestQuestion = sheet.getRange('B' + lastRow).getValue();
  const latestAnswer = sheet.getRange('G' + lastRow).getValue();
  
  // LINE Notifyに送信するメッセージを作成
  const message = '\n【' + latestQuestion + '】 が追加されました！' + '\n\n【AI提案】\n' + latestAnswer + '\n"ご自身のAppSheetの共有リンク"';
  
  const lineNotifyOptions = {
    'method': 'post',
    'headers': {
      'Authorization': 'Bearer ' + lineNotifyToken,
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    'payload': 'message=' + encodeURIComponent(message)
  };
  
  // LINE Notifyに通知を送信
  try {
    const lineResponse = UrlFetchApp.fetch(lineNotifyUrl, lineNotifyOptions);
    Logger.log('LINE Notify Response: ' + lineResponse.getContentText());
  } catch (error) {
    Logger.log('LINE Notify Error: ' + error.message);
  }
}
```
</details>

でも流用ばかりしていては芸がないので、**AppSheet(ToDoリスト)に新規項目が追加されたときにLINE Notifyに通知を送る**コードを忍ばせてみました。

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3597730/91131ee9-6747-55d9-47ac-cd1b716c1042.jpeg">

これで**自分**と**パートナー**と**LINE Notify**のグループLINEを作成し、どちらかがAppSheet(ToDoリスト)を更新すればLINEで通知を確認できるようになりました。

また、LLMからの提案内容についても **【AI提案】** という形で一目で確認できるようにしました。

## デモ

https://x.com/vanchan2625/status/1813528382229106960

## パートナーからの反応
<img width="70%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3597730/21fc8f79-156c-b72c-7c80-03d00e870865.jpeg">

シンプルですが、反応があるのはうれしいですね。

## 今後の展望
現状は、AppSheet(ToDoリスト)に新規項目を追加した際に、その項目名をそのままLLMに入力して、何を買うべきか回答を生成させています。

ですので、 **ハルシネーション** と呼ばれる誤った情報を出力する場合があります。

https://atmarkit.itmedia.co.jp/ait/articles/2303/30/news027.html

それを解決するためには **RAG(Retrieval-Augmented Generation：検索拡張生成)** の仕組みを活用することが有効です。

https://atmarkit.itmedia.co.jp/ait/articles/2403/13/news035.html

今後はより質の高いToDoリストに昇華させるためにも、以下のように入力文をいったんGoogleなどに検索をかけ、**入力文とWeb検索結果を併用してLLMから回答を得る仕組み**に修正していきたいと考えています。

<img width="100%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3597730/a58a9e57-ab08-fe60-9af1-e41b1b5aa832.jpeg">

LLMのプロンプトは以下のように修正します。
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3597730/7bba2d5b-cfd3-28d7-9dfe-3a2627726eb1.jpeg">

そして、Difyには**DALL-E 3**のモジュールがあることも発見しました。
入力文から生成した画像をAppSheetの各項目のサムネイルとして利用できないかも検討していきたいと考えています。

### いったん、Dify上だけで動作確認

https://x.com/vanchan2625/status/1813531677983166844

## これまで作製したプロダクトのご紹介
https://qiita.com/vanchan2625/items/3445b546e44649742c71

https://qiita.com/vanchan2625/items/3892184ef8b37bf40ce9

https://qiita.com/vanchan2625/items/00b9caeb1dadd36d6f5e