Difyを使ってSlack Chatbotを作る

今更感はありますが、Difyを初めて使ってみたのでその手順を共有します。

今回はlocalhost上にDifyとBoltを立てて、Slack上で動作するChatbotを簡単に作成します。API経由でDifyと連携することで、後からChatbotの挙動を柔軟に変更できる構成を目指します。

## 前提条件

* Docker環境構築済み
* python環境構築済み
* Slackアカウント作成し、ワークスペース作成済み

## Difyの環境構築

Difyのリポジトリをクローンし、Docker Composeを使って起動します。手順は[Difyの公式ドキュメント](https://docs.dify.ai/ja-jp/getting-started/install-self-hosted/docker-compose)を参考にしています。

```sh copy
git clone https://github.com/langgenius/dify.git
cd dify/docker
cp .env.example .env
docker compose up -d
```

これでDifyが起動します。`http://localhost/install` にアクセスして初期設定を行ってください。

[http://localhost/install](http://localhost/install)

![localhost_install.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1264617/33369ab2-d22e-c3cd-f0f1-5ba2f1c3b63c.png)

画面の指示に従ってアカウントを作成し、ログインすると以下の画面が表示されます。

![localhost_signin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1264617/7f02a8b5-e49d-4484-843a-220bafdf5210.png)

今回はLLMとしてGoogle Geminiを使用します。事前に以下のGemini APIのページからAPIキーを発行しておいてください。

https://ai.google.dev/gemini-api/docs/api-key?hl=ja

右上のログインユーザー名をクリックし、「設定」>「モデルプロバイダー」を選択すると、以下のダイアログが表示されます。

![localhost_signin (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1264617/5c3ddd4a-f34c-1563-c025-3460747bd428.png)

この画面でGeminiを選択し、取得したAPIキーを登録します。セットアップが完了すると以下のようになります。

![localhost_apps1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1264617/5673d0d6-cefc-d0c2-ea36-c5c4b9ed999d.png)

## Chatbotの作成
テンプレートを使用せず、「最初から作成」をクリックします。

![localhost_apps2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1264617/bb09fade-c0d4-752f-dd01-ff00b770edb0.png)

「チャットボット」を選択し、アプリ名と説明を入力して「作成」をクリックします。

作成後、以下の画面が表示されます。

![localhost_apps3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1264617/3aa2a5d5-39c2-87c0-f342-5b33233508e4.png)


この画面でプロンプトの設定などができますが、今回はとりあえず動作確認を優先するため、細かい設定は省略します。後からワークフローを設定することでカスタマイズが可能です。

また、この管理者ページから、作成したChatbotのログや監視画面で、消費トークン数やリクエストの詳細を確認できます。

DifyのAPIシークレットキーを作成します。画像の赤枠で示した「APIアクセス」をクリックし、画面遷移後右上の「APIキー」ボタンをクリックします。
![localhost_app_f4f6c140-66fe-426b-8091-92dacedaf8bb_develop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1264617/6809929b-b936-23a9-7ac1-0eb8588031ee.png)

「新しいシークレットキーを作成」をクリックし、表示されたキーをメモしておきます。このシークレットキーは後ほどSlack Boltの環境変数設定で使用します。

今回は使用しませんが、「公開する」>「アプリを実行」をクリックすると、Chat画面が表示され、簡単に試すことができます。
![localhost_apps5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1264617/174b3af1-8c9a-f816-461e-7c779016ed54.png)


![localhost_chat_mTpsHm5Nh7USU8gX (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1264617/ce6243b6-364a-bb16-760f-7d5e1c1f97cf.png)


## Slack Appの設定
まずはSlack Appを作成します。https://api.slack.com/apps からワークスペースに新しいAppを作成してください。

Slack Appの作成方法の詳細は、[Slack Bolt入門ガイド](https://tools.slack.dev/bolt-python/ja-jp/getting-started/)を参照してください。

Socket Modeを使用するために、以下の権限が必要です。

#### Bot Token Scopesの追加
- OAuth & Permissions -> Bot Token Scopes -> Add an OAuth Scope
  - chat:write
  - app_mentions:read

#### App-Level Tokensの作成
- Basic Information -> App-Level Tokens -> Generate Token and Scopes
  - Token Nameに任意のトークン名を入力
  - Add Scopeでconnections:writeを追加（ソケットモードに必要）
  - Generateボタンでアプリレベルトークンを生成し、メモしておく

#### Socket Modeの有効化
- Socket Mode -> Connect using Socket Mode -> Enable Socket Modeをオン

#### イベント取得の設定
- Event Subscriptions -> Enable Eventsをオン
  - Subscribe -> Add Bot User Eventでapp_mentionを追加

#### アプリのワークスペースへのインストールとBot Tokenの取得
- OAuth & Permissions -> Install App to Workspaceでアプリをインストール
  - アクセス権限リクエスト画面が表示されるので、許可するをクリック
  - Bot User OAuth Access Tokenをメモしておく

ここまでできたら、あとはslack_boltでbotを起動するだけです。今回はローカルの環境で実行します。

## Slack Bolt
`.env`ファイルを作成し、以下の環境変数を設定します。
```sh
DIFY_API_KEY=<Difyで発行したAPIキー>
SLACK_BOT_TOKEN=<xoxbから始まるBot Token>
SLACK_APP_TOKEN=<xappから始まるApp-Level Token>
```

次に以下のPythonスクリプトを作成します。

```sh copy
import os
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import requests
from dotenv import load_dotenv

load_dotenv()

app = App(token=os.environ.get("SLACK_BOT_TOKEN"))

# ボットのユーザーIDを取得
bot_user_id = app.client.auth_test()["user_id"]

@app.event("app_mention")
def handle_app_mention(event, say):
    print(event)
    if event and 'text' in event:
        dify_api_key = os.environ["DIFY_API_KEY"]
        url = 'http://localhost/v1/chat-messages'  # Dify API endpoint
        user = event['user']
        query = event['text'].replace(f"<@{bot_user_id}>", "").strip() # メンション部分を削除
        headers = {
            'Authorization': f'Bearer {dify_api_key}',
            'Content-Type': 'application/json'
        }
        data = {
            'query': query,
            'response_mode': 'blocking',
            'user': user,
            'conversation_id': '', # 必要に応じて設定
            'inputs': {}
        }
        response = requests.post(url, headers=headers, json=data)
        response_data = response.json()
        if 'answer' in response_data:
            say(response_data['answer'])
        else:
            say(f"Dify APIからの予期しないレスポンス: {response_data}")
    else:
        say("メッセージの内容を取得できませんでした。")

if __name__ == "__main__":
    SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()
```
スクリプトを実行後、Slackのチャンネルにアプリを追加し、メンションを付けて質問を送信すると、チャンネルに返答が返ってきます。
![スクリーンショット 2024-12-10 2.40.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1264617/553eb26b-189b-9a50-9fef-5aa7eb158b29.png)

Slackの設定などを調べつつ、短時間で作成・利用開始できました！手軽に始められるだけでなく、まだまだ使えていない機能があるので、今後も色々と試してみたいと思います。