LoginSignup
1
1

More than 1 year has passed since last update.

SlackでChatGPTボットアプリをつくる

Last updated at Posted at 2023-05-06

はじめに

現時点でまだSlackのChatGPT公式アプリはリリースされておりませんが
OpenAIからAPIは提供されていますのでこれを使ってSlack上でChatGPTとやりとりができるボットアプリの作成方法を共有したいと思います。

今回はPythonのFlaskを利用した例となります。
OpenAIの提供しているPython公式SDKを利用します。

処理仕様

ユーザーからAIへの質問の入力を受ける方法はアプリへのメンションイベントとしてEvent Subscriptionsのapp_mentionを拾うことにします。

処理設計の中で重要なポイントとしてはデータベースを極力利用せずに会話履歴を保持する方法です。
ChatGPTのWeb画面ではトピックごとにNew chatを立てていくと思いますがこれをどうやってSlackのインターフェースの中で実現していくのかがポイントとなります。

これについてはNew chatごとにスレッドを立ててもらう方法を取りました。
スレッドの内容を取得できるAPIエンドポイントconversations.repliesで会話履歴を構築できます。

以上を考慮した処理フローは以下のようになりました。

処理内容

必要なPythonライブラリは以下となります。

pip install requests
pip install openai
import requests
import openai

メンションイベント受信

example
@app.route("/openai_mention", methods=["POST"])
def openai_mention():

    # リトライを無視する対応
    if "X-Slack-Retry-Num" in request.headers and request.headers["X-Slack-Retry-Reason"] == "http_timeout":
        print('ignore retry!!!')
        return {'text': 'ignore retry!!!'}

    request_json = request.json
    print(request_json)

    # jsonから必要なパラメータを取得する
    text = request_json["event"]["text"]
    user_id = request_json["event"]["user"]
    channel_id = request_json["event"]["channel"]
    thread_ts = ''
    url = ''

    # スレッド内のメンションの場合はスレッドIDを取得する
    if "thread_ts" in request_json["event"]:
        thread_ts = request_json["event"]["thread_ts"]

Event SubscriptionsのRequest URLは/openai_mentionをコールするエンドポイントを指定してあげてください。

Event Subscriptionsでは3秒以内にレスポンスしないとリトライする仕様があります。
サーバーレス環境などでの運用ではアプリがコールドスタートした場合に返答にどうしてもタイムラグが発生してしまいます。
これを食らってしまうと重複処理が起こるのでリトライのリクエストをチェックし無視する対策を入れています。

イベントのパラメータからどのチャンネルchannel_idで誰user_idが何を発言textをどのスレッドthread_tsでしたのかなどの情報を取得します。

スレッド情報取得

example

APP_USER_ID_RAW = '@UXXXXXXXXXX'
APP_USER_ID = f"<@{APP_USER_ID_RAW}>"

payload = {'token':APP_TOKEN, 'channel':channel, 'ts': thread_ts}
res = post("https://slack.com/api/conversations.replies", data=payload)
res_json = res.json()
messages = res_json['messages']
for message in messages:
    # アプリへのメンションの場合
    if APP_USER_ID in message['text']:
        # 制御文字を抜く
        input = message['text'].replace(APP_USER_ID, '')
        q_message = {"role": "user", "content": input}
        prompt.append(q_message)
    # AIの回答の場合
    elif APP_USER_ID_RAW in message['user']:
        input = message['text']
        a_message = {"role": "assistant", "content": input}
        prompt.append(a_message)

チャンネルとスレッドの情報をパラメータとしてconversations.repliesを実行するとスレッド情報一覧が取得できます。
スレッドの内のメッセージを一つずつ走査して以下の処理を行います。

  • 自分の質問内容の文字列内にAPP_USER_IDと一致するものが存在しているかで判定しそれを除去する(これを'role': 'user'としている)
  • AIの返答についてはAPP_USER_ID_RAWuserと一致しているかで判定する(これを'role': 'assistant'としている)

APP_USER_ID_RAWにはアプリのメンバーIDを設定します。
APP_USER_IDにはメンションされた際にメッセージの中に含まれるアプリのメンバーID文字列を格納しておきます。

なぜこのような制御をしてるかというとスレッド内に関係ないメッセージが紛れ込んでくる可能性がありますので明確にAIへの質問と回答だけを正しくパラメータにセットするためです。

プロンプトパラメータサンプル

example
{'role': 'user', 'content': ' 令和元年の西暦は?'}, 
{'role': 'assistant', 'content': '令和元年は、西暦2019年にあたります。'}, 
{'role': 'user', 'content': ' 平成は?'}, 
{'role': 'assistant', 'content': '平成元年は、西暦1989年にあたります。'}, 
{'role': 'user', 'content': ' 昭和は?'}

スレッド内容を解析後最終的にはこのようなパラメータが構築されることになります。
一番最後の部分が新しいAIへの質問内容となります。

ChatGPTプロンプト送信&回答をSlackに投稿

example
    result = openai.ChatCompletion.create(model=AI_ENGINE, messages=prompt)

    ai_response = result.choices[0].message.content
    print(ai_response)

    # メッセージ送信用のPOST
    payload = {'token':APP_TOKEN, 'channel':channel, 'text': ai_response}
    # スレッド内の場合はそこに打ち返すこと
    if thread_ts != '':
        payload['thread_ts'] = thread_ts

    res = requests.post("https://slack.com/api/chat.postMessage", data=payload)

AIから受け取った回答をchat.postMessageを指定のスレッドthread_tsに投稿します。
万が一thread_tsが未指定の場合はチャンネルにそのまま投稿しておくことにします。

その他

必要なBot Token Scopesは以下です。

  • app_mentions:read
  • channels:history
  • chat:write

最後に作成したアプリをチャンネルにジョインさせてあげます。
これでアプリにメンションするだけの非常にシンプルな使い方になります。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1