データと生成AIで週末体験をデザインするMVPの取り組み

こんにちは。NECの田辺です。
この記事は、:christmas_tree:[NEC デジタルテクノロジー開発研究所 Advent Calendar 2023](https://qiita.com/advent-calendar/2023/nec-dt-labs):christmas_tree:の13日目の記事になります。

# はじめに
 **スマートウォッチ**のデータ と **検索**したデータ を **生成AI** に入れて
 「あなたの健康状態は{xxx}という状況なので、週末は{yyy}した方が良いよ」
とレコメンドしてくれるbotを作りました。

![スクリーンショット 2023-12-09 14.21.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121442/fe3f5a4b-67ce-651e-2de8-53f6591a5f3a.png)

> 「bot」とは、ロボット（Robot）の略語で、自動化されたタスクを実行するためのソフトウェアプログラムのことを指します。

まずは所属組織の紹介からはじめます。

## デジタルテクノロジー開発研究所におけるリサーチエンジニアリンググループの取り組み
研究者と事業開発を構想するビジネスデザイナーとエンジニアが一体となった組織の中で、研究所全体のエンジニアリング強化に向けて様々な施策を実行する組織です。その中でも、研究所技術を活用したコンセプトを検証する際に、 **MVP(Minimum Viable Product)** を活用したアプローチをとる専門チームに所属しています。本稿にあるような生成AIを活用する案件も多くあり、社外にもポジションを公開しているため、少しでも興味のある方はゼヒお声がけください！

[【募集中】テックリードまたはテックリード候補（新規プロダクト/サービス開発）](https://jobs.nec-careers.com/u/job.phtml?job_code=3670)
[【募集中】AIエンジニア（新規プロダクト/サービス開発）](https://jobs.nec-careers.com/u/job.phtml?job_code=3669)
[【募集中】UXデザイナー（新規事業開発）](https://jobs.nec-careers.com/u/job.phtml?job_code=3643)

## 本稿の位置づけ
実際の業務内容は公開前の事案となるため、今回は **「お家でMVP」** した内容を記事にしています。ユースケースはカジュアルでありながら、差し支えのない範囲で生成AIの活用のポイントがわかるようにまとめました。

# いざ、MVP。そしてbot開発へ。
リーンスタートアップにおけるMVPは **「学びを得るための小規模で素早い方法」** と謡われています。
MVPのPはプロダクト（製品）の印象が強いのですが、本来は形に拘る必要などなく、仮説を検証するためにコトに向かえば良いのです。
以降、方法論としてのMVPになぞらえてbot開発の道筋を示していきます。

## ペルソナを設定し深掘りしていく
以下のようなペルソナ（自分）を設定して深堀していきました。

![スクリーンショット 2023-12-09 15.53.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121442/5977babc-3210-aa62-9901-6154bff714ba.png)

上記を受けて、どんなことができるだろうかと検討していきます。
- 普段から[スマートウォッチ(fitbit)](https://qiita.com/tkeneix/items/4f9ed11978707ff5993f)をつけているので蓄えたデータはある
- 生成AIにjsonのままデータを放り込んで解析、レポーティングしてくれたら何か示唆を得られそう
- 季節に合わせた情報はリアルタイム検索することで得られそう
- 対象のコンディションをデータで捉え、咀嚼し、季節感を踏まえた週末の過ごし方を提案しくれるbotがいたら、良き行動指針になるのでは

このような検討を経て、生成AIを活用することにしました。

## 生成AIをミニマムに検証するためのポイント
外部データを用いた生成AIの活用を探る場合、手始めにRAG(Retrieval Augmented Generation)という方式を適用することが一般的になっており、本稿でもこのベストプラクティスに倣います。
ここでは簡単にRAGの３つの要素を以下の表に示します。

| 要素 | 役割 |
|---|---|
| Retrieval | 情報の取得や検索のプロセスを指します。データベース、文書、ウェブページなどから情報を取り出す行為です。|
| Augmented | 拡張や補完を意味します。既存のものを向上させたり、強化したりするプロセスです。|
| Generation | 新しいテキストやコンテンツを生成するプロセスです。|

> OpenAIからもcookbookと題して様々な処理方式、実装が公開されています
> [Question_answering_using_a_search_API](https://github.com/openai/openai-cookbook/blob/main/examples/Question_answering_using_a_search_API.ipynb)

Retrievalは生成AIが保有するデータの他に、外部のデータを活用するプロセスのことで、
本稿では「スマートウォッチで蓄積したデータ」や「検索サイトから季節感を抽出したテキストデータ」の活用に相当します。
一般にRAGを紹介するサイトでは、外部のデータをベクトル形式にエンコードする手法も掲載されていることが多いのですが、
Augmentedを凝った形で行わない限りは必ずしも必要な対応ではありません。
 **MVPは必要でありながら、失敗を許容できるほどの、小さい一歩を踏み出すことが有用です。**


生成AIの活用において、ミニマムに外部データを取り扱う例を以下に示します。

![スクリーンショット 2023-12-03 15.28.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121442/867a7374-439c-a019-cd1c-4c3d5ddb18c9.png)

生成AIが受付可能なプロンプトの領域に外部データを入れてしまえば良い、ということですね。

## アーキテクチャ
生成AI活用のポイントが見えてきたところで、次にどんなシステムであれば、実現できるかを考えていきます。

![スクリーンショット 2023-12-09 14.59.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121442/6b88eba7-1b12-61b1-3d5f-08dbeb735c14.png)

利用する外部データを以下の表に示します。
fitbitは[API](https://dev.fitbit.com/build/reference/web-api/developer-guide/authorization/)が利用できますし、langchainの[tools](https://api.python.langchain.com/en/stable/api_reference.html#module-langchain.tools)には様々な検索エンジンとの連携手段が用意されているため、これらをありがたく活用します。

| 外部データ | 入手元 | 用途 |
|---|---|---|
| 消費カロリー | fitbit | 運動不足の状況を把握 |
| [安静時心拍数](https://help.fitbit.com/articles/ja/Help_article/1565.htm) | fitbit | 緊張やストレスの状況を把握 | 
| [睡眠の質](https://help.fitbit.com/articles/ja/Help_article/2439.htm?Highlight=%E7%9D%A1%E7%9C%A0) | fitbit | よく寝れているかどうかを把握 | 
| 季節性を表すテキスト | 検索エンジン | 生成AIに今の季節性、おすすめの週末を伝えるため | 

出力する内容は以下の表に示します。
生成AIによるレコメンドの他、概観をつかむためのグラフもつけることにしました。

| 出力データ | 生成元 | 用途 |
|---|---|---|
| 先週/今週を比較した定性レポート | 生成AI | 消費カロリーと心拍数、睡眠の質から得られた健康状態をテキストで把握するため |
| 先週/今週を比較した定量レポート | pandas/matplotlib | 時系列データをプロットしたグラフで概観を把握するため(睡眠の質は除く) |
| オススメの週末体験 | 生成AI | 健康状態と季節性を表すテキストに基づいて、週末体験をレコメンドするため

ユーザーへの通知方法はコミュニケーションサービスのDiscordとし、スマホで通知内容を受け取れるようにしました。

## Discordにレコメンドされたサンプル
以上の検討を踏まえて、bot開発を終えました。
実際にレコメンドされたサンプルを以下に示します。

![スクリーンショット 2023-12-11 16.54.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121442/e9f65fba-68b9-d27f-400c-6a7e1da49a1a.png)
![スクリーンショット 2023-12-10 12.18.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121442/9e0efde9-99a0-a124-d09b-335903736d06.png)

先週に比べて心拍数や睡眠の質の変化はわずかで、消費カロリーが減少していることがわかりますね。
![スクリーンショット 2023-12-11 16.55.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121442/0e8b84ab-10d7-7d27-f765-f6c5590d9afd.png)

外に出てウォーキングをした方が良いこと、秋を楽しむためのヒントについてもレコメンドしてくれました。
季節性の情報に、土地勘や最近の興味を反映できるようになると、「{xxx}で行われる{yyy}のイベントに行ったほうが良い」などとレコメンドしてくれるようになるかもしれません。

さて、この後は「懸念は解消できたのか、行動が変わったか、その先に得たかった状態に至れているか」をトレースし評価していきたいのですが、しばらく試行してから振り返ることとしましょう。

是非、みなさんもMVPを方法論として活用し、新しい体験を創られてみてはいかがでしょうか。


# 備忘録
ここから先は主要箇所の技術的な事柄について、備忘録として記載します。

## fitbit apiの利用

### パッケージインストール
```bash
pip install fitbit
```
### クライアントの初期化
```python
fitbit_client = fitbit.Fitbit(app_mng_id, client_secret,
                             access_token=access_token,
                             refresh_token=refresh_token
                             )
```
> secret, token類は[この辺](https://dev.fitbit.com/build/reference/web-api/developer-guide/authorization/)を見ながら取得。


## 生成AIの利用

### パッケージインストール
```bash
pip install langchain
```

### クライアントの初期化
```python
llm = ChatOpenAI(model_name='gpt-3.5-turbo-1106', openai_api_key='<api_key>', temperature=0.7)
```

### ヘルスケアの専門家によるレポーティング（プロンプト）

```python
def report_fitbit_data(llm, this_week_heart_rate, last_week_heart_rate, this_week_sleep_quality, last_week_sleep_quality):
    system_template = "あなたは、ヘルスケア領域に詳しい専門家です。質問に対して詳細に分析、レポートをします。"
    external_data_template  = (
        "以下に外部データを示します。\n"
        "今週の安静時心拍数: {this_week_heart_rate}\n"
        "先週の安静時心拍数: {last_week_heart_rate}\n"
        "今週の睡眠の質: {this_week_sleep_quality}\n"
        "先週の睡眠の質: {last_week_sleep_quality}\n"
    )
    user_template = (
        "外部データに基づき、活動の傾向やストレスレベルについて、次の条件を考慮したレポートを生成してください。\n"
        "条件：\n"
        "- 先週と比べて消費カロリーがどのように変化したかを明記する。\n"
        "- 先週と比べて安静時心拍数がどのように変化したかを明記する。\n"
        "- 安静時心拍数は3ポイント以上の急な変化はケアする必要があるが、それ以外は特別視しない。\n"
        "- 睡眠の質は10%以上の急な変化はケアする必要があるが、それ以外は特別視しない。\n"
        "- 消費カロリーが減少している場合は、行動的な対処を提案する。\n"
        "- 結論として、['休息', '適度なリフレッシュ', 'アクティブな外出']のうちいずれか1つの行動を勧める。\n"
        "レポート:\n"
    )

    chat_prompt = ChatPromptTemplate.from_messages([
        ("system", system_template),
        ("system", external_data_template),        
        ("user", user_template),
    ])
    return llm.invoke(
        chat_prompt.format_messages(
            input_language="Japanese",
            output_language="Japanese",
            this_week_heart_rate=this_week_heart_rate,
            last_week_heart_rate=last_week_heart_rate,
            this_week_sleep_quality=this_week_sleep_quality,
            last_week_sleep_quality=last_week_sleep_quality
        )).content
```


### 週末体験コーディネーターによるレコメンド（プロンプト）
```python
def advice_weekend(llm, report_content, seasonality_content):
    system_template = "あなたは、健康的な週末体験を提案するコーディネーターです。質問に対して端的に答えます。"
    external_data_template  = (
        "以下に外部データを示します。\n"
        "季節性: {seasonality_content}\n"
        "特徴: {report_content}\n"
    )
    user_template = (
        "季節性と特徴を示す人の翌日からの週末の過ごし方を条件を踏まえて１つ提案してください。:\n"
        "条件：\n"
        "- 先週からの今週の変化を受けて、勧める内容を検討する。\n"
        "- 行動的な内容、リフレッシュを勧める場合は、季節性に沿った内容を勧める。\n"
        "提案：\n"   
    )
    chat_prompt = ChatPromptTemplate.from_messages([
        ("system", system_template),
        ("system", external_data_template),        
        ("user", user_template),
    ])
    return llm.invoke(
        chat_prompt.format_messages(
            input_language="Japanese",
            output_language="Japanese",
            seasonality_content=seasonality_content,
            report_content=report_content,
        )).content
```

## 検索エンジンの利用

```python
# 検索エンジンのインスタンスを取得してsearchへ設定
seasonality_content = search.run(f"{datetime.now().strftime('%m')}月 季節 おすすめの週末")
```