日報フィードバックアプリをDifyで構築してみた！

こんにちは、ふくちです。

アドベントカレンダー Dify編2日目の記事です！投稿が遅れてしまって申し訳ありません🙇‍♂️

https://qiita.com/advent-calendar/2024/dify

初めてDifyを触っているので大したプロダクトではないですが、自分が欲しいものを作ってみました！

# 今回作りたかったもの
一言で言えば、**日報フィードバックアプリ**です。
その日やったことを記録すれば、AIがフィードバックしてくれるようなイメージですね。
そしてそれをNotionにまとめて、後で自分の成長を振り返られるようにしたい。

このアプリを作る目的としては、自身がより技術力を高めるための習慣付けをすることです。
要するに、**エンジニアとしてより成長するための土台を作りたい**、ということです。

しかし、ただ闇雲に、あるいは漠然と行動だけしていても、あまり成長できません。
成長のためには、**「目標・行動・振り返り・改善」** が必要だと私は考えています。

ただ、この中の「振り返り・改善」って1人でやるのは結構難しいですよね。
客観的な評価や、それをベースにした改善策というのは1人ではなかなか思いつきませんし、時間もかかるので後回しにしてしまいがちです。

そこで、その日の自身の行動を振り返って、それを分析・添削・保存してくれるようなアプリを開発してみよう！となった次第です。

# 構成
このアプリのワークフローとしては以下の通りです。
1. 日報を入力する
1. 日報データをJSON形式に変換する
1. データをNotonに転送する

この1~3をDify上で実現してみます。

# 1.Notionの準備
まずはNotionで日報保存用のデータベースを作成します。
このアプリにおけるNotionに到達するまでの流れとしては、以下の感じです。

Difyに日報送信 → Dify上でAIがフィードバックコメント作成 → **DifyからNotionに送信**

ということで、Dify上でどんな形に整形するかが鍵になりそうです。

また、後から振り返った時の見やすさを考えた結果、以下のようなプロパティでやってみることにしました。

- 日付
- 合否判定
- フィードバック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/bbd6b5be-6a80-b1d8-aaa1-39b2c99f1873.png)

合否判定は、その日の中で自己研鑽できたかを評価してもらいます。具体的な方が良さそうなので、ここでは「ブログ作成時間が2時間以上あったか」を基準とします。

※PoCなので、ひとまずこれで進めることにしました。
データが足りなかったり粒度が荒すぎたりした場合は後々修正しようと思います。

# 2.Notion Integrationの作成
DifyとNotionを接続するためには、このNotion Integrationというのが必要だそうです。

:::note
Notion Integrationとは…
外部ツール(Dify, Jira, Google Drive, Slackなど)をNotionに接続して**ワークフローを構築・強化できるようになる**、というもの

https://www.notion.com/ja/integrations

:::

**1.[Notion Integration管理ページ]()にアクセス**

**2.「新しいインテグレーションの作成」をクリックする**

**3.以下の感じで設定する**
- 任意のインテグレーション名をつける(ここではDifyと命名)
- 関連ワークスペース：先ほど作成したDBが存在するワークスペースを選択
- 種類：内部(Internal)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/858fdcb8-8727-f079-5954-d447ff4f8f81.png)

4.インテグレーションの設定を開き、「内部インテグレーションシークレット」をコピー
→コピーする際は「表示」をクリックすることで、「コピー」の選択肢が出てきました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/310550fd-cf54-0ad4-fb6b-43782027bd99.png)

ちなみにですが、設定ページの下部で権限設定も行えるようです。
デフォルト設定はこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/68b3e559-8bde-211e-74f8-eb1c23fd8d8b.png)

**5.Notionに戻って、以下の操作を行う**
- 作成したデータベースページを開く
- 右上の3点メニューの中にある「接続先」を開く
- 先ほど作成したインテグレーションを選択する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/a3fcb68f-4f27-afd2-859d-3c6ed00019d2.png)

すると、こんな感じで接続が確保され、編集権限が表示されるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/ecd05e7b-5224-30aa-f314-3c6798326dcb.png)

**6.NotionデータベースのURLから、データベースIDを抜き出しておく**
先程同様に3点メニューを開き、今度は「リンク/をコピー」を選択します。
→すると、 `https://notion.so/XXXXXXXX?v` 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/67ebfb92-1587-e29c-7401-108983a2a590.png)


# 3.Difyでワークフローを構築
ようやくDifyの構築です。
今回はアプリケーションを新規作成していきます。タイプはワークフローです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/81e6df32-248f-fa22-2292-3c9c49e4e308.png)

最終的に完成したワークフローはこんな感じです！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/0394f1dc-fbea-6cc2-f5d2-cfcd8606bc3b.png)

試しに実行してみると、無事成功し、Notionに登録されていました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/77b7e998-d1d9-9879-7989-aced6bf9ffe7.png)
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/6b13204c-c74b-ab9b-d9ef-a8aa4e6cab05.png)

まぁ、ブログの内容なんてどこにも書いてないのに妄想で「内容がわかりやすく」なんて言われているので使い物にはなっていないのですが…ひとまずやりたいことはできた！ということで。

## 各ノードの工夫ポイント
工夫というか、ほぼエラー対処…という感じですが、上から順番にいきましょう。

### LLMのプロンプト
最初のLLMでは、日報に対してフィードバックを作成しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/0745c416-93eb-411e-e41b-1206960dfd93.png)

プロンプトは最終的にこんな感じになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/2df42182-31b6-c794-46e5-cb5c06a19ac1.png)

コンテキストとして日報の入力を明示的に渡しているのは、こうしないと全然正しく評価してくれなかったからです。
「ブログ作成時間が2時間以上あれば合格」という条件にしており、日報で「ブログ作成時間3時間」としているにも関わらず、判定が不合格にされてばかりでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/f2f94fda-2001-c7e9-9750-9ba244771b8d.png)

これを解消するために、コンテキストとしてプロンプトの中に直接日報入力データを渡すようにしたところ、正しく判定してくれるようになりました。

またこの時、後でNotionに渡しやすくするため、JSON形式にしてあります。
Difyではモデル選択画面でレスポンスフォーマットをJSONオブジェクトなどで指定できるということで、これは非常に使いやすいなと思いました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/23d3aace-6bb6-1ca5-3531-f8e0afdad212.png)

***

### コードノード
LLMが出力したJSONをパースしてオブジェクト形式に変換します。
その後、変数として `judgement` と `feedback` を出力し、後続の処理に渡しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/7723b647-fb7b-b57d-8f7f-f6f61e65492a.png)
***
### 日時取得ノード
続いて、Notionに格納するための日付を取得します。
日報なので、あまり細かい時間の計測は不要と判断し、年月日だけ取得しています。日本時間で取得してくれるのもありがたかったです！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/2900baec-1b45-437e-e8f8-51d5460b6e15.png)
***
### 出力形式をテンプレート化
ここで、Notionが受け取るためのフォーマットに整形します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/b9dd7db0-9f95-e567-f69f-b808f320dbf9.png)

フォーマットとしては以下の感じになりました。
{{ }} で囲われているのは、Dify上の入力変数を渡しているためです。
```template.json
{
  "parent": { "database_id": "{{ db_id }}" },
  "properties": {
    "Date": {
      "title": [
        {
          "text": {
            "content": "{{ time }}"
          }
        }
      ]
    },
    "judgement": {
      "rich_text": [
        {
          "text": {
            "content": "{{ judgement }}"
          }
        }
      ]
    },
    "feedback": {
      "rich_text": [
        {
          "text": {
            "content": "{{ feedback }}"
          }
        }
      ]
    }
  }
}
```

このNotionのフォーマットで本当に悪戦苦闘しました…

***
### LLMノードその2とHTTPリクエスト
続いて、再びLLMノードを通った後、HTTPリクエストを実行してNotionへのアクセス・データ保存を試みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/ea519e3f-c776-da25-7dd3-674e23305c86.png)

ここのLLMの役割としては、HTTPリクエストボディにある文字列をエスケープ処理すること。
そして最終的に、望んだ形のリクエストボディだけを返してもらっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/bdda2be1-991a-e65e-f6e3-95b500bfaca3.png)

その値を持ったうえで、HTTPリクエストを送信します。
ここで指定したURLは、Notion APIでデータを保存する際に使用するAPIのようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/d4abeeb3-f5c4-98c6-a0ab-fa675be3e60a.png)

また、認証APIキーには、環境変数として設定したNotionインテグレーションのシークレットキーを指定しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/264968aa-4ca5-58db-7887-de0aec81ebfe.png)

なぜこのようにしているかというと、テンプレートで整形したフォーマットをそのままHTTPリクエストに渡すと、エラーが出たためです。
一部だけお見せするとこんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/e648eed2-cb2e-dd0b-7d93-04d821939528.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/f7b0f19d-f924-2761-1ca0-61a118bdc5fa.png)

これらを調べたり、Claudeに原因を聞いたりした結果、どうやらエンコーディングでの問題が起こっているとのことでした。

追加で調査していると、エスケープ処理をした上でHTTPリクエストに送信している方がいたので見様見真似で試したところ、上手くいったという感じでした。

正直何が起こってるのか理解できていないので、ここは別途キャッチアップしておきます…

***

ということで、各ノードでやっていることのご紹介でした！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/0394f1dc-fbea-6cc2-f5d2-cfcd8606bc3b.png)

### 簡単なエラーハンドリング
最後はステータスコードを確認して、OKかそうじゃないかを確認しています。
エラーだった場合はリクエストのbodyが返されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/161d34dc-357f-d298-6c9b-b8536e3377cd.png)
***
### 環境変数
最後に、設定した環境変数について簡単にご紹介しておきます。
Notionとの連携に必要不可欠なので…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/da876cbb-1118-323c-4ce7-5fee04268db0.png)

| 変数名 | 値 | タイプ |
|:-|:-|:-|
| NOTION_VERSION | 2022-06-28 | String |
| NOTION_DATABASE_ID | ※<データベースID> | Secret |
| NOTION_INTEGRATION_SECRET_KEY | <シークレットキー> | Secret |
| END | Notionへの登録が完了しました！ | String |

:::note warn
NotionのデータベースIDについてですが、**設定する際にハイフンで区切る**必要があります。

というのも、NotionデータベースのページからデータベースIDを取得してそのまま環境変数の値に格納していたのですが、ステータスコード400が返ってきてしまい、データを保存できていませんでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3616210/d04ed04c-078b-81b4-e0b7-628ff9557390.png)


この時、「**データベースIDはUUIDの形である必要がある**」と指摘されていました。
それに倣って、「**8桁-4桁-4桁-4桁-12桁** 」の形に変換して格納したところ、成功しました！

ここは注意しておかないとドツボにハマりそうなので気をつけましょう！
:::

***

# まとめ
初めてDify使ってみたのですが、直感的に扱えて非常に面白かったです！
ただ、GUIでワークフロー組むだけなんだから簡単だろうと思っていたら、構築に丸2日かかりました…想像以上に難しかったです。

構築に時間がかかりすぎて投稿日(12/2)も超過してしまい申し訳ありませんでしたmm

なにはともあれ、なんとかやりたかったことが実現できました！
しかしまだまだ理想には程遠いので、この経験を土台にして少しずつDifyでの開発に慣れていきたいと思います！

***
参考にさせていただいたブログのリンクをこちらに掲載しておきます。

https://powerplatformnikki.com/difyrecepiautomation2/

https://www.6666666.jp/blog/post/20210617/

https://zenn.dev/yu_9/articles/3145f964a58996

https://qiita.com/wwwy/items/7c13cda8100e403fea48