G-gen のタナです。Google Cloud (旧称 GCP) の生成 AI (Generative AI) である PaLM 2 を用いて、Slackと連携した簡易的なチャットボットの PoC を行いました。
はじめに
今回の記事では、Google Cloud (旧称 GCP) の生成 AI (Generative AI) である PaLM 2 を用いて、Slack と連携した簡易的なチャットボットの PoC を行いました。
生成 AI を社内で運用し、データを内部で管理することで、機密情報の保護ができます。また社員が入力したプロンプトの履歴をログとして保存することで、現場の実務を分析し、社員に対する課題や必要なサポート、関心のあるトピック・キーワードを見つけ出すことができます。人事の方や管理の方はこの情報を活用し、研修の計画作りなどにも役立てることができます。
前提知識と事前準備
PaLM 2
PaLM 2 は Google の最新の生成 AI です。詳しくは以下の記事をご覧ください。
2023年8月現在、Vertex AI PaLM API は日本語未対応ですが、筆者の環境は Trusted Testers プログラムに参加中のため日本語にも対応しております。
Slack App
Slack API を使用するためには、まず Slack app を作成する必要があります。以下のリンク先をご覧いただき、Slack App を作成してください。
作成時に得られる「Signing Secret」、「Bot User OAuth Token」などのシークレット情報は、安全な場所に保管してください。また、作成した Slack App には適切な権限を付与することを忘れないでください。
必要な権限は channels:history
channels:join
channels:read
chat:write
im:history
im:write
などです。(Features / OAuth & Permissions 画面で設定できます。)
- 参考 : Quickstart
Python Slack SDK
Slack API を Python で簡単に利用するための ライブラリー が用意されています。このライブラリーを使えば、Slack メッセージを読み取ったり、投稿したりすることが可能です。詳細は以下のリンクをご覧ください。
- 参考 : Slack Bolt
- 参考 : Slack Bolt / Fast API examples
Google App Engine
Google App Engine (または単に App Engine) は Web アプリケーションをホストすることができるサーバーレスの Google Cloud プロダクトです。サービスの特徴については以下の記事をご参照ください。
APIの有効化
以下のAPIを有効化する必要があります。
- Cloud Resource Manager API
- App Engine
- Secret Manager API
権限周りの設定
App Engine に紐づかれるサービスアカウントに以下のロールを付与する必要があります。
- Secret Manager Secret Accessor
検証
概要と構成図
以下の図は、今回開発したアプリケーションの全体像を示しています。このアーキテクチャでは App Engine が中継サーバとして機能し、Slack と PaLM 2 間の通信をスムーズに行います。具体的には slack_bolt (FastAPI) フレームワークを用いた Python アプリケーションが、Slack からのメッセージ (プロンプト) を受け取り、それを PaLM 2 に転送します。その後、PaLM 2 からの応答 (レスポンス) を取得して Slack へ返します。
またこのアーキテクチャでは、Secret Manager を活用して、認証済みサービスアカウントにアタッチされた App Engine の Python アプリケーションへとシークレット情報をセキュアに提供します。
さらに、この Python アプリケーションは、ユーザーのプロンプトと PaLM 2 からのレスポンスを Cloud Logging へ記録します。Cloud Logging はログデータをログバケットに保存し、それを BigQuery へ自動的に同期するように設定します。これにより、管理者はプロンプトと応答の履歴を簡単に分析することができます。
なお今回は簡易的な検証のため「テキストプロンプト」モデルである text-bison を選択しました。
App Engine へのデプロイ
必要なファイル
Python アプリケーションを App Engine にデプロイ運用するためには、特定のファイルが必須となります。具体的には requirements.txt
、 app.yaml
、そして app.py
です。
以下に、これらのファイルを活用したアプリケーションのソースコードを示します。読者の皆様も、以下のサンプルコードや設定ファイル を参考に、自身のアプリケーションデプロイに役立てていただけます。
1. requirements.txt
main.pyを実行するために必要なパッケージのリストは requirements.txt に記載します。このアプリケーションで主に使用するライブラリは以下の通りです。他にも必要なライブラリがありますので、詳細は requirements.txt をご参照ください。
google-api-core==2.11.1 google-api-python-client==2.93.0 google-auth==2.22.0 google-auth-httplib2==0.1.0 google-cloud-logging==3.6.0 google-cloud-aiplatform==1.28.0 google-cloud-bigquery==3.11.3 google-cloud-core==2.3.3 google-cloud-resource-manager==1.10.2 google-cloud-secret-manager==2.16.2 google-cloud-storage==2.10.0 google-crc32c==1.5.0 google-resumable-media==2.5.0 googleapis-common-protos==1.59.1 grpc-google-iam-v1==0.12.6 slackclient==2.9.4 slackeventsapi==3.0.1
2. app.yaml
app.yaml は App Engine で使用される重要な設定ファイルで、特定のサービスのデプロイメント記述子として機能します。このファイルは以下を定義します。
- App Engine がアプリケーションとどのようにやり取りするか
- ランタイム、インスタンスのスケーリングなどの設定
- サービスのバージョン
- 環境変数
以下は例です。
runtime: python39 instance_class: F1 service: palm2-slack-chatbot automatic_scaling: target_cpu_utilization: 0.90 min_instances: 1 max_instances: 2 entrypoint: gunicorn -w 2 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8080 app:api
それぞれの項目について簡単に説明します。
runtime: python39
: この行は、アプリケーションのランタイム環境を指定しています。この場合、Python 3.9を使用します。instance_class: F1
: インスタンスのクラスを指定します。F1は最小のインスタンスクラスで、最も低いコストです(2023-07-31時点)。service: palm2-slack-chatbot
: App Engine では複数のサービスを管理することができます。この行は、このアプリケーションが「palm2-slack-chatbot」という名前のサービスに関連していることを示しています。automatic_scaling
: このセクションでは、アプリケーションの自動スケーリングの設定を定義します。target_cpu_utilization: 0.90
は CPU 使用率が90%に達した時に新たなインスタンスを立ち上げることを示しています。min_instances: 0
とmax_instances: 2
は、自動スケーリングによって管理されるインスタンスの最小数と最大数をそれぞれ指定しています。- トラフィックが少ない場合でコストを極力に押さえることを希望する場合、
automatic_scaling
のmax_instances:
を1にしてください。
entrypoint: gunicorn -w 2 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8080 app:api
: エントリーポイントは App Engine がアプリケーションを起動する際に最初に実行するコマンドを指定します。今回は、FastAPIを利用するので、gunicorn -w 2 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8080 app:api
コマンドにより app.py が実行されます。
インスタントタイプごとの料金については、次のリンクをご参照ください。
- 参考 : App Engine の料金
3. app.py
以下は、Slack と PaLM 2 間の通信をサポートする中継サーバとして機能する Python アプリケーションのソースコードです。以下のように動作します。
- Slack からのメッセージを受け取り、それをPaLM 2に転送
- PaLM 2 からのレスポンスを取得し、それをSlackに返信
- ユーザーのプロンプト(メッセージ)と PaLM 2 からのレスポンスを Cloud Logging で記録
from fastapi import FastAPI, Request from google.cloud import logging from slack_bolt import App from slack_bolt.adapter.fastapi import SlackRequestHandler from slack_sdk.web.async_client import AsyncWebClient import vertexai from vertexai.language_models import ChatModel, InputOutputTextPair, TextGenerationModel from modules import gc_utils, utils # Secret Managerから環境変数を読み込む(Secret Managerを使わなければ1.事前に環境変数にこれらの値を格納し、環境変数から読み込む。2.ハードコードで入力。) PROJECT_ID, PROJECT_NO = gc_utils.get_project_number() SIGNING_SECRET = gc_utils.access_secret_version( PROJECT_NO, "palm2-slack-chatbot-l-signing-secret" ) SLACK_TOKEN = gc_utils.access_secret_version( PROJECT_NO, "palm2-slack-chatbot-l-slack-token" ) RESOURCE_LOCATION = "us-central1" HISTORICAL_CHAT_BUCKET_NAME = "historical-chat-object" # FastAPI app = App(token=SLACK_TOKEN, signing_secret=SIGNING_SECRET) app_handler = SlackRequestHandler(app) api = FastAPI() @api.post("/slack/events") async def endpoint(req: Request): return await app_handler.handle(req) # VertexAIを初期化 vertexai.init(project=PROJECT_ID, location=RESOURCE_LOCATION) text_model = TextGenerationModel.from_pretrained("text-bison@001") PARAMETERS = { "max_output_tokens": 1024, "temperature": 0.20, "top_p": 0.95, "top_k": 40, } RESPONSE_STYLE = """""" # cloud logging logging_client = logging.Client() # cloud logging: 書き込むログの名前 logger_name = "palm2_slack_chatbot" # cloud logging: ロガーを選択する logger = logging_client.logger(logger_name) # 本動作はここから def generate_response( client: AsyncWebClient, ts: str, conversation_thread: str, user_id: str, channel_id: str, prompt: str, ) -> None: """ ユーザーIDがボットのIDまたはNoneでなく、かつチャンネルIDが存在する場合、Slackチャンネルにメッセージを投稿する。 Parameters ---------- ts : str メッセージのタイムスタンプ user_id : str ユーザーID channel_id : str チャンネルID prompt : str プロンプト """ response = text_model.predict(prompt, **PARAMETERS) # ブロックされたか確認する is_blocked = response.is_blocked is_empty_response = len(response.text.strip(" \n")) < 1 if is_blocked or is_empty_response: payload = "入力または出力が Google のポリシーに違反している可能性があるため、出力がブロックされています。プロンプトの言い換えや、パラメータ設定の調整を試してください。" else: # slackで**などのmarkdownを正しく表示できないので削除し、簡潔にする payload = utils.remove_markdown(response.text) # レスポンスをslackへ返す client.chat_postMessage(channel=channel_id, thread_ts=ts, text=payload) keyword = gc_utils.get_keyword(text_model, prompt, PARAMETERS) gc_utils.send_log(logger, user_id, prompt, payload, keyword) @app.event("message") def handle_incoming_message(client: AsyncWebClient, payload: dict) -> None: """ 受信メッセージを処理する Parameters ---------- payload : dict ペイロード """ channel_id = payload.get("channel") user_id = payload.get("user") prompt = payload.get("text") ts = payload.get("ts") thread_ts = payload.get("thread_ts") conversation_thread = ts if thread_ts is None else thread_ts generate_response(client, ts, conversation_thread, user_id, channel_id, prompt)
一番最初に、準備しておいたgc_utils及びutils モジュールを読み込みます。gc_utils及びutils はそれぞれGoogle Cloud と連携する関数、基本操作の関数が格納されています。
app.pyでは、それぞれの関数は以下のような役割を果たしています:
get_project_number
: Google Cloud プロジェクト ID と番号を取得access_secret_version
: Secret Manager からシークレット情報を取得remove_markdown
: PaLM 2 からのレスポンスのテキストからマークダウンを削除gc_utils.get_keyword
: ユーザーのプロンプトからキーワードを生成。キーワードは、ログデータに含めるために使用するものgc_utils.send_log
: ユーザーのプロンプト、PaLM 2 からのレスポンス、キーワードを Cloud Logging に送信generate_response
: 回答を生成し、Slack チャンネルにメッセージを投稿handle_incoming_message
: Slack からのメッセージを受け取り処理。この関数は
全体のフローとしてはまず handle_incoming_message
関数が Slack からのメッセージを受け取ります。
このメッセージはユーザーのプロンプトとして generate_response
関数に渡されます。次に、この関数はプロンプトを PaLM 2 に送信し、レスポンスを取得します。
取得したレスポンステキストからマークダウンを削除します (Slack は限定的なマークダウンしか対応できないため)。次に、クレンジング済みのレスポンステキストを Slack へ返信します。
最後に、ユーザーのプロンプトと PaLM 2 からのレスポンス、そして生成されたキーワードを含むログを Cloud Logging に送信します。
gc_utils.get_keyword
関数では few-shot 学習 (few-shot learning) というテクニックを活用しています。few-shot 学習は、限られた数の学習例(「ショット」)から新しいタスクを学習する能力を持つモデルを訓練する手法です。この場合、PaLM 2 は既に数多くのテキストデータを学習しており、それに基づいて新しいタスク、すなわち特定のプロンプトからキーワードを生成するタスクを実行します。
具体的には、関数内部でプロンプトとして数個の「学習例」を PaLM 2 に提示しています。これらの例は、文章からキーワードを生成するための「input」を示し、それに対する「output」も提供します。その後、ユーザーからの実際のプロンプトを input として提示し、それに対するキーワード(output)を PaLM 2 に生成させます。
このように few-shot 学習により、PaLM 2 は新しいタスクであるキーワード生成を適応的に行うことができます。この手法は、特に大規模な言語モデルを用いる際に強力で、新しいタスクへの迅速な適応を可能にします。
コード内のコメントや docstring を参照したい方は、ここ をご覧ください。
実行結果
Slack を介して PaLM 2 を活用するユーザー体験
手順は後述しますが、ユーザーが適切なプロンプトを設計し、それを Slack を通じて送信すると、PaLM 2 からは複雑なビジネス戦略やマーケティング課題についてのヒントとなる回答が得られます。これは、内部ツールとしての利用を通じて、ビジネスユーザーが業務効率を向上させる効果が期待できる一例です。
上記の例では、ビジネス戦略の課題に対する応答として PaLM 2 がどのように客観的かつ有益なアドバイスを提供してくれるかが分かります。
また、上記のマーケティング課題について、対象者をより詳細に特定することで、それに適したアイディアを PaLM 2 が生成してくれました。
BigQuery によるプロンプト履歴の分析
手順は後述しますが Cloud Logging を活用して BigQuery と連携させることで、プロンプトの履歴を BigQuery で確認し、分析を行うことが可能となります。このようなデータを用いて、職場の関心事や話題を把握し、それに基づいた職場環境の改善策を見つけることができます。
例えば、特定のプロンプトが頻繁に使用されている場合、それはそのトピックが現場で高い関心を持たれている可能性を示しています。また PaLM 2 からのレスポンスを分析することで、そのトピックに対する具体的なアドバイスや解決策を見つけることも可能です。
このように BigQuery を介したログデータの分析は、組織の課題解決に役立つ洞察を提供することができます。
デプロイの手順
1. Slackのシークレット取得
Slack API を使うために「Signing Secret」と「OAuth Tokens for Your Workspace」というシークレットが必要です。
1.1.作成済みの Slack App の設定画面を開く
1.2. Setting の Basic Information に移動し Signing Secret を取得。安全な場所に保存
1.3. Features の OAuth & Permissions に移動し OAuth Tokens for Your Workspace を取得。安全な場所に保存
取得したシークレットは、次のステップで Google Cloud の Secret Manager を利用してセキュアに保管します。これにより、アプリケーションがシークレットを必要とする際に、安全にアクセスできます。
2. シークレットをSecret Managerで保持
Google Cloud の Secret Manager は、シークレット(データベースのパスワード、APIキー、TLS証明書などの構成情報)を保存、管理、アクセスするためのツールです。非常に使いやすいツールなので、おすすめです。
Secret Manager でシークレットを作成する手順については以下の記事をご参照ください。
上記の手順に従って Signing Secret と OAuth Tokens for Your Workspace を保持してください。それぞれのシークレット名を「palm2-slack-chatbot-l-signing-secret」および「palm2-slack-chatbot-l-slack-token」にすると、私のコードでパラメータ名を変えずにすぐに活用できます。
3. Cloud Loggingの設定
このセクションでは、ユーザーのプロンプトと PaLM 2 のレスポンス履歴を効率的にログとして記録し、BigQuery との連携を実現するための Cloud Logging の設定方法を説明します。
3.1 アプリケーション専用のログバケットを作成
ログバケットの作成方法については以下のリンクを参照してください。
- 参考 :バケットの作成
設定画面は以下のイメージのようになります。
BigQueryとの連携を可能にするために、「Upgrade to use Log Analysis」と「このバケットに新しいBigQueryデータセットをリンクする」にチェックを入れてください。
3.2 ログのルーティング設定
ログのルーティングを設定しない場合、Python から取得した Cloud Logging のメッセージがデフォルト (_Default) のログバケットに送信されます。今回は、作成したログバケット(BigQueryと連携している)へログを転送するため、シンクを作ります。 シンクの作成方法については、以下のリンクを参照してください。
設定画面は以下のイメージのようになります。指定したログ名に基づいてメッセージを作成したログバケットに転送するように設定します。logName を私と同じ内容に設定すれば、私のサンプルコードをすぐに利用できます。
4. デプロイ
4.1. Cloud Shell でアプリケーション関連のファイルを格納するディレクトリを作成
4.2. 準備した requirements.txt、app.yaml、main.pyを下記のように配置
4.3. gcloud app deploy
コマンドでデプロイ
4.4 エンドポイント URL 表示
次のステップではこのURLを使用して、我々の PaLM 2 アプリケーションを Slack イベントにサブスクライブします。
5. アプリを Slack App のイベントにサブスクライブ
5.1. 作成済みの Slack App の設定画面を開く
5.2. Event Subscriptions 設定
Features の Event Subscriptions に移動し、4で作成したアプリの URL を下図のように入力します。そのURLの後ろに「slack/events」を指定します。
また、slackボットに送るイベントを次のように設定します。
設定は以上です。これで Slack からプロンプトを入力することができます。
アプリケーションの改善
今回紹介したアーキテクチャは、PaLM 2 のユースケースを示すための PoC です。このアーキテクチャを改善して、組織内で PaLM2 をデプロイすることが可能です。以下に改善例を示します。
1. text-bison モデルの代わりに chat-bison モデルを使用
ユーザーのプロンプトに対する応答を chat-bison モデルで行うことで、ユーザーエクスペリエンスを向上させることができます。各チャットセッションの維持を管理するために、会話履歴を保持することができます。
chat-bison モデルを使用するための参考コードはこちらです。
生成AIでチャットボットを作るときの具体的なコツは以下の記事をご参照ください。
2. プロンプト履歴を保存する場所の変更
今回は簡易的なアプリのため、Cloud Logging のログバケットにデータを保持することにしました。代わりに Cloud Storage や Cloud SQL などにプロンプト履歴を書き込むようにすることも可能です。
3. 応答品質を向上させるための追加のプロンプトレイヤー
ユーザーのプロンプトと最終的な応答を行う PaLM 2 の間に、テキストベースの PaLM 2 モデルを用いたプロンプトレイヤーを追加することで、ユーザーからのプロンプトを改善することができます。このレイヤーの出力を元のプロンプトの改善版とするために、プロンプトエンジニアリングを行う必要があります。
例えば、追加のプロンプトレイヤーはユーザーの元のプロンプトから「コンテキスト」を抽出し、それを元のプロンプトの冒頭に挿入して出力することができます。例えば、元のプロンプトが
私のレストランで昼間の客数を増やす方法は?私のレストランはオフィスエリアにあるインド料理店で、ターゲットの客は日本人です。
である場合、改善されたプロンプトの一例は次のようになります。
ビジネス戦略 オフィスエリアに位置するインド料理店。ターゲットは日本人。 昼間の客数を増やす方法は?
この改善されたプロンプトを取得したら、再度 text-bison/chat-bison モデルに送信して PaLM 2 からの最終的な応答を得ます。
4. モデルチューニング
特定のタスクを達成するためにプロンプトエンジニアリングをしても想定した回答や形式が得られないとき、チューニングモデルを作成することができます。
例えば、問い合わせ対応や転写のタイプミスの修正などのタスクです。その場合、以下の手順に従ってください。
タナ (記事一覧)
データアナリティクス準備室 データエンジニア
バックエンド開発を含むデータ分析とデータエンジニアの経験を持つ。AIの活用にも関心がある。Professional Machine Learningを取得。出身地はタイのバンコクで、現在は広島在住。