ちょっと話題の記事

Amazon BedrockのClaudeとAmazon Connectを利用し、電話で色々な質問に答えてくれるコールセンター向けAIチャットボットを構築してみた

2023.10.08

はじめに

Amazon BedrockのClaudeとAmazon Connectを利用した、電話で様々な質問に対する応答が可能なコールセンター向けAIチャットボットを構築しました。

電話をかけて質問すると、BedrockのAPIを利用し、質問に答えてくれます。音声は、Amazon Connectが提供するものを活用しています。

以下は、電話をかけた際の対話の様子を示したイメージです:

実際に電話をかけたときのデモ動画です。Bedrockのモデルは、Claude Instant v1.2を使用しています。

構成図は、以下になります

Connectのコンタクトフロー内で、Lexで質問内容を受け取り、音声から文字起こしされ(裏でAmazon Transcribeが利用)、Lambdaが文字起こしされた質問テキストをBedrock APIにリクエストします。

レスポンス内容をLexに渡し、質問内容に回答する流れです。

前提条件

すべてのリソースは、バージニアリージョンで作成しました。

理由は、Amazon Bedrockで日本語対応のモデルであるClaude v2Claude Instant v1.2をそれぞれ試すためです。 東京リージョンでは、執筆時点でClaude Instant v1.2しか利用できません。

ただし、両者を試してみると、レスポンス時間が短いClaude Instant v1.2を採用したため、結果的には東京リージョンでも同様の構築が可能です。

LambdaからBedrock APIにリクエストしてからレスポンスまでの時間は、Claude Instant v1.2が5秒程度で、Claude v2が15秒程度かかりました。

  • Amazon BedrockのClaude Instantが利用できること
  • Connectインスタンスをバージニアリージョンで構築済み
    • 電話番号も取得済み

構築

以下の流れで構築します。

  1. Lambdaを作成
  2. Lexを構築
  3. Connectのコンタクトフローを作成

Lambdaの構築

Lambdaを作成します。以下を参考に、IAMロールやBedrockを利用するためのBoto3ライブラリをアップロードしてください。

上記に加え、以下の設定をしました。

  • IAMロールには、CloudWatch Logsにログを出力するため、CloudWatchLogsFullAccessを割り当ててます。
  • 実行時間は、デフォルトの3秒から1分に変更しました。
  • コードは、下記になります
import json
import boto3
from decimal import Decimal
bedrock_runtime = boto3.client('bedrock-runtime')

def decimal_to_int(obj):
    if isinstance(obj, Decimal):
        return int(obj)

def elicit_slot(slot_to_elicit, intent_name, slots):
    return {
        'sessionState': {
            'dialogAction': {
                'type': 'ElicitSlot',
                'slotToElicit': slot_to_elicit,
            },
            'intent': {
                'name': intent_name,
                'slots': slots,
                'state': 'InProgress'
            }
        }
    }

def confirm_intent(message_content, intent_name, slots):
    return {
        'messages': [{'contentType': 'PlainText', 'content': message_content}],
        'sessionState': {
            'dialogAction': {
                'type': 'ConfirmIntent',
            },
            'intent': {
                'name': intent_name,
                'slots': slots,
                'state': 'Fulfilled'
            }
        }
    }

def close(fulfillment_state, message_content, intent_name, slots):
    return {
        'messages': [{'contentType': 'PlainText', 'content': message_content}],
        "sessionState": {
            'dialogAction': {
                'type': 'Close',
            },
            'intent': {
                'name': intent_name,
                'slots': slots,
                'state': fulfillment_state
            }
        }
    }

def get_bedrock_response(input_text):
    prompt = f'\n\nHuman: 200字以内で答えてください。答える際、200字以内と言わなくてよいです。{input_text}\n\nAssistant:'

    modelId = 'anthropic.claude-instant-v1' 
    accept = 'application/json'
    contentType = 'application/json'

    body = json.dumps({
        "prompt": prompt,
        "max_tokens_to_sample": 1000
    })

    response = bedrock_runtime.invoke_model(
    	modelId=modelId,
    	accept=accept,
    	contentType=contentType,
        body=body
    )

    response_body = json.loads(response.get('body').read())
    
    return response_body.get('completion')

def Bedrock_intent(event):
    print("Received event:" + json.dumps(event, default=decimal_to_int, ensure_ascii=False))
    intent_name = event['sessionState']['intent']['name']
    slots = event['sessionState']['intent']['slots']
    input_text = event['inputTranscript']

    if slots['freeinput'] is None:
        return elicit_slot('freeinput', intent_name, slots)

    confirmation_status = event['sessionState']['intent']['confirmationState']

    if confirmation_status == "Confirmed":
        return close("Fulfilled", 'それでは、電話を切ります', intent_name, slots)

    elif confirmation_status == "Denied":
        return close("Failed", 'お力になれず、申し訳ありません。電話を切ります', intent_name, slots)

    # confirmation_status == "None"
    response_text = get_bedrock_response(input_text)
    print("Received response_text:" + response_text)

    return confirm_intent(
        f'それでは、回答します。{response_text}。以上が回答になります。回答に納得したかたは、はい、とお伝え下さい。納得いかない場合、いいえ、とお伝え下さい',
        intent_name, slots)

def lambda_handler(event, context):
    print("Received event:" + json.dumps(event, default=decimal_to_int, ensure_ascii=False))

    intent_name = event['sessionState']['intent']['name']

    if intent_name == 'bedrock':
        return Bedrock_intent(event)
  • 関数名:elicit_slot
    • Lexのスロットの値が埋まっていない場合に使用します。
  • 関数名:confirm_intent
    • Lexのスロットが全て埋まった時に使用します。
      • 確認プロンプトに設定しているプロンプトを伝えます。今回の場合、「それでは、回答します。.......」になります
  • 関数名:close
    • 確認プロンプトの後、インテントを終了するときに使用します。
      • クローズ時に設定しているプロンプトを聞きます。
  • 関数名:get_bedrock_response
    • Bedrock APIからのレスポンスを取得します。
  • if slots['freeinput'] is None:このうち、freeinputは、次の章で説明するLexのスロット名です。
  • if intent_name == 'bedrock':このうち、bedrockは、次の章で説明するLexのインテント名です。

Lexを構築

Amazon Lexのボットとインテントを作成します。

もちろん、対応言語は日本語です。

先程作成したLambdaをLexから呼び出すため、指定し保存します。

Lambdaの紐付け設定は、分かりにくいのですが、[エイリアス]→対象のエイリアス(TestBotAlias)→[言語:Japanese (Japan)]を順にクリックすると、紐付ける設定画面がでます。

インテント名はbedrockとし、インテントを呼び出すためのサンプル発話は、はいにします。

スロット設定は以下です

  • スロット名はfreeinput
  • スロットタイプは、自由形式の入力を受け付けるAMAZON.FreeFormInput
  • プロンプトは、「ご質問ください」

AMAZON.FreeFormInputの詳細は、下記をご参照ください。

先程設定したLambdaが利用されるように、初期化と検証に Lambda 関数を使用にチェックを入れましょう。

この設定で、[インテントを保存]の後、[Build]し、正常に構築されることを確認します。

Connectのコンタクトフローを作成

対象のConnectインスタンスに、先程作成したLexを登録します

フローは、シンプルですが、下記の4つのブロックのみにしました。

音声設定は、Kazuhaにしてます。言語属性を設定のチェックも忘れずにしましょう。

[顧客の入力を取得する]ブロックでは、読み上げるテキストを入力し、Lexのボット名やエイリアス名、インテント名を記載して保存します。

フロー公開後、電話番号をフローに割り当てば、Connectの設定は、完了です。

Claudeの回答内容とレスポンス時間

以下は、デモ動画で使用したモデルClaude Instant v1.2に対して、「東京について」の質問を行った際の回答です。

人口が約1300万人と国内最大の都市で、世界有数の大都市の1つです。
東京の街は23特別区から構成されており、それぞれに個性的な街並みや文化が形成されています。
新宿、渋谷、池袋、すきまなど青年文化が発達した繁華街がある一方で、上野、浅草、秋葉原など歴史ある文化施設も豊富に設けられています。
東京タワーをはじめとするユニークなランドマークも東京の魅力の1つ。
経済や政治の中枢機能を有するだけでなく、文化や arteryの中心地としても発展してきた東京です。

所々日本語に違和感や、英語が混じっています。

レスポンス時間は、5秒程度でした。

一方、モデルClaude v2に対して同様の質問を行った回答は、以下の通りです。

東京は日本の首都で、世界最大の都市の一つです。
東京には皇居やスカイツリー、渋谷、原宿、秋葉原などたくさんの有名な観光地があります。
また、美術館や建築物、寿司やラーメンなどの食文化も豊富です。
東京はビジネスの中心地である一方、ファッションやカルチャーのトレンドも生まれる活気に満ちた街です。
交通網も発達していて、鉄道を使えばどこへでも簡単に行けます。
東京には見どころがたくさんある素晴らしい街です。

日本語に違和感がなく、分かりやすいです。

ただし、レスポンス時間が15秒程度かかりました。

Claude Instant v1.2は、レスポンス時間が短い一方、日本語での回答に違和感がありました。

対して、Claude v2は、日本語の回答は分かりやすいのですが、レスポンス時間が長いというデメリットがありました。つまり、レスポンス時間と回答の精度はトレードオフの関係にあります。

電話での回答と考えると、レスポンス時間が短いClaude Instant v1.2を選択するしかないかなと思います。

最後に

今回は、Amazon BedrockのClaudeとAmazon Connectを組み合わせて、電話上で質問に応えることが可能なコールセンター向けAIチャットボットを構築しました。

BedrockのClaude Instant v1.2Claude v2について、現時点では、日本語の精度とレスポンス時間はトレードオフですが、今後の性能は向上に期待しましょう。

今後、KendraなどのAIサービスも組み合わせて、様々なテストを試していきたいと思います。