生成 AI Slack ボットにモデル切り替え機能を実装してみた

生成 AI Slack ボットにモデル切り替え機能を実装してみた

Clock Icon2024.07.18

こんにちは、森田です。

生成AIアプリケーションを利用する際に、モデルは1つだけではなく、多くのモデルから選択できると嬉しいですよね。

このようなケースでは、アプリケーション側でモデルを入力するインタフェースを用意する必要があります。

例えば、よくあるWEBアプリですと、以下のようにデフォルトのモデルが設定されており、モデルを変更したい時だけ設定値を変更するような使い方ができます。

スクリーンショット 2024-07-19 3.39.22

引用: https://classmethod.jp/services/generative-ai/ai-starter/

一方で、チャットボットでは、上記のようなモデルの切り替えをしたいとなった時には、少し工夫が必要です。

本記事では、Slackでのチャットボットにおけるモデル切り替え機能を実装してみたいと思います。

前提

チャットボットのベースは以下のリポジトリのコードを使用します。

https://github.com/cH6noota/gen-ai-slack-bot-devio2024

モデルの切り替えについて

今回は、スラッシュコマンドからモーダルを開き、モデルの切り替えができるようにしてみます。

https://slack.dev/bolt-python/ja-jp/concepts#commands

また、せっかくなので、プロンプトも設定できるようにしていきます。

やってみた

全体のコードは以下のリンクへ格納しています。

https://github.com/cH6noota/gen-ai-slack-bot-devio2024/tree/feature/shortcut

重要な項目だけピックアップして紹介します。

DynamoDB の作成

ユーザのモデルID、プロンプト情報の記録を行うため、DynamoDBで新規にテーブルを作成します。

以下のように、UserIdをキーとするシンプルなテーブルを作成します。

template.yaml
UserTable:
  Type: AWS::DynamoDB::Table
  Properties:
      AttributeDefinitions:
          - AttributeName: UserId
            AttributeType: S
      KeySchema:
          - AttributeName: UserId
            KeyType: HASH
      ProvisionedThroughput:
          ReadCapacityUnits: 5
          WriteCapacityUnits: 5

Lazy Listener の利用

今回実装する処理については、DBの書き込み・読み込みを行うため、タイムアウトエラーを考慮して、Lazy Listener を利用します。

https://dev.classmethod.jp/articles/bolt-lambda/

スラッシュコマンドの作成

スラッシュコマンド実行時にトリガーされる処理を作成します。

events/command.py

from slack_app import app, model_id_list
from modal.edit_view import view
from lib.user_db import get_item

def edit(body, client, logger, say):
    user_id = body["user_id"]
    # ユーザ情報の取得
    user_data = get_item(user_id)
    client.views_open(
        trigger_id=body["trigger_id"],
        view=view(user_data, model_id_list)
    )

def ack_only(ack):
    ack()

app.command("/edit")(
    ack=ack_only,
    lazy=[edit]
)

上記のコードでは、DynamoDBからユーザ情報の取得を行い、ユーザ情報に基づいて、モーダルの表示を行います。

modal/edit_view.py
def view(user_data, model_id_list):

    options = [
        {
            "text": {
                "type": "plain_text",
                "text": model["ModelId"],
                "emoji": True
            },
            "value": model["ModelId"]
        } for model in model_id_list
    ]

    return {
        "type": "modal",
        "callback_id": "edit_view",
        "title": {"type": "plain_text", "text":"ユーザ情報変更"},
        "submit": {"type": "plain_text", "text":"送信"},
        "blocks": [
            {
                "type": "input",
                "block_id": "model_id",
                "element": {
                    "type": "static_select",
                    "placeholder": {
                        "type": "plain_text",
                        "text": user_data["ModelId"],
                        "emoji": True
                    },
                    "options": options,
                    "action_id": "static_select-action"
                },
                "label": {
                    "type": "plain_text",
                    "text": "モデルID",
                    "emoji": True
                }
            },
            {
                "type": "input",
                "block_id": "system_prompt",
                "label": {"type": "plain_text", "text":"システムプロンプト"},
                "element": {
                    "type": "plain_text_input",
                    "action_id": "system_prompt",
                    "multiline":True,
                    "placeholder": {
                        "type": "plain_text",
                        "text": user_data["SystemPrompt"],
                        "emoji": True
                    }
                }
            }
        ]
    }

view関数では、表示するモーダルの情報を生成しています。

モーダル送信時の処理

モーダル送信時には、以下の処理が実行されます。

events/modal.py
import json
from slack_app import app
from lib.user_db import update_item

def edit_save(say, logger, body):
    user_id = body["user"]["id"]
    model_id = body["view"]["state"]["values"]["model_id"]["static_select-action"]["selected_option"]["value"]
    system_prompt = body["view"]["state"]["values"]["system_prompt"]["system_prompt"]["value"]
    update_item(user_id, model_id, system_prompt)

def ack_only(ack):
    ack()

app.view("edit_view")(
    ack=ack_only,
    lazy=[edit_save]
)

上記の処理では、モーダルの入力値を受け取り、ユーザ情報の更新を行なっています。

モデル呼び出し関数

既存のchat.py内のモデル呼び出し関数(chat_use_history)を環境変数から引数で入力した値に変更します。

lib/chat.py

def chat_use_history(question, session_id, model_id, system_prompt):
    chat = ChatBedrock(
        model_id=model_id,
        model_kwargs={"temperature": 0.1},
        region_name=os.environ.get("ModelRegion")
    )

    prompt = ChatPromptTemplate.from_messages(
        [
            ("system", system_prompt),
            MessagesPlaceholder(variable_name="messages"),
        ]
    )
    chat_history = DynamoDBChatMessageHistory(
            table_name=os.environ.get("TableName"), 
            primary_key_name=os.environ.get("KeyName"),
            session_id=session_id, 
    )
    # 質問追加
    chat_history.add_user_message(question)

    chain = prompt | chat

    ## chainの実行
    result = chain.invoke(
        {
            "messages": chat_history.messages
        }
    )
    # 回答結果の保存
    chat_history.add_ai_message(result.content)
    return result.content

動作確認

Slackにて「/edit」を入力すると、以下のようにモーダルが開きます。

スクリーンショット 2024-07-19 3.34.22

モーダルへ変更したい情報を入力して、「送信」を押すと、変更内容の反映が行われます。

実際にモデルの呼び出しを行ってみると、確かにシステムプロンプト通りの回答ができています。

スクリーンショット 2024-07-19 3.45.43

さいごに

サクッと生成AIを活用したい場合に、Slackのような既存のツールに寄せることで、ユーザインターフェースの作成は不要となりますが、カスタマイズをしようとなった時に少々大変です。

一方で、Slackでは、カスタマイズが簡単に行えるようなフレームワークもありますので気になった方はぜひ試してみてください。

参考

https://qiita.com/seratch/items/0b1790697281d4cf6ab3

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.