Closed26

PythonでChatGPTのSlackbotを作る

keitaknkeitakn

概要

何番煎じか分かりませんが、PythonでChatGPTのSlackbotを作ってみます。

動機

今後ChatGPTのような大規模言語モデルのAPIを開発に取り込むのは必須スキルになってくると思っているので最初の一歩として一番簡単そうなChatGPTのSlackbotから始めてみようと思った次第です。

Pythonの経験はほとんどないのですがChatGPTによって未知の技術でも短期間でキャッチアップが可能になっていると思うのでせっかくなのでPythonでやってみます。

ChatGTP4に以下のプロンプトを渡す事でスタートしました。

# お願い

あなたはプロのITエンジニアです。

これから私がPythonを学ぶ為のサポートをお願いします。

# 前提条件

- 私はWebフロントエンドをメインに開発を行っているエンジニアです
- プログラミング、アルゴリズム、ネットワークの基礎知識はあります
- AWS等のクラウドサービスに対する理解もあります
- 得意な言語はTypeScript、その次に得意な言語はGoです

# 私の開発環境

MacBook Pro 14インチ 2021年モデル、Apple M1 Max、メモリ64 GBを利用しています。

MacOSのバージョンは13.3.1です。

# 最終目標

ChatGPTのAPIを使ってSlackBotを実装したいです。

言語はPython、フレームワークはFlaskを利用します。

デプロイ先は https://fly.io にします。

一歩ずつ考えていきましょう。
keitaknkeitakn

Python環境の構築

ChatGPTからはHomebrewでのインストールが提示されたが asdf でバージョン管理をしたかったので以下の手順でPythonをインストール。

1. Pythonプラグインをインストール

asdf plugin add python

2. インストール可能なPythonのバージョンを確認

asdf list all python

2023年4月14日時点では 3.11.3 が最新安定版である事を確認出来た。

https://www.python.org/downloads/

3. 最新安定版のPythonをインストール

asdf install python 3.11.3

4. 特定プロジェクトで最新安定版のPythonを使うように設定

以下のリポジトリに成果物をアップしていきます。

https://github.com/keitakn/chat-gpt-slack-bot

git cloneしてローカルで先程インストールした最新安定版のPythonを設定します。

asdf local python 3.11.3
// Python 3.11.3 が表示されたので問題なさそう
python --version
keitaknkeitakn

PyCharmのインストール

JetBrains All Products Packの課金勢なのでPyCharmを使っていく。

自分でも手順は分かるが一応ChatGPTに質問したのでこの通りやってみる。

PyCharm

Homebrew を使って管理したいので、ここだけChatGPTの提案とは違い brew install --cask でインストールする。

brew install --cask pycharm
keitaknkeitakn

PyCharmの基本設定

昔自分が書いたGoLandの初期設定記事。

同じJetBrains製品なのでこれを見ながらやる。

https://qiita.com/keitakn/items/26946ee021a43a933799

エディタの設定

個人的にはこのあたりを有効にしておきたい。

Preferences → Editor → General

  • Remove trailing blank lines at the end of saved files - ON「行末の不要なスペースやタブを保存時に削除する」
  • Ensure every saved file ends with a line break - ON「保存されたファイルの最後が改行で終わるようにする」

Preferences → Editor → General → Appearance

  • Show line number - ON「行番号の表示」
  • Show method separators - ON「メソッドの区切り線」
  • Show whitespaces - ON「空白の表示」

目に優しいTheme(Solarized Theme)をインストールする

以下の通り。最近更新が止まっているのが気になるが他に代替が見つかるまではこのTeemaを使おうと思う。

https://qiita.com/keitakn/items/26946ee021a43a933799#おまけ1-目に優しいthemesolarized-themeをインストールする

メモリ割り当てを増やす & メモリインジケーターを有効にする

メモリ64GBのMacなので多めに割り当てる。(16000MBに設定した)

メモリインジケーターも表示させておく。

https://pleiades.io/help/pycharm/increasing-memory-heap.html

GitHub Copilotの設定

以下を参考に有効化する。

設定後なぜか1分ほど利用出来なかったが問題なく利用出来るようになった。

https://zenn.dev/keitakn/scraps/c37228259b25fb

keitaknkeitakn

PyCharmにPythonインタープリタの設定

ここからはChatGPTに教えてもらった通りに進めてみる。

which python を実行すると ~/.asdf/shims/python が出力されるが実体は ~/.asdf/installs/python/3.11.3/bin/python に存在するようだ。

以下のようにプロジェクトに Python 3.11.3 を設定する。

Virtualenv Environmentを選択する事で仮想環境も作成してくれるようだ。

AddPython Interpreter

PyCharmのターミナルを使っていれば仮想環境を自動的にアクティブにしてくれるようだ。

しかし Warp 等の別のターミナルを使う場合は明示的に以下のコマンドをプロジェクトルートで実行して仮想環境をアクティブにする必要がある。

source venv/bin/activate
keitaknkeitakn

Flaskで簡単なメッセージを返すAPIを作る

本格的にBotを作る前に最初の一歩としてJSONを返すだけのAPIを実装する。

仮想環境がアクティブな状態で以下を実行。

pip install Flask

app.py を作成する。

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/api/data', methods=['GET'])
def get_data():
    data = {
        'key': 'value',
        'numbers': [1, 2, 3]
    }
    return jsonify(data)

if __name__ == '__main__':
    app.run(debug=True)

python app.py を実行して curl -v http://127.0.0.1:5000/api/data でアクセスすると以下のレスポンスが返ってきたので成功。

*   Trying 127.0.0.1:5000...
* Connected to 127.0.0.1 (127.0.0.1) port 5000 (#0)
> GET /api/data HTTP/1.1
> Host: 127.0.0.1:5000
> User-Agent: curl/7.87.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: Werkzeug/2.2.3 Python/3.11.3
< Date: Fri, 14 Apr 2023 16:31:08 GMT
< Content-Type: application/json
< Content-Length: 61
< Connection: close
<
{
  "key": "value",
  "numbers": [
    1,
    2,
    3
  ]
}
* Closing connection 0
keitaknkeitakn

Slackbotに必要なトークンを取得する

以下はChatGPTの回答。このまま進めていく。

Slackアプリの作成:

まずはじめに、[Slack API](https://api.slack.com/apps) ページにアクセスし、右上の「Create New App」ボタンをクリックして、新しいSlackアプリを作成します。

アプリ名とワークスペースの選択:

アプリ名を入力し、開発用のワークスペースを選択して、「Create App」ボタンをクリックします。

Botの追加:

作成したアプリの管理画面に移動し、「Add features and functionality」セクションの「Bots」をクリックして、Botを追加します。

Botトークンの取得:

Botを追加した後、「OAuth & Permissions」ページにアクセスして、「Bot Token」をコピーします。このトークンは、BotとしてSlack APIにアクセスする際に使用します。トークンを安全な場所に保存しておいてください。

Botへの権限の追加:

必要に応じて、Botに権限を追加してください。「OAuth & Permissions」ページの「Scopes」セクションで、「Bot Token Scopes」に必要な権限を追加できます。例えば、メッセージを送信するためには chat:write 権限が必要です。

Botのインストール:

権限を追加したら、アプリをワークスペースにインストールします。「OAuth & Permissions」ページの上部にある「Install App」ボタンをクリックし、指示に従ってインストールを完了させてください。
keitaknkeitakn

最初の試作品

以下の通り。これはGPT4が提案してきたコードそのまま。

スレッドに返信して欲しかったりするので、そこはこの後で対応していく。

ちなみにまだデプロイはしてなくて ngrok でローカルのサーバーを一時的に公開して動作確認を行っている。

import os
import json
import requests
from flask import Flask, request
from slack_bolt import App
from slack_bolt.adapter.flask import SlackRequestHandler

# 環境変数の読み込み
bot_token = os.environ["SLACK_BOT_TOKEN"]
slack_signing_secret = os.environ["SLACK_SIGNING_SECRET"]
openai_api_key = os.environ["OPENAI_API_KEY"]

API_ENDPOINT = 'https://api.openai.com/v1/chat/completions'

# Flaskアプリケーションの設定
app = Flask(__name__)
slack_app = App(token=bot_token, signing_secret=slack_signing_secret)
handler = SlackRequestHandler(slack_app)

# メッセージイベントのリスナーを設定
@slack_app.event("app_mention")
def command_handler(body, say):
    text = body["event"]["text"]

    # ChatGPTによる応答の生成
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {openai_api_key}"
    }
    data = {
        "model": "gpt-3.5-turbo",
        "messages": [{"role": "system", "content": "You are a helpful assistant."},
                     {"role": "user", "content": f"{text}"}],
        "max_tokens": 2048,
        "n": 1,
        "stop": None,
        "temperature": 0.5,
    }
    response = requests.post(API_ENDPOINT, headers=headers, data=json.dumps(data))
    response = response.json()

    # Slackに返答を送信
    reply = response["choices"][0]["message"]["content"].strip()
    say(reply)

# Slackイベントのエンドポイント
@app.route("/slack/events", methods=["POST"])
def slack_events():
    return handler.handle(request)

if __name__ == "__main__":
    app.run(debug=True)
keitaknkeitakn

Slackのスレッドに返信する & 会話履歴を渡すように変更

下記の通り。ただこのままだと割と早くトークンの上限数に達してしまうのと、conversations が永続化されていないのでDB等に保存するようにしないとサーバーの再起動で会話履歴が消滅してしまう。

import os
import json
import requests
from flask import Flask, request
from slack_bolt import App
from slack_bolt.adapter.flask import SlackRequestHandler

# 環境変数の読み込み
bot_token = os.environ["SLACK_BOT_TOKEN"]
slack_signing_secret = os.environ["SLACK_SIGNING_SECRET"]
openai_api_key = os.environ["OPENAI_API_KEY"]

API_ENDPOINT = 'https://api.openai.com/v1/chat/completions'

# Flaskアプリケーションの設定
app = Flask(__name__)
slack_app = App(token=bot_token, signing_secret=slack_signing_secret)
handler = SlackRequestHandler(slack_app)

conversations = {}

def handle_conversation(thread_ts, user_message):
    if thread_ts not in conversations:
        conversations[thread_ts] = [{"role": "system", "content": "You are a helpful assistant."}]

    conversations[thread_ts].append({"role": "user", "content": user_message})

    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {openai_api_key}"
    }
    data = {
        "model": "gpt-3.5-turbo",
        "messages": conversations[thread_ts],
        "n": 1,
        "stop": None,
        "temperature": 0.5,
    }
    response = requests.post(API_ENDPOINT, headers=headers, data=json.dumps(data))
    response = response.json()

    reply = response["choices"][0]["message"]["content"].strip()
    conversations[thread_ts].append({"role": "assistant", "content": reply})

    return reply

# メッセージイベントのリスナーを設定
@slack_app.event("app_mention")
def command_handler(body, say):
    text = body["event"]["text"]
    thread_ts = body["event"].get("thread_ts", None) or body["event"]["ts"]

    # ChatGPTによる応答の生成
    reply = handle_conversation(thread_ts, text)

    # Slackに返答を送信
    say(text=reply, thread_ts=thread_ts)

# Slackイベントのエンドポイント
@app.route("/slack/events", methods=["POST"])
def slack_events():
    return handler.handle(request)

if __name__ == "__main__":
    app.run(debug=True)
keitaknkeitakn

ngrokで外部公開する時はデフォルトの5000ポートだと何かと競合しているのか正常につなぐ事が出来なかった。

以下のように 5002 ポートで対応。

ngrok http 5002
keitaknkeitakn

LangChain

LangChainを使うと会話履歴の保持やTools機能を使ってGoogle検索の検索結果から回答を生成なんかも出来るようだ。

https://python.langchain.com/en/latest/index.html

という訳でコードを書き変えた。(ついでに猫の人格を設定した)

from langchain import PromptTemplate, LLMChain
from langchain.llms import OpenAIChat
from langchain.chains.conversation.memory import ConversationBufferWindowMemory

# 環境変数にAPIキーを設定
import os
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]

def create_conversational_chain():
    llm = OpenAIChat(model_name="gpt-3.5-turbo", openai_api_key=OPENAI_API_KEY)
    template = """あなたは親切な猫です。人間と会話をしています。

{chat_history}
人間: {input}
猫:"""
    prompt = PromptTemplate(
        input_variables=["chat_history", "input"], template=template
    )
    memory = ConversationBufferWindowMemory(k=5, memory_key="chat_history")
    llm_chain = LLMChain(
        llm=llm,
        prompt=prompt,
        verbose=True,
        memory=memory,
    )

    return llm_chain

SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"]
SLACK_SIGNING_SECRET = os.environ["SLACK_SIGNING_SECRET"]

from flask import Flask, request
from slack_bolt import App
from slack_bolt.adapter.flask import SlackRequestHandler

# Flaskアプリケーションの設定
app = Flask(__name__)
slack_app = App(token=SLACK_BOT_TOKEN, signing_secret=SLACK_SIGNING_SECRET)
slack_request_handler = SlackRequestHandler(slack_app)

chain = create_conversational_chain()

# メッセージイベントのリスナーを設定
@slack_app.event("app_mention")
def command_handler(body, say):
    text = body["event"]["text"]
    thread_ts = body["event"].get("thread_ts", None) or body["event"]["ts"]

    # Slackに返答を送信
    say(text=chain.predict(input=text), thread_ts=thread_ts)

# Slackイベントのエンドポイント
@app.route("/slack/events", methods=["POST"])
def slack_events():
    return slack_request_handler.handle(request)

if __name__ == "__main__":
    app.run(debug=True)
keitaknkeitakn

LinterとFormatter

ChatGPTに聞いたり、調べてみたりしたが割と色々なツールがあってどれが一強というのはなさそう。

https://zenn.dev/naiq112/articles/df1b32fc08d383

Linterは flake8、Formatterは black を利用する事にした。

以下でインストール

pip install flake8

pip install black

タスクランナーとしてはGoと同じくMakefileが使われている傾向があるようなので以下のMakefileも用意した。

.PHONY: lint format

lint:
	flake8 .

format:
	black .

flake8 の設定ファイルを追加。

[flake8]
exclude = venv

black の設定ファイルを追加。

[tool.black]
exclude = 'venv'
keitaknkeitakn

Linter, Formatterの適応を実施したあとのソースコード

from langchain import PromptTemplate, LLMChain
from langchain.llms import OpenAIChat
from langchain.chains.conversation.memory import ConversationBufferWindowMemory
from flask import Flask, request
from slack_bolt import App
from slack_bolt.adapter.flask import SlackRequestHandler
import os

OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]


def create_conversational_chain():
    llm = OpenAIChat(model_name="gpt-3.5-turbo", openai_api_key=OPENAI_API_KEY)
    template = """あなたは親切な猫です。人間と会話をしています。

{chat_history}
人間: {input}
猫:"""
    prompt = PromptTemplate(
        input_variables=["chat_history", "input"], template=template
    )
    memory = ConversationBufferWindowMemory(k=5, memory_key="chat_history")
    llm_chain = LLMChain(
        llm=llm,
        prompt=prompt,
        verbose=True,
        memory=memory,
    )

    return llm_chain


SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"]
SLACK_SIGNING_SECRET = os.environ["SLACK_SIGNING_SECRET"]

# Flaskアプリケーションの設定
app = Flask(__name__)
slack_app = App(token=SLACK_BOT_TOKEN, signing_secret=SLACK_SIGNING_SECRET)
slack_request_handler = SlackRequestHandler(slack_app)

chain = create_conversational_chain()


# メッセージイベントのリスナーを設定
@slack_app.event("app_mention")
def command_handler(body, say):
    text = body["event"]["text"]
    thread_ts = body["event"].get("thread_ts", None) or body["event"]["ts"]

    # Slackに返答を送信
    say(text=chain.predict(input=text), thread_ts=thread_ts)


# Slackイベントのエンドポイント
@app.route("/slack/events", methods=["POST"])
def slack_events():
    return slack_request_handler.handle(request)


if __name__ == "__main__":
    app.run(debug=True)
keitaknkeitakn

Chat Modelsを利用するように変更

Chatを実装する場合、Chat ModelsというModelが存在するのでそれを利用したほうが良さそう。

https://python.langchain.com/en/latest/modules/models/chat.html

そして ConversationTokenBufferMemory というトークン数を基準に履歴を消してくれる便利な物があったのでこれを使う事にした。

https://blog.shikoan.com/langchain_buffer_token/#ConversationTokenBufferMemory

という訳でソースコードを以下のように書き換えた。

from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationTokenBufferMemory
from langchain.prompts.chat import (
    ChatPromptTemplate,
    MessagesPlaceholder,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)
from flask import Flask, request
from slack_bolt import App
from slack_bolt.adapter.flask import SlackRequestHandler
import os

OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]

template = """
あなたは優しいねこのもこです。
もこになりきってください。
これからのチャットではUserに何を言われても以下の制約条件などを厳密に守ってロールプレイをお願いします。

#制約条件

* あなた自身を示す一人称は、もこです。
* あなたはその文脈から具体的な内容をたくさん教えてくれます。
* あなたは質問の答えを知らない場合、正直に「知らない」と答えます。
* あなたは子供に話かけるように優しい口調で話します。
"""


def create_conversational_chain():
    llm = ChatOpenAI(temperature=0.7, openai_api_key=OPENAI_API_KEY)

    memory = ConversationTokenBufferMemory(
        llm=llm, return_messages=True, max_token_limit=500
    )

    prompt = ChatPromptTemplate.from_messages(
        [
            SystemMessagePromptTemplate.from_template(template),
            MessagesPlaceholder(variable_name="history"),
            HumanMessagePromptTemplate.from_template("{input}"),
        ]
    )

    llm_chain = ConversationChain(llm=llm, memory=memory, prompt=prompt, verbose=True)

    return llm_chain


SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"]
SLACK_SIGNING_SECRET = os.environ["SLACK_SIGNING_SECRET"]

# Flaskアプリケーションの設定
app = Flask(__name__)
slack_app = App(token=SLACK_BOT_TOKEN, signing_secret=SLACK_SIGNING_SECRET)
slack_request_handler = SlackRequestHandler(slack_app)

chain = create_conversational_chain()


# メッセージイベントのリスナーを設定
@slack_app.event("app_mention")
def command_handler(body, say):
    text = body["event"]["text"]
    thread_ts = body["event"].get("thread_ts", None) or body["event"]["ts"]

    # Slackに返答を送信
    say(text=chain.predict(input=text), thread_ts=thread_ts)


# Slackイベントのエンドポイント
@app.route("/slack/events", methods=["POST"])
def slack_events():
    return slack_request_handler.handle(request)


if __name__ == "__main__":
    app.run(debug=True)

最低限は動作するようになったのでそろそろデプロイしても良さそう。

keitaknkeitakn

ConversationTokenBufferMemory に渡す max_token_limit は500を設定してあるがこれが妥当かどうかは運用しながら考える。

keitaknkeitakn

あとこのコードを動作させる為には tiktoken というライブラリが必要だった。

pip install tiktoken
keitaknkeitakn

Poetryについて

標準の pip だと正確なバージョンの指定が出来ない。

langchain はまだ新しいライブラリで今後破壊的な変更が入る可能性も低くはない。

という訳で Poetry を使う事にする。

インストール

以下でインストールを実施する。

brew install poetry

以下を実行して Poetry (version 1.4.2) のように表示されれば問題なし。

poetry --version

以下を実行する。これを実行するとプロジェクトのディレクトリ配下に仮想環境を作ってくれるようになる。

プロジェクト配下に仮想環境があったほうが分かりやすいので実施しておく。

poetry config virtualenvs.in-project true

ちなみに現状の設定に関しては poetry config --list で確認出来る。

先程設定した virtualenvs.in-projecttrue になっている事を確認出来る。

初期設定

以下を実施する。

poetry init

対話式のインターフェースに従って入力していく。

poetryで仮想環境を作成する

以下を実行して仮想環境を作成。

poetry install

この時点では何も依存packageを選択していないので、以下のようなエラーが出るが仮想環境の作成自体は出来ている。

/Users/myusername/gitrepos/chat-gpt-slack-bot/chat_gpt_slack_bot does not contain any element
zsh: exit 1     poetry instal

PyCharmの公式ドキュメントを見ながらやっていく。

https://pleiades.io/help/pycharm/poetry.html

PyCharm → Project: Project名(私の環境だとchat-gpt-slack-bot) → Python Interpreter → Add Interpreterから設定する。

PyCharm-poetry

仮想環境をアクティベートする

以下のコマンドを実行する。

poetry shell

例によってPyCharmのターミナルを開くと最初からアクティベートされた状態になっている。

必要なpackageを追加

Poetryでは以下のコマンドで必要なpackageを追加するようだ。

poetry add <package_name>

最終的に必要なpackageは以下の通りなので以下を実行する。

poetry add slack-bolt langchain Flask tiktoken flake8 black openai

まだ試していないがpackageを削除する時は以下のように実行するらしい。

poetry remove <package_name>
keitaknkeitakn

fly.io へのデプロイ

事前準備

コンテナで起動する為の準備をする。

Pythonだと gunicorn というHTTPサーバーを利用する事が多いようだ。

という訳でpackageを追加する。

poetry add gunicorn

app.py を変更してログを標準出力するように設定する。

from flask import Flask
import logging
from logging import StreamHandler

// 中略

# Flaskアプリケーションの設定
app = Flask(__name__)

# ログの設定↓を追加
stream_handler = StreamHandler()
stream_handler.setLevel(logging.INFO)
app.logger.addHandler(stream_handler)
app.logger.setLevel(logging.INFO)

Dockerfileの追加

最初にDockerfileを作成する。

docker build -t chat-gpt-slack-bot .

以下で先程Buildしたイメージが存在する事を確認。

docker images

コンテナを起動。

docker container run -d -p 5002:5000 -e OPENAI_API_KEY=$OPENAI_API_KEY -e SLACK_BOT_TOKEN=$SLACK_BOT_TOKEN -e SLACK_SIGNING_SECRET=$SLACK_SIGNING_SECRET -e FLASK_DEBUG=1 chat-gpt-slack-bot

ngrok http 5002 で一時的にコンテナのURLを開放して動作確認。

ログを確認して起動してリクエストを正常に受け付けている事を確認。

# acf172cd54b8 はコンテナID
docker logs -f acf172cd54b8
# acf172cd54b8 はコンテナID
docker container stop acf172cd54b8

flyctl の追加

GitHub認証でアカウントを作成出来るのでアカウントを作成する。

今回作成したSlackbotは無料枠で利用出来るが今後の為に一応カード登録を済ませておく。

確か東京リージョンにデプロイする為にもカード登録が必須だった気がする。(未確認)

CLIツールをインストールする。

brew install flyctl

以下で認証を実施する。

flyctl auth login

初期化を実施する。

# デプロイ対象のアプリケーションディレクトリに移動
cd chat-gpt-slack-bot
flyctl launch

ここから対話型のIFとなる。

Choose an app name と聞かれるのでアプリ名を入力。

Choose a region for deployment でTokyo, Japanを選択。

データベースの作成(Would you like to set up a Postgresql database now?) は今回使わないのでNo。

Redis(Would you like to set up an Upstash Redis database now?) も今回使わないのでNo。

Would you like to deploy now? と今すぐデプロイするか聞かれるが先に環境変数を登録したいのでNoを選択。

これで fly.toml が自動生成される。

環境変数の登録

以下のコマンドで環境変数を登録する。

flyctl secrets set -a アプリ名 SLACK_BOT_TOKEN=実際の値を指定
flyctl secrets set -a アプリ名 SLACK_SIGNING_SECRET=実際の値を指定
flyctl secrets set -a アプリ名 OPENAI_API_KEY=実際の値を指定

デプロイ

ここまで準備が出来たら以下のコマンドでデプロイを実行する。

flyctl deploy

実行ログを見るとDockerイメージのBuildしてレジストリに登録してアプリケーションをデプロイしている様子が分かる。

デフォルトだと https://{アプリ名}.fly.dev でアクセス可能になるようだ。

Slack APIの設定側で Event Subscriptions のURLを https://{アプリ名}.fly.dev/slack/events に変更して正常動作する事が確認出来た。

keitaknkeitakn

ステージング環境の作成(実際には試していない)

以下はChatGPTが教えてくれた手順。

公式のドキュメントも一応確認してみたが動きそうではある。

今回ステージング環境は作らないが今後の為に一応メモしておく。

ステージング用アプリ作成

以下で作成出来る、GUIで作っても問題はなさそう。

flyctl apps create myapp-staging

ステージング用アプリの設定ファイルを作成

以下でコピーを作成。

cp fly.toml fly.staging.toml

中を開いて app のところだけステージング用の物に変更。

ステージング用アプリ向けの環境変数を登録。

flyctl secrets set -a myapp-staging SLACK_BOT_TOKEN=実際の値を指定
flyctl secrets set -a myapp-staging SLACK_SIGNING_SECRET=実際の値を指定
flyctl secrets set -a myapp-staging OPENAI_API_KEY=実際の値を指定

ステージング用設定ファイルを指定してデプロイします。

flyctl deploy -c fly.staging.toml
keitaknkeitakn

デプロイの自動化

main ブランチにマージされたタイミングでデプロイを実行するようにしたい。

Vercelのようにリポジトリとの連携だけで自動デプロイを行う仕組みは存在しない模様。

しかし以下に公式からGitHubActionsのサンプルが記載されているので、これを見ながらやってみる。

https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/

https://fly.io/user/personal_access_tokens からアクセストークンを発行する。

公式の通り FLY_API_TOKEN という名前でGitHubActionsのSecretsとして登録する。

最終的には以下のようにした。

name: deploy to production
on:
  push:
    branches:
      - main
jobs:
  deploy:
    name: Deploy app
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v3
      - uses: superfly/flyctl-actions/setup-flyctl@master
      - run: flyctl deploy --remote-only
        env:
          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

ubuntu-22.04ubuntu-latest と書いても良いがバージョンを固定したほうがWorkflowが安定するのであえてこう書いている。

この辺の情報は下記のGitHubの公式リポジトリを参考にした。

https://github.com/actions/runner-images

対応時のPRのURLも載せておく。

https://github.com/keitakn/chat-gpt-slack-bot/pull/22

keitaknkeitakn

今後対応する事

一旦Slackbotの作成はここまでにする。というのもSlackbotを作っている最中にLLMを組み込んだWebサービスを作りたくなったのでそっちを始めようと思う。

ここまで作成したSlackbotは本当に最低限の機能しかない。本格的に利用するには最低でも以下のような対応を実施していく必要がある。

  • Slackbotのタイムアウト対応、このままだとGPTからのレスポンスに時間がかかる場合タイムアウトが発生してしまう、この記事 のようにリクエストを受け付ける部分とレスポンスを返す部分を別プロセスにする必要がありそう。
  • 会話履歴をDBに保存するようにする。
このスクラップは2023/05/02にクローズされました