Databricksで作成したLLM QA Chatbot をSlackと連携してみた


# はじめに

@taka_yayoiさんが作成された「[大規模言語モデルを活用したDatabricksのQ&A bot構築のウォークスルー](https://qiita.com/taka_yayoi/items/c8c612b18ebc9f5cae8c)」 を参考にDatabricks上でQA Chatbotを作成し、そのEndpointとSlackを繋げて、SlackからQAを受け付けられるようにしてみました。

https://qiita.com/taka_yayoi/items/43302d79369a36b66ee4

# 前提要件

- 上記Qiitaを参考にDatabricks上で Q&A botが動いている。(Model Servingが起動している）
- Slack APIを使ってSlack APPを作成できる権限を持っている
- pythonを動かす環境がある。（デモ環境であればPCでOKです。本番利用の場合はVMなど用意）

# 1. Databricks Model Serving上でQA受付できることを確認

まずはちゃんとModel Servingが機能しているか確認します。起動しているモデルサービングを表示し右上の**エンドポイントにクエリ**をクリックし、以下のようにJSON形式でクエリーを投げて反応を確認します。

```json
{"inputs" : 
  {"question" : ["レイクハウスとは？"]}
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/960c20a7-b073-2778-ef22-0ccf9174926b.png)

上記の通りレスポンスが返ってくればOKです。

後ほど使うので以下の情報を取得＆メモしておいてください。

- DATABRICKS_URL : `モデルサービングのURL`
- DATABRICKS_TOKEN : `Personal Token`


# 2. Slack APP の作成

## 1) Slack APPを作成しワークスペースに追加

こちらの[Slack APP](https://api.slack.com/apps)より、新規APPを作成します。
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/411ea036-f0ef-0666-5dd4-3c4bd49583cb.png' width='400'>

## 2) Scope 追加

次にAPPが利用できる権限などを追加していきます。左サイドバーの「**OAuth & Permissions**」をクリックし、「**Bot Token Scopes**」セクションまで下にスクロールします。「**Add an OAuth Scope**」をクリックします。

私が追加したスコープはこちらです。（正直言うと何が正解の権限かわからなかったです。要件によってこちらの権限を追加したり削除したり変更ください）
- app_mentions:read
- im:history
- im:read
- im:write

## 3) Event設定

次にEvent設定をして、どのようなEvent発生時に反応するのかを指定します。今回はmentionで呼ばれた時に反応するようにします。

左側のサイドバーから **Event Subscriptions** にアクセスして、機能を有効にしてください。 **Subscribe to Bot Events** 配下で、ボットが受け取れる　イベントを追加することができます。

今回追加したイベントはこちらです。
- app_mention
- message.im

<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/3b916043-8cbf-f09d-50d9-d06c3ba73859.png' width='600'>

## 4) Socket Modeを有効にする

左側のサイドバーから **Socket Mode** にアクセスして、機能を有効にしてください。Tokenが生成されるのでコピーしてメモしておいてください。

<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/ae2b50f7-1390-20d9-ed7c-cadb28afcacf.png' width='600'>


## 5) Install App 

左側のサイドバーから **Install App** にアクセスして、[Install to Workspace]をクリックします。
以下のようなメッセージが出ますので、許可します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/4c3c2eee-01d8-67b7-bc9c-76a6b4cf21c7.png" width='400'>

再度、Install Appメニューに行くと、**Bot User OAuth Token**が表示されるのでメモしておきます。

## 6) Show Tabを有効にする

AppにMessageタブが表示されるように有効化しておきます。
左側のサイドバーから **App Home** にアクセスして以下のように、**Messages Tab**を有効にし、**チェックボックス**にチェックを入れておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/06072dd0-daf1-2067-cf0f-528547532385.png)


## 7) Tokenをメモする

この後、python コードからアクセスするためのトークンなどを取得します。今回利用するのは以下の２つです。
- SLACK_BOT_TOKEN : `5)Install APPで生成されたトークン`
- SLACK_APP_TOKEN : `4)のSocket Modeを有効化した際に生成されたトークン`



# 3. Slack ClientからAppを追加する

SlackのAppを追加メニューから、作成したAppを追加します。またSlack ChannelにAppを追加しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/b872ee1b-59b5-f1c8-aa45-9c30edf2f017.png)

これで箱は出来ました。ただこれだけではChatとして何も動作しませんので、次のコードをどこかで実行します。

# 4. Slack Bolt for Pythonを利用

今回はPythonでコードを書きたかったので、調べたところSlack Boltというのが沢山の類似記事などもあり良さそうでしたので、使わせてもらうことにしました。初心者向けの入門ガイドもあるので実行してみると理解しやすいです。

https://slack.dev/bolt-python/ja-jp/tutorial/getting-started#listening-and-responding-to-a-message

### プロジェクトを作成し、必要なライブラリーをインストールしておきます。

```
mkdir databricks-bot-app
cd databricks-bot-app
python -m venv .venv
source .venv/bin/activate
pip install slack_bolt
```
*他にも不足しているライブラリがあれば随時 pip installでインストールしてください。

### Tokenなどを環境変数に設定

Tokenなどはコードに含めると危険なのでOSの環境変数に設定しておきます。

```
export DATABRICKS_URL=xxxxxxxx
export DATABRICKS_TOKEN=xxxxxxxx
export SLACK_APP_TOKEN=xxxxxxx
export SLACK_BOT_TOKEN=xxxxxxx
```

### 今回はこちらのようなコードを作成しました。

```python:app.py
import os
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
from PIL import Image
import base64, io, requests, json
import numpy as np
import pandas as pd

## Databricks endpoint 連携の設定
def create_tf_serving_json(data):
    return {
        "inputs": {name: data[name].tolist() for name in data.keys()}
        if isinstance(data, dict)
        else data.tolist()
    }

def score_model(question):
  token = os.environ.get("DATABRICKS_TOKEN")
  url = os.environ.get("DATABRICKS_URL")

  headers = {'Authorization': f'Bearer {token}', "Content-Type": "application/json",}
  dataset = pd.DataFrame({'question':[question]})
  ds_dict = (
        {"dataframe_split": dataset.to_dict(orient="split")}
        if isinstance(dataset, pd.DataFrame)
        else create_tf_serving_json(dataset)
    )
  data_json = json.dumps(ds_dict, allow_nan=True)
  response = requests.request(method="POST", headers=headers, url=url, data=data_json)
  if response.status_code != 200:
    raise Exception( f"Request failed with status {response.status_code}, {response.text}")
  return response.json()

# ここから Slack 設定
# トークンとソケットモードハンドラーを使ってアプリを初期化します
app = App(token=os.environ.get("SLACK_BOT_TOKEN"))

# mentionされた場合メッセージを読み取ります
@app.event("app_mention")
def respond_to_mention(body, say):
    channel_id = body["event"]["channel"]
    user_id = body["event"]["user"]
    question = body["event"]["text"]

    response = score_model(question)
    answer = response['predictions'][0]["answer"]
    source = response['predictions'][0]["source"]

    # Send a preparatory message
    #say("回答中・・・, 少々お待ちください.", channel=channel_id)

    # Craft your response message
    message = f"{answer}.  \n Source: {source}"

    # Send the response message
    say(message, channel=channel_id)

# アプリを起動します
if __name__ == "__main__":
    SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()
```

## コードを実行します

利用する際はコードを実行しっぱなしにしないとダメなので、本番利用の場合はVMなどを立てて実行するといいでしょう。

```
python app.py
```
以下のようなメッセージが表示されればOK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/7a3144f6-7f6c-98e3-162a-7fdd75162232.png)

# 試してみよう！

それでは実際にSlackのChannelでDatabricksに関する質問を投げてみましょう。今回のBotはDatabricksのBlogや記事などから収集した情報を元に回答してくれるので、データソースなども返してくれます。

今回のbotは、mentionをトリガーに反応してくれるので、質問を投げる際にはmentionが必要です。botを追加したChannelに移動して、いくつか質問を投げてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/c441efa0-ac14-5ce0-5353-11e31382ebde.png)

Model Servingが起動していないと最初反応が遅いですが、起動していると数秒で返してくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/2cd495d4-273e-ef50-5f4f-7464eba70d7f.png)

同様の仕組みを利用すれば、Databricks上のデータを使った様々なチャットシステムを構築できそうですね。