Databricksのデータに SQLDatabaseChainを使ってSlackからアクセスしてみた


# はじめに

こちらの@taka_yayoi さんの記事にもありますが、LangchainのSQLDatabaseChainを使ってDatabricksの特定のデータに自然言語でアクセス出来るようになりました。

https://qiita.com/taka_yayoi/items/164d195efb5e2625b832

今回、こちらの仕組みをSlackから簡単にアクセスできるようにまとめましたので、皆様も是非トライしてみてください。

# 必要な環境
- Databricks SQLWarehouse　　
- OpenAI API (Secret Keyが必要です）
- Slack App
- EC2 or Lamdaなど pythonコードが動作する環境。デモの場合はPCでも可能です。

# コードやSlackAppのデプロイファイルなど
今回必要なコード類やこちらのgitに公開しております。まずはお手元にクローンしてください。
https://github.com/maroon-spec/slack_sqldb


# 設定手順

### 1. SlackAppの作成

今回はレポジトリの中にSlackAppのデプロイファイルを用意してありますので、そちらを利用します。
```yaml:slack_app.yaml
display_information:
  name: Databricks-sql-bot
features:
  bot_user:
    display_name: databricks-sql
    always_online: true
oauth_config:
  scopes:
    bot:
      - chat:write
      - channels:history
      - groups:history
      - mpim:history
      - im:history
      - app_mentions:read
      - reactions:write
      - users:read
settings:
  event_subscriptions:
    bot_events:
      - app_mention
      - message.channels
      - message.groups
  org_deploy_enabled: false
  socket_mode_enabled: true
  token_rotation_enabled: false
```

[SlackApp]に移動して、**Create New App**にて、**From an app manifest**を選択し、上記のyamlをコピーして貼り付けます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/bd93a210-f991-7e60-8530-89d3ad6585d7.png" width="400" >

Install Appに移動して、対象のワークスペースとAppを紐付けます。(以下はイメージ)
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/3aef2eaa-9daa-a631-e3bb-b4bbfa324b9c.png' width="400">

App Tokenを作成します。このTokenは後ほど利用します。
**Basic information** -> **App-Levels Token**.  **Scope: connections:write**
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/71858bce-32c2-df53-d65c-bde6d6c26c0d.png" width="400">

最後にBot Tokenを取得します。
**Install App** -> **Bot User OAuth Token**取得

### 2. Slack上でAppが追加されているので、対象のChannelにBotを追加する.
**チャンネル詳細** -> **インテグレーション** -> **App追加**
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/952758ae-fe82-a8a9-913c-36c958e7ad3f.png" width="400"> 

### 3. Pythonコード実行

最後に　Pythonコードを実行します。今回はシンプルにPC上で実行します。
（他にLambda とAPI　Gatewayを組み合わせたり、Publicアクセス可能なEC2上などで動作させると本番でも利用できます）

まずはvenvを作成し、いくつかのライブラリーをインストールします。
```
$ python -m venv .venv
$ source .venv/bin/activate
$ pip install -r requirements.txt
```

次にOS上に環境変数として以下をセットします。
```:OS環境変数
export DATABRICKS_HOST=""    # Databricks Workspace URL(https://は除く)
export DATABRICKS_TOKEN=""   # Personal Access Token
export DATABRICKS_WAREHOUSE=""   # SQL Warehouse ID (最後のID部分のみ）
export OPENAI_API_KEY=""     # OpenAI APIのSecret Key
export SLACK_BOT_TOKEN=""    # 先ほどのSlack BOT Token
export SLACK_APP_TOKEN=""    # 先ほどのSlack App Token 
```

実行するコードはこちらです。
コード内のカタログやスキーマ、テーブルを変更すると、対象となるテーブルを変更することができます。

```python:app.py
import os
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
from langchain import OpenAI, SQLDatabase, SQLDatabaseChain

#######################################################
## 今回検索対象とするカタログとスキーマを選択します。   #######
#######################################################

catalog = "samples"
schema = "nyctaxi" 
tables  = ["trips"] 
host = os.environ.get("DATABRICKS_HOST") 
warehouse_id = os.environ.get("DATABRICKS_WAREHOUSE") 
api_token = os.environ.get("DATABRICKS_TOKEN")

# LLM
llm = OpenAI(temperature=0, verbose=True)

## Langchain SQLDatabaseChain
db = SQLDatabase.from_databricks(catalog=catalog, 
                                 schema=schema, 
                                 include_tables=tables, 
                                 host = host, 
                                 warehouse_id=warehouse_id, 
                                 api_token=api_token)

db_chain = SQLDatabaseChain.from_llm(llm, db, verbose=True)

#######################################################
# ここから 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 = db_chain.run(question)

    # Craft your response message
    message = f"Answer: {response}"

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

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

実行します。
```
$ python app.py
```

設定は以上です。

# 試してみる

今回は、MentionでBotに問い合わせると、指定したテーブルに対して、自然言語で問い合わせできます。

例えば、どんなデータがあるか聞いてみます。（英語で帰ってきたので言語を指定しました）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/b3c1bb21-7b7f-671e-1a2e-347c15c8a620.png)

次に、fare_amountというカラムがあったので、「運賃の平均金額は？」　と聞いてみると自動的に fare_amount と認識して平均値を出してくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/83c7dba8-2ed9-fc6c-13c8-325163724fc6.png)

もう少し難しい、２つのカラムの時間差の平均時間を聞いてみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/952f90e9-913c-5c48-6ca3-272c087254d5.png)

実行しているシェルを見てみると、裏ではSQLに変換して正しい問い合わせをしてくれてます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/f4fea1c3-efef-c5aa-f8db-7456c86e715f.png)

Slack以外にも色々なインターフェースと組み合わせて利用できます。
対象となるテーブルはUnity CatalogのACLでRead権限のみを許可した方がいいです。MergeやDropもできてしまいます。DatabricksのService PrincipalなどでRead権限などを許可されたトークンを発行すると良いと思います。