LoginSignup
4
5

Slackで質問に自動回答!ChatGPTとLangChainで社内FAQボットを作ってみた Part3

Posted at

概要

本記事では、Slack BoltとChatGPT、LangChainを用いて社内FAQボットを開発する過程を、Partに分けて解説します。
Part1では独自データを回答する。Part2ではSlack Bolt for Pythonを使ってSlack Botの実装について解説しました。今回のPart3では、Dockerを使ったLambda環境でのデバッグとPart2でできていないslackから独自データを回答するところまでを解説します。

Dockerでデバッグ

Part2までで作ったコードは最終的にはAWS Lambda上で動かしたいのですが、ここで2つの課題をクリアする為に、Dockerを使います。

  1. AWS Lambdaはアップロードしたデプロイパッケージの解凍後のサイズが250MBを超えるとエラーとなります。複数のライブラリを入れるとすぐに上限を超えてしまうため、デプロイ方法を考える必要があります。コンテナイメージは、この制限を回避することができます。(その他にもやり方はいくつかありますが、今回はこれを選択します。)
  2. 開発環境では動いたのに本番環境では動かないというトラブルがよくあります。コンテナイメージをデプロイすることで、開発・本番と同じ状態が作れます。

1.Docker Desktopインストール

私の環境はWindows10なので、Windows用のDockerをインストールします。参考になる記事を貼り付けておきます。

インストールが完了したら起動します。起動だけで特に操作は必要ありません。

2.IDEと拡張機能

VSCodeを使用します。
拡張機能に以下を追加してください。
image.png

Dev Containers拡張機能を使うことで、コンテナをフルタイムの開発環境として使用できます。実行中のコンテナにアタッチしてデバッグもできます。
image.png
(画像引用元:https://code.visualstudio.com/docs/devcontainers/containers

3.コンテナ環境構築と接続

構築にあたり、この記事を参考にさせて頂きました。

一応、私の手順も載せておきます。
以下、ファイル構成です。これらのファイルを作ります。

qiita/
│
├── .devcontainer/
│   └── devcontainer.json
│
├── docker-compose.yml
├── Dockerfile
├── ...

まずDockerfileを作ります。これはコンテナイメージを管理、構築するためのファイルです。
Part1から作っているプロジェクト内に Dockerfile という名前のファイルを作ります。
中身はこんな感じです。

# Amazon ECR Public Gallery からイメージを取得します。
FROM public.ecr.aws/lambda/python:3.11

# ワーキングディレクトリを設定
WORKDIR /var/task

# PYTHONPATH の設定
ENV PYTHONPATH="/var/task/app"

# 依存関係のファイルコピー
COPY requirements.txt .

# 依存関係のインストール
RUN pip install --upgrade pip && \
    pip install -r requirements.txt

# appディレクトリを/var/task/app/へコピー
COPY app/ /var/task/app/

# Lambdaの呼び出す関数を指定
CMD ["slack_app.handler"]

次にComposeファイルを作ります。これはアプリケーション全体の構成を記述するためのYMLファイルです。Dockerfileと同じパスに docker-compose.yml という名前のファイルを作成します。以下、docker-composeの中身です。詳しい解説は省きますが、後ほどAWSのECRへアップロードしますので、事前にAWSの環境とアクセス用のキーが必要です。

version: "3"
services:
  dev:
    build:
      context: .
      dockerfile: Dockerfile
    image: faqbot_image
    container_name: faqbot_container
    working_dir: /var/task
    volumes:
      - ./:/var/task
    ports:
      - "3000:3000"
    environment:
      AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY #環境変数に登録したものを参照しています。
      AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY #環境変数に登録したものを参照しています。
    env_file:
      - ./.env

最後にdevcontainerファイルを用意して接続に行きましょう。
画面左下の「><」のマークをクリック
image.png

開発コンテナー構成ファイルを追加…」をクリック
image.png

ワークスペースに構成を追加する」をクリック
image.png

なにも選択せずに「OK」をクリックします。 devcontainer.json という構成ファイルが作成されます。
image.png

デフォルトで色々書かれてますが、自分が必要な内容に書き換えます。

{
	"name": "slack faqbot app",
	"dockerComposeFile": "../docker-compose.yml",
	"service": "dev",
	"workspaceFolder": "/var/task",
	"settings": {
	  "terminal.integrated.shell.linux": "/bin/bash"
	},
	"customizations": {
	  "vscode": {
		"extensions": [
		  "amazonwebservices.aws-toolkit-vscode",
		  "ms-python.python",
		  "magicstack.MagicPython",
		  "ms-python.vscode-pylance",
		  "microsoft.vscode-docker"
		]
	  }
	},
	"remoteUser": "root",
	"shutdownAction": "stopCompose"
  }  

準備が多かったですが、やっと実行できます。画面左下の「><」をクリックし、「コンテナーで再度開く」をクリックします。
image.png

少し時間がかかりますが、開発コンテナーが起動します。これでローカルと同じようにデバッグができます。※docker desktopを起動していないとエラーになります。
image.png

指定した名前でdockerイメージが作成されています。
image.png

コンテナーも起動しています。
image.png

4.デバッグ

vscodeに戻り、デバッグします。コンテナー上で実行されていることが分かります。
image.png

ここからはPart2で使ったngrokを起動し、Forwarding のアドレスをこれまたPart2で作ったslackアプリ作成の「Request URL」にセットして更新します。Slackからのイベントを正常に受信できています。これでDockerコンテナーを利用したデバッグができるようになりました。
image.png

Slackで独自データを回答

さて、ここまでで、Dokcerを使ったSlackとのやり取りが完成したので、Slackから質問して独自データを回答してもらいましょう。

1.独自データ回答クラス

まず、Part1で作成したソースを修正して、Faissインデックスファイルの読み込みと、独自データの回答を返信できるクラスを作ります。以下、ソースです。(ファイル名はchatgpt_client.pyとしています。)
FaissインデックスファイルはPart1で作成したものを使います。「Windowsで開発環境を設定する 」のPDFファイルを読み込ませたものです。

import os
from dotenv import load_dotenv
from langchain_community.vectorstores.faiss import FAISS
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

class ChatGptClient():

    # OPENAI API KEYを環境変数から取得
    ChatOpenAI.openai_api_key = os.environ.get("OPENAI_API_KEY")
    # ベクターストア
    vector_store: FAISS
    
    faiss_index_dir = 'ベクトルデータのファイルを保存したフォルダを指定してください'

    def answer(self, question):
        """質問に対して、独自ドキュメントデータから該当する内容を回答する
        Args:
            question(str): 質問事項
        """

        retriever = self.vector_store.as_retriever()

        # promptの作成
        template = """
        contextに従って回答してください:{context}
        質問: {question}
        """
        prompt = ChatPromptTemplate.from_template(template)
        model = ChatOpenAI(temperature=0.0, max_tokens=1000)

        # ChatGPTへの質問生成
        chain = (
            {"context": retriever, "question": RunnablePassthrough()}
            | prompt
            | model
        )

        # ChatGPTによる回答生成
        ret = chain.invoke(question)

        # 回答を返す
        return ret.content

    def __init__(self):

        index_path = os.path.join(
            os.path.dirname(self.faiss_index_dir), 'index.faiss')

        if os.path.isfile(index_path):
            # Faissインデックス読み込み
            self.vector_store = FAISS.load_local(
                os.path.dirname(self.faiss_index_dir),
                OpenAIEmbeddings(),
                allow_dangerous_deserialization=True)
        else:
            # ファイルがなければindexファイルを作成する処理を書く
            pass

2.Slackとのやり取りと独自データの回答

次にPart2で作成したslack_app.pyのソースを修正して、上のChatGptClinetクラスを使って回答を取得できるようにしましょう。

import os
from chatgpt_client import ChatGptClient
from slack_bolt import App


# ボットトークンと署名シークレットを使ってアプリを初期化します
# SLACK_BOT_TOKENはBot User OAuth Tokenを環境変数に登録したものを参照しています
# SLACK_SIGNING_SECRETはSigning Secretを環境変数に登録したものを参照しています
app = App(token=os.environ.get('SLACK_BOT_TOKEN'),
          signing_secret=os.environ.get('SLACK_SIGNING_SECRET'),
          process_before_response=True)

# ChatGptClientのインスタンス作成
gpt_client = ChatGptClient()

# chatbotにメンションが付けられたときのハンドラ
@app.event("app_mention")
def handle_mentions(event, say, request):

    # 再送リクエストには200を返す
    if request.headers.get("x-slack-retry-num") is not None:
        return {"statusCode": 200}
    
    #ChatGPTを使って独自データを回答する
    res = gpt_client.answer(event["text"])
    say(text=res)


if __name__ == "__main__":
    # python app.py のように実行すると開発用 Web サーバーで起動します
    app.start()

⚡️ Bolt app is running! (development server)

それでは、SlackからFAQボットに対して質問を投稿して回答をもらいます。
FAQボットにメンションをつけてメッセージを送ります。
image.png

回答が返ってきました。Djangoプロジェクトの作成手順を回答していますね。
image.png

デバッグではありますが、概ねFAQボットの開発は完了しました。
次回はAmazon ECRにコンテナーイメージをアップロードして、AWS Lambda上で動作するようにします。

参考文献

4
5
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
4
5