LLM でヒューマンインザループなニュースレコメンドアプリを作成する

## はじめに
佐藤([@rayuron](https://x.com/rayuron))です。皆さんは有益情報のキャッチアップをどのようにしていますか？Slack で RSS feed を流している方も多いと思いますが、情報量が多くなり結局自分に合った情報をキャッチアップするのに苦労することがあると思います。業務でレコメンド AI を作成していることもあり、今回は Slack で自分に合った有益情報をレコメンドするアプリをご紹介します。

## 背景

このアプリを作成した背景を簡単に説明すると以下の通りです。⭐️が今回お話しする内容です。

| 日付         | イベント                                                                                                 |
|--------------|--------------------------------------------------------------------------------------------------------|
| 2023/12/01  | 最新技術のキャッチアップが面倒という課題感を持つ                                                         |
| 2023/12/22  | RSS の更新をトリガーとしてニュースを Slack に配信するアプリを開発                  |
| 2024/03/12  | [今日から始めるChatGPT+Zapierで雑パーソナライズ情報収集](https://tech.layerx.co.jp/entry/2024/03/12/182848)の記事を見かけて「これは良い！」と思う |
| 2024/03/13  | アプリを Zapier に移行          |
| 2024/04/01  | ⭐ レコメンドを強化する機能追加を考える                                           |


### 課題と解決策
これまでも ChatGPT を用いて興味のありそうなニュースを Slack に配信するアプリを運用していましたが「**プロンプトエンジニアリングの作業負担が大きい**」という課題がありました。ユーザーの興味をプロンプトに入れてニュース記事をレコメンドしてもらいますが、ユーザーの興味が変化したり新しい概念が出てきた時には手動で興味のプロンプトを調整しており、時間と労力がかかっていました。


そこで、解決策として過去にレコメンドしたニュースに対する「**ユーザーのフィードバックを取得して次のレコメンドに活用する**」ことを考えました。ユーザーのフィードバックとして、ユーザーの面倒さが少ない Slack メッセージへの絵文字リアクション機能を使用します。

<img width="500" alt="Slackメッセージ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518616/d1049fdb-c72a-b37c-df5e-da00f5da9ce7.png">

▼ フィードバックには以下のルールを設けます。
- Slack に配信されたニュースレコメンドが良いと思った時 → ⭕ スタンプを押す
- Slack に配信されたニュースレコメンドが良くないと思った時 →  ❌ スタンプを押す

## アーキテクチャ
上記を実現するために3つのワークフロー(Zap)を作成し、以下の画像のような仕組みを構築します。

<img width="700" alt="アーキテクチャ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518616/bd9b5ad9-a0ac-231b-6359-9bae89a428d9.png">

### 1. ニュースレコメンド
この Zap ではニュースとユーザーの興味の類似度を計算しレコメンドするニュースを決定します。
1. トリガー: RSS feed の更新
2. 記事の情報を整形
3. 興味テーブルからユーザーの興味を取得
4. ChatGPT がユーザーの興味と記事の関連度を計算
5. 関連度が閾値以上の時にレコメンド内容をレコメンドテーブルに保存する

▼ 関連度の計算には以下のプロンプトを使用します。
```markdown
ユーザーが興味のある記事とない記事を判別したいです。【ユーザーの興味】と【記事】の内容から、ユーザーにとって記事が有益となる確率を 0 ~ 1 の間で出力してください。【ユーザーの興味】は {記事のジャンル: 興味度合い} の辞書型で格納しています。興味度合いは数値で、高ければ高いほどそのジャンルについてユーザーは興味があります。逆に低ければ低いほど興味がないジャンルとなります。

【ユーザーの興味】
{'AI': 1 , '推薦': 3, 'レコメンド': 2, 映画: -1, ...} 

【記事】
Title: {記事のタイトル}
Description: {記事の説明}
```

### 2. ユーザー興味識別器
この Zap では絵文字リアクションをトリガーにユーザーの興味を更新します。
1. トリガー: 特定チャンネル内で Slack メッセージに対する ⭕️ or ❌ のリアクション
2. ChatGPT でメッセージのジャンルを3つ取得
3. リアクションに応じてユーザーの興味テーブルの更新

▼ ユーザーの興味テーブルは以下の形式で保存します。

<img width="500" alt="興味テーブル" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518616/4af96de0-833c-bfa7-2cb3-cd09318481c8.png">


### 3. Slack でニュースの定期配信
この Zap ではレコメンドテーブルの情報をまとめて定期配信します。元々 RSS feed が更新される度にメッセージを送信していたのですが、通知の頻度が多くユーザー体験が悪かったため、まとめて定期配信することにしました。
1. トリガー: 毎日 12:00
2. 「**1. ニュースレコメンド**」で保存したレコメンドテーブルのデータを取得し Slack に配信


## 結果

レコメンドが自分にとって嬉しいかどうかの0,1のアノテーションを行い評価指標を時系列で計算しました。配信しなかったニュースに関してもデータが残っているので嬉しいかどうかのアノテーションを行いました。

▼ 配信とユーザーの反応パターンは以下の4つに分けられます。
| 配信/反応                  | 嬉しい                         | 嬉しくない                         |
|----------------------------|-------------------------------|-------------------------------------|
| 配信された                | 真陽性: 配信されて嬉しい            | 偽陽性: 配信されて嬉しくない             |
| 配信されなかった           | 偽陰性: 配信されていれば嬉しい       | 真陰性: 配信されておらず嬉しくない       |


上記に基づいて Precision, Recall, F1 Score を計算し時系列でグラフに描画しました。
- 2024/3/29 まで: アップデート前の仕組み
- 2024/3/29 ~ 2024/04/08: 開発期間
- 2024/04/09 から: アップデート後の仕組み

<img width="700" alt="結果" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518616/473ae244-2748-3502-7bef-d9c308990565.png">

アップデート直後は情報がまだ十分にフィルタリングされていないため、Recall が高く、土日はニュースが少ないので指標がブレやすいですが、全ての指標が時間経過で改善していることが分かります。

## おわりに
今回は Slack メッセージへのリアクションをフィードバックとして、ヒューマンインザループなニュースレコメンドアプリを作成する方法についてお話ししました。類似度の計算方法を ChatGPT に任せていたり、フィードバックの仕組みは配信されたメッセージだけに適用されるためフィルターバブルに陥りやすい、などの問題はありますが、この仕組みは比較的簡単に作成できるので気になる方は是非試してみてください。