Googleの生成AI、PaLM 2をSlack連携して社内ツールとして導入してみた

記事タイトルとURLをコピーする

G-gen のタナです。Google Cloud (旧称 GCP) の生成 AI (Generative AI) である PaLM 2 を用いて、Slackと連携した簡易的なチャットボットの PoC を行いました。

はじめに

今回の記事では、Google Cloud (旧称 GCP) の生成 AI (Generative AI) である PaLM 2 を用いて、Slack と連携した簡易的なチャットボットの PoC を行いました。

生成 AI を社内で運用し、データを内部で管理することで、機密情報の保護ができます。また社員が入力したプロンプトの履歴をログとして保存することで、現場の実務を分析し、社員に対する課題や必要なサポート、関心のあるトピック・キーワードを見つけ出すことができます。人事の方や管理の方はこの情報を活用し、研修の計画作りなどにも役立てることができます。

SlackxPaLM2のアプリ
Slack x PaLM2のアプリ

前提知識と事前準備

PaLM 2

PaLM 2 は Google の最新の生成 AI です。詳しくは以下の記事をご覧ください。

blog.g-gen.co.jp

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 画面で設定できます。)

Python Slack SDK

Slack API を Python で簡単に利用するための ライブラリー が用意されています。このライブラリーを使えば、Slack メッセージを読み取ったり、投稿したりすることが可能です。詳細は以下のリンクをご覧ください。

Google App Engine

Google App Engine (または単に App Engine) は Web アプリケーションをホストすることができるサーバーレスの Google Cloud プロダクトです。サービスの特徴については以下の記事をご参照ください。

blog.g-gen.co.jp

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.txtapp.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: 0max_instances: 2 は、自動スケーリングによって管理されるインスタンスの最小数と最大数をそれぞれ指定しています。
    • トラフィックが少ない場合でコストを極力に押さえることを希望する場合、automatic_scalingmax_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 が実行されます。

インスタントタイプごとの料金については、次のリンクをご参照ください。

3. app.py

以下は、Slack と PaLM 2 間の通信をサポートする中継サーバとして機能する Python アプリケーションのソースコードです。以下のように動作します。

  1. Slack からのメッセージを受け取り、それをPaLM 2に転送
  2. PaLM 2 からのレスポンスを取得し、それをSlackに返信
  3. ユーザーのプロンプト(メッセージ)と 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 がどのように客観的かつ有益なアドバイスを提供してくれるかが分かります。

マーケティングキャンペーンに関するユーザーのプロンプト及びPaLM 2の回答

また、上記のマーケティング課題について、対象者をより詳細に特定することで、それに適したアイディアを PaLM 2 が生成してくれました。

BigQuery によるプロンプト履歴の分析

手順は後述しますが Cloud Logging を活用して BigQuery と連携させることで、プロンプトの履歴を BigQuery で確認し、分析を行うことが可能となります。このようなデータを用いて、職場の関心事や話題を把握し、それに基づいた職場環境の改善策を見つけることができます。

例えば、特定のプロンプトが頻繁に使用されている場合、それはそのトピックが現場で高い関心を持たれている可能性を示しています。また PaLM 2 からのレスポンスを分析することで、そのトピックに対する具体的なアドバイスや解決策を見つけることも可能です。

このように BigQuery を介したログデータの分析は、組織の課題解決に役立つ洞察を提供することができます。

BigQueryでプロンプト履歴の確認

デプロイの手順

1. Slackのシークレット取得

Slack API を使うために「Signing Secret」と「OAuth Tokens for Your Workspace」というシークレットが必要です。

1.1.作成済みの Slack App の設定画面を開く

1.2. Setting の Basic Information に移動し Signing Secret を取得。安全な場所に保存

Signing Secretを取得する方法

1.3. Features の OAuth & Permissions に移動し OAuth Tokens for Your Workspace を取得。安全な場所に保存

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 アプリケーション専用のログバケットを作成

ログバケットの作成方法については以下のリンクを参照してください。

設定画面は以下のイメージのようになります。

CloudLoggingログバケットの作成
ログバケットの作成

BigQueryとの連携を可能にするために、「Upgrade to use Log Analysis」と「このバケットに新しいBigQueryデータセットをリンクする」にチェックを入れてください。

3.2 ログのルーティング設定

ログのルーティングを設定しない場合、Python から取得した Cloud Logging のメッセージがデフォルト (_Default) のログバケットに送信されます。今回は、作成したログバケット(BigQueryと連携している)へログを転送するため、シンクを作ります。 シンクの作成方法については、以下のリンクを参照してください。

設定画面は以下のイメージのようになります。指定したログ名に基づいてメッセージを作成したログバケットに転送するように設定します。logName を私と同じ内容に設定すれば、私のサンプルコードをすぐに利用できます。

CloudLoggingシンクの作成
シンクの作成

4. デプロイ

4.1. Cloud Shell でアプリケーション関連のファイルを格納するディレクトリを作成

4.2. 準備した requirements.txt、app.yaml、main.pyを下記のように配置

App Engineでデプロイするファイル
App Engineでデプロイするファイル

4.3. gcloud app deploy コマンドでデプロイ

App EngineでPaLM2アプリのデプロイ
App EngineでPaLM 2 アプリのデプロイ

4.4 エンドポイント URL 表示

AppEngineのデプロイURL
App EngineのデプロイURL

次のステップではこのURLを使用して、我々の PaLM 2 アプリケーションを Slack イベントにサブスクライブします。

5. アプリを Slack App のイベントにサブスクライブ

5.1. 作成済みの Slack App の設定画面を開く

5.2. Event Subscriptions 設定

Features の Event Subscriptions に移動し、4で作成したアプリの URL を下図のように入力します。そのURLの後ろに「slack/events」を指定します。

PaLM2アプリSlackのイベントにサブスクライブ
Slackのイベントにサブスクライブ

また、slackボットに送るイベントを次のように設定します。

Subscribe-to-bot-eventsの設定

設定は以上です。これで Slack からプロンプトを入力することができます。

アプリケーションの改善

今回紹介したアーキテクチャは、PaLM 2 のユースケースを示すための PoC です。このアーキテクチャを改善して、組織内で PaLM2 をデプロイすることが可能です。以下に改善例を示します。

1. text-bison モデルの代わりに chat-bison モデルを使用

ユーザーのプロンプトに対する応答を chat-bison モデルで行うことで、ユーザーエクスペリエンスを向上させることができます。各チャットセッションの維持を管理するために、会話履歴を保持することができます。

chat-bison モデルを使用するための参考コードはこちらです。

生成AIでチャットボットを作るときの具体的なコツは以下の記事をご参照ください。

blog.g-gen.co.jp

2. プロンプト履歴を保存する場所の変更

今回は簡易的なアプリのため、Cloud Logging のログバケットにデータを保持することにしました。代わりに Cloud Storage や Cloud SQL などにプロンプト履歴を書き込むようにすることも可能です。

3. 応答品質を向上させるための追加のプロンプトレイヤー

ユーザーのプロンプトと最終的な応答を行う PaLM 2 の間に、テキストベースの PaLM 2 モデルを用いたプロンプトレイヤーを追加することで、ユーザーからのプロンプトを改善することができます。このレイヤーの出力を元のプロンプトの改善版とするために、プロンプトエンジニアリングを行う必要があります。

例えば、追加のプロンプトレイヤーはユーザーの元のプロンプトから「コンテキスト」を抽出し、それを元のプロンプトの冒頭に挿入して出力することができます。例えば、元のプロンプトが

私のレストランで昼間の客数を増やす方法は?私のレストランはオフィスエリアにあるインド料理店で、ターゲットの客は日本人です。

である場合、改善されたプロンプトの一例は次のようになります。

ビジネス戦略

オフィスエリアに位置するインド料理店。ターゲットは日本人。

昼間の客数を増やす方法は?

この改善されたプロンプトを取得したら、再度 text-bison/chat-bison モデルに送信して PaLM 2 からの最終的な応答を得ます。

4. モデルチューニング

特定のタスクを達成するためにプロンプトエンジニアリングをしても想定した回答や形式が得られないとき、チューニングモデルを作成することができます。

例えば、問い合わせ対応や転写のタイプミスの修正などのタスクです。その場合、以下の手順に従ってください。

タナ (記事一覧)

データアナリティクス準備室 データエンジニア

バックエンド開発を含むデータ分析とデータエンジニアの経験を持つ。AIの活用にも関心がある。Professional Machine Learningを取得。出身地はタイのバンコクで、現在は広島在住。