Money Forward Developers Blog

株式会社マネーフォワード公式開発者向けブログです。技術や開発手法、イベント登壇などを発信します。サービスに関するご質問は、各サービス窓口までご連絡ください。

20230215130734

ChatGPTのAPIがオープンになったのでSlackに分身を配置した話

※飯テロを要求する社員と飯テロするbotと飯テロにダメだしする社員戯画

ご機嫌よう、世界
なんかもう最近は猫も杓子もChatGPTじゃないですか?
僕みたいに自然言語処理をかじっていると、社内外問わずいろんな人から「ChatGPT、どうっすか」と まるで宗右衛門町の客引きのように 声をかけられます
どうもこうも、それはそれでしょう、そもそもあの子のベースになっているGPT-3.5は単純にパラメータ数がry

ChatGPTのAPIがついにオープンになったのでSlack botを早速導入する

※とんでもない嘘をつくChatGPT図絵

どうもCTO室AI推進部とグループ会社HiTTOで 二股 兼務中の@ken11です。

いや今回の話は非常にシンプルで掲題の通りなんですが、冗談抜きで社内外問わずChatGPTに対する強い興味と関心のお声を頂戴しており、せっかくなのでAPIが使えるようになったならみんなに伝わりやすい使い方をしてみましょうね、そうだよねハム太郎ってことでSlack botをつくりました
すなわちもう一人の自分、ペルソナを召喚しました()

こういうのは話題性があるうちにやるのが大事なのでね、機能性よりスピード重視です!
僕は午前4時頃に「そろそろオフトゥンへGOの時間だなも…ねむねむ…(おもむろにYouTubeで新兎わいちゃんを見始める ※ここ重要)」というナイトルーティン(死語)をし始めた頃にOpenAIから「APIあけたーーーー」ってメールが配信されていることに気づき、「おいおいおいおい今夜は寝かさねえってか?」とオフトゥンからエクソダス、botを作り始めたのでした(いい話だ)

www.youtube.com

おいしいChatGPTなSlack botのつくりかた

とりあえず今回のbotの構成について、マジでシンプルにこんな感じでつくります

Slack bot ⇔ API Gateway ⇔ Lambda

ほんと、それ以上でも以下でもないです、おもちゃですから
※おもちゃとはいえAWS使ってるのでしっかりお金はかかります!また、ChatGPT APIは最初は無料分でまかなえると思いますが、そのうち課金しないと動かなくなります!

Lambdaを用意する

なにはともかくChatGPT APIを使うLambdaをつくりましょう
OpenAIのユーザ登録をして、とりあえずAPIキーを取得します

そしてOpenAIを使うにはライブラリを利用するのが楽なので、Lambdaのlayerを生成します
あ、今回僕はPythonでやってます

$ mkdir python
$ pip install openai -t ./python
$ zip -r9 layer.zip python

そしてレイヤーメニューからレイヤーを作成します

先ほど作成した layer.zip をアップロードし、適当な名前をつけて作成します

続けて、とりあえずLambda関数を「一から作成」で作成します
ランタイムはPython3.9にします
最下部の レイヤーの追加 から先ほど作成したレイヤーを追加します

これでLambda関数内で openai のライブラリが使用できるようになりました

続いて、環境変数の設定をします
設定の環境変数を選択し、ここでは OPENAI_API_KEY という名前で、OpenAIのAPIキーをセットしておきます

また、Lambdaのタイムアウト時間は適宜延ばしておくとよいと思います(デフォルト3秒は結構厳しい、とはいえそもそもSlack側が3秒でタイムアウトするから別にいいのかもしれない)

最後に、Lambda関数のコードを書きます

import os
import openai
import json
import hmac
import hashlib
import datetime
from urllib.parse import parse_qs

openai.api_key = os.getenv("OPENAI_API_KEY")
secret = os.environ['SLACK_API_SIGNING_SECRET']

def verify(headers, body):
    try:
        signature = headers["X-Slack-Signature"]
        request_ts = int(headers["X-Slack-Request-Timestamp"])
        now_ts = int(datetime.datetime.now().timestamp())

        message = "v0:{}:{}".format(headers["X-Slack-Request-Timestamp"], body)

        expected = "v0={}".format(hmac.new(
                        bytes(secret, 'UTF-8'),
                        bytes(message, 'UTF-8'),
                        hashlib.sha256).hexdigest())
    except Exception:
        return False
    else:
        if (abs(request_ts - now_ts) > (60 * 5)
                or not hmac.compare_digest(expected, signature)):
            return False
        return True

def lambda_handler(event, context):
    if verify(event['headers'], event['body']):
        params = parse_qs(event['body'])
        text = params['text'][0]
        response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo",
            messages=[
                {"role": "system", "content": "You are a mad scientist."},
                {"role": "user", "content": text}
            ]
        )
    
        payload = {
            "response_type": "in_channel",
            "text": response["choices"][0]["message"]["content"]
        }

        return {
            "statusCode": 200,
            "headers": {
                "Content-Type": "application/json"
            },
            "body": json.dumps(payload)
        }
    else:
        return {"statusCode": 400}

以下、ポイントです。

  • 一応Slackからのリクエストなのか検証しています、環境変数に SLACK_API_SIGNING_SECRET を設定してごにょごにょ
  • API GWからきたリクエスト内容は、 parse_qsevent['body'] をパースすることで取得できます
  • 今回新たに開放された openai.ChatCompletion.create を使ってChatGPTを呼び出しています。messages オプションでまず system というroleを使って assistant (実際に回答してくるbot)のキャラクターを指定することができます。僕はマッドサイエンティストにしておきました() また、ここに過去のuserとassistantのやりとりを設定することで会話の流れをつくれるわけですが、今回は過去のログを記憶させるのが面倒だったので実施していません。その他、ドキュメントのこの辺に詳細は書かれています。
  • ChatGPTのレスポンスはjsonで、回答だけ取りたい場合は ["choices"][0]["message"]["content"] で十分です
  • payloadresponse_type はSlack側の仕様で、これを指定しないとスラッシュコマンドを利用したユーザにしか返信が見えなくなってしまいます。みんなで盛り上がるためにはみんなに見えるようにしよう!()

これでひとまずLambda関数はできました

API Gatewayを用意する

続いてAPI Gatewayを作成します
まず、APIの作成からRESTを選択して作成します

次に、アクションメニュー内のメソッドの作成を選び、POSTメソッドを作成します
作成済みのLambda関数を選び、プロキシ統合にしておきます

最後に、エンドポイントをデプロイします

これでもうAPI Gatewayはリクエストを受け付けている状態になります。

呼び出し用のURLが発行されるので、ここにリクエストするとLambdaが実行され、レスポンスが返ってくる次第です
このURLにめちゃくちゃリクエストされるとお金がBackwardする可能性があるので、URLの取り扱いは慎重に

Slack botをつくる

最後に、Slack botをつくります
ここCreateNewApp から作成を開始します
名前を適当につけて作成したら左のメニューにあるSlash Commandsを選びます

Create New Commandを選択し

こんな感じで設定してあげます
URLに先ほどのAPI GatewayのURLを入れてあげるだけで、あとはお好みでどうぞ

また、Signing SecretをLambdaで検証しているので、Lambdaの SLACK_API_SIGNING_SECRET に設定するのをお忘れなきよう

最後に、左側のメニューの Install App でbotを使いたいワークスペースにインストールしたら完了です
これでもう、Slack上でいつでもどこでも呼び出せます
都合のいい存在ですね

注意点

いくつか注意というか補足があります。

  • 会話の記憶は実装しなかったので、そこはまだ楽しめないですごめんなさい
  • API GatewayもLambdaもお金かかります、セキュリティ的にいい状態ではないので、リクエストが大量にきて泣かないよう気をつけてください。マジでお金がBackwardします
    • 緩和策として、API Gatewayには使用量プランというオプションがあり、クオータ制限等かけられるので心配ならどうぞ(ここでは詳しい話はしません)
  • Slackのスラッシュコマンドは3秒以内にレスポンスを返さないといけないという厳しい制限があります。Lambdaのコールドスタート、ChatGPT APIからのレスポンス等、3秒は結構厳しくなる場面もあるので、タイムアウトしがちなのはご愛嬌です。わざわざ非同期にするほどのものでもないしな……

最後に

というわけで、午前4時から爆速でSlack botをつくりあげ、寝て起きたらみんなが非常に楽しそうに使ってくれていたのでほっこりしました
これで僕が寝ている間もみんなSlack芸楽しめるね…( ˘ω˘ )
あ、ちなみにbotの名前は自分の分身として ken111 にしました、Vault111から来たアイツみたいな感じになりましたね()

以上、早速ChatGPT APIを使って遊び倒した話でした!
マネーフォワードってわりと真面目な会社だと思ってますか?
マネーフォワードには僕みたいに一日中Slack芸をして「ホントは仕事してないんでしょう?」と言われるtimes芸人が、実際には多数在籍しています!!(※個人調べ)

moneyforward-dev.jp

僕個人は一緒にSlack芸をしてtimes芸人になってくれる方を募集してます!