ChatGPT Slack Bot (スレッド対応版)を作成しました (python)

こんにちは。chatGPTすごいですね。

Slackを使われている方なら、Slackに組み合わせて使いたいと思いますよね。

https://qiita.com/tomoasleep/items/030e42b5e3853c609cf9

こんな感じです。

chatGPTに対応するSlack Botはいろいろなところで実装例も示されていますが、Slack Botのサンプルのデフォルトがjavascriptのため、javascriptで実装したものが多いようです。
当記事では、Slack Botをpythonで実装した例をご紹介します。

このコード例では、Botをローカルホストで実行させることができます。
Slack側のBotの導入設定は、マニュアル通りに実施してください。


## Bot コード例　（python）

```python:chatbot.py
import os
from dotenv import load_dotenv
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import openai

load_dotenv()
app = App(token=os.environ["SLACK_BOT_TOKEN"])
openai.api_key = os.environ["OPENAI_API_KEY"]

BOTUID = "<@HOGEHOGE>"  # 設定したbotのIDに変更する

prev_messages = []
system_message = "You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible."
MAX_PREV_MESSAGES = 20

def generate_prompt(messages, input_message):
    messages.append({"role": "user", "content": input_message})
    print("prompt: " + str(messages))
    return messages

def get_response(messages):
    response = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=messages)
    res = response["choices"][0]["message"]["content"]
    print("ChatGPT: " + res)
    return res

def save_messages(thread_ts, input_message, text):
    global prev_messages
    prev_messages.append({"thread_ts": thread_ts, "role": "user", "content": input_message})
    prev_messages.append({"thread_ts": thread_ts, "role": "assistant", "content": text})
    if len(prev_messages) > MAX_PREV_MESSAGES:
        prev_messages.pop(0)

def add_prev_messages(messages, thread_ts):
    global prev_messages
    for mes in prev_messages:
        if mes["thread_ts"] == thread_ts:
            messages.append( {"role": mes["role"], "content": mes["content"] })
    return messages
 
@app.event("app_mention")
def on_mention(event, say):
    global prev_messages, system_message
    input_message = event["text"]
    thread_ts = event.get("thread_ts") or None
    channel = event["channel"]

    # スレッド内でメンション付きで呼ばれた場合は無視する
    if thread_ts != None:
        return

    # メンションで付けられたslackボット名を削除する
    input_message = input_message.replace(BOTUID, "")
    
    # システムプロンプト(性格付け)を付与
    messages = [{"role": "system", "content": system_message}]
    
    prompt = generate_prompt(messages, input_message)
    
    response_text = get_response(prompt)

    # スレッドを作って、スレッド内に応答する
    response = app.client.conversations_replies(channel=channel, ts=event["ts"])
    thread_ts = response["messages"][0]["ts"]
    say(text=response_text, thread_ts=thread_ts, channel=channel)

    save_messages(thread_ts, input_message, response_text)

@app.event("message")
def on_message(event, say):
    global prev_messages

    # スレッド内での処理の場合のみ実行
    if "thread_ts" in event:
        messages = []
        input_message = event["text"]
        channel = event["channel"]
        thread_ts = event["thread_ts"]

        # メンションで付けられたslackボット名を削除する
        input_message = input_message.replace(BOTUID, "")

        messages = add_prev_messages(messages, thread_ts)

        # messages長=0、すなわち既存のchatGPT生成スレッドではない場合は何もしない
        if len(messages) == 0:
            return
        
        prompt = generate_prompt(messages, input_message)

        response_text = get_response(prompt)

        say(text=response_text, thread_ts=thread_ts, channel=channel)

        save_messages(thread_ts, input_message, response_text)

if __name__ == "__main__":
    handler = SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
    handler.start()
```

やり取りの履歴は20件まで記憶しています。ただし、すべてのスレッドで共有しているため、複数並行してやり取りしているとすぐに履歴を消費してしまうでしょう。その場合は20をもっと大きな数字にしてください。

## 参考文献
なお、コード作成にあたっては下記を参考にさせていただきました。ありがとうございます。

https://qiita.com/ina111/items/0591ab8714139e67b19c

BotをAWS上で動かす場合の例

https://qiita.com/Hiromu25/items/527b49eb3e5541ae7326