話題の記事

AIチャットボットで問い合わせに対応し、回答が難しい内容に限り担当者にエスカレーション[Amazon Connect + Lex + Bedrock]

2023.11.02

はじめに

Amazon Connect + LexでAIチャットボットを構築し、問い合わせに対して無人対応し、対応が難しい内容に限り、オペレーター(以降、担当者)にエスカレーションする仕組みを作成しました。

コールセンターの負担軽減や人手不足の解消を目指して、AIチャットボットを活用して有人対応から自動応答に切り替えたいというニーズは増えているように思います。

本記事では、お問い合わせをAIチャットボットがヒアリングして、生成AIのAmazon BedrockのClaudeを用いて種別判定を行い、回答できるものはチャットボットが回答し、それ以外の内容については、担当者にエスカレーションする方法をまとめました。

今回検証するお問い合わせの種別は、以下の5種類です。

  • メニューに関する問い合わせ
  • 大人数対応に関する問い合わせ
  • 予約に関する問い合わせ
  • 営業日や時間に関する問い合わせ
  • 上記に当てはまらない場合、その他

お問い合わせ内容の種別を判定後、下記の対応をします。

  • 担当者にエスカレーション
    • メニューに関する問い合わせ
    • 大人数対応に関する問い合わせ
    • 予約に関する問い合わせ
    • その他
  • チャットボットが回答
    • 営業日や時間に関する問い合わせ
      • ただし、よくある質問(FAQ)に限ります
      • FAQ以外のお問い合わせについては、担当者にエスカレーション

営業日や時間に関する問い合わせはチャットボットで回答し、他の種別は、担当者にエスカレーションさせます。

ただし、営業日や時間に関する問い合わせでも、チャットボットで答えられない内容は、担当者に繋げます。

ちなみに、AIチャットボットで種別判定のみを行い、担当者につなげる方法は下記のブログをご参考ください。

構成

構成としては、下記の通りです。

担当者にエスカレーションする例として、処理の流れは以下の通りです。

  1. 顧客が電話をかけ、Connectのフローの一部であるLexのブロックに到達すると、顧客が発話します。
  2. 顧客が「予約できますか?」と発言すると、この内容がLexによってテキスト化され、その後BedrockのClaude Instantによって種別判定が行われます。
  3. 種別が「予約に関する問い合わせ」と判定され、担当者に電話をつなぎます。

チャットボットのみでの対応の例として、処理の流れは以下の通りです。

  1. 顧客が電話をかけ、Connectのフローの一部であるLexのブロックに到達すると、顧客が発話します。
  2. 顧客が「休みの日を教えて」と発言すると、この内容がLexによってテキスト化され、その後BedrockのClaude Instantによって種別判定が行われます。
  3. 種別が「営業日や時間に関する問い合わせ」と判定され、Claude V2がFAQを参考に回答します。
    1. FAQ以外のお問い合わせ内容であれば、担当者に電話をつなぎます。

以下の図は、電話での対話の流れを示しています。チャットボットで回答できない場合、担当者にエスカレーションします。

前提

  • 2023年10月時点での検証内容です。今後のアップデートにより改善される可能性があります。恒久的な結果ではありません。
  • 今回は、いくつかのサンプルで検証を行っただけであり、他のサンプルでも同様の結果となるとは限りません。これらの結果は一例として参照ください。
  • 検証では、ゆっくりと明瞭に発話していますので、早口であったり不明瞭な発話の場合、Lexの文字起こし精度に影響が出る可能性があります。

Lexボットの作成

ボットは日本語で作成します。

インテント名はfreeで、スロットはfreeinputtypeの2つ作成します。

スロットはどちらもAMAZON.FreeFormInputで設定し、プロンプトを適当に設定します。

スロットのtypeは、顧客からの発話を聞き取るのではなく、Claude Instantで種別判定した結果を挿入するために使用します。

Lambdaを使用するように設定しましょう。

後で作成しますが、LambdaをLexから呼び出すため、適切なLambdaを指定し保存します。

Lambdaを呼び出す設定は、[エイリアス]→対象のエイリアス(TestBotAlias)→[言語:Japanese (Japan)]を順にクリックすると、呼び出す設定画面がでます。

これでビルドするとLexのボットの構築は完了です。

Lambda

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

実行時間は、デフォルトの3秒から1分に変更しました。

コードは下記の通りです。

import json
import boto3
from decimal import Decimal
bedrock_runtime = boto3.client(service_name='bedrock-runtime', region_name="us-east-1")

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 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 input_slots_value(value):
    return {
        'shape': 'Scalar',
        'value': {
            'originalValue': value,
            'resolvedValues': [value],
            'interpretedValue': value
        }
    }
    
def get_bedrock_response(reception, input_text, modelId):
    if reception in "first_reception":

        prompt = f"""\n\nHuman:あなたは、居酒屋の電話担当者です。
        お客様からのお問い合わせ内容を元に、ルール内のリストからもっとも適切な種別を選んでください。
        存在しない問い合わせ種別に遭遇した場合は、「その他」を回答してください。
        <rule>
        1. 返信には、「以下のように変換しました」等の文言は含めないでください。
        2. お客様のお問い合わせ内容に最も適した種別を次のリストから選択してください:
        - メニューに関する問い合わせ
        - 大人数対応に関する問い合わせ
        - 予約に関する問い合わせ
        - 営業日や時間に関する問い合わせ
        3. 回答は、選択した種別のみを返してしてください。
        </rule>
        お客さんのお問い合わせ内容は次のとおりです。
        <question>
        {input_text}
        </question>
        \n\nAssistant:
        """
    else:

        prompt = f"""\n\nHuman:あなたは、居酒屋の電話担当者です。
        お客様からの質問に対して、「営業日や時間に関する問い合わせ」のよくあるお問合せのリストを参考に、適切な回答をしてください。
        有人対応が必要な場合に限り、「有人対応」のみを返答ください。他の文言は、返答しないでください。
        <rule>
        1. 返信には、「以下のように変換しました」等の文言は含めないでください。
        2. 下記は、「営業日や時間に関する問い合わせ」のよくあるお問合せのリストです。
        - 営業時間を教えてください。
            - 営業時間は、午後5時から翌日午前4時まで営業しています。水曜日のみ休みです。
        - 日曜日も営業していますか?
            - 水曜日以外は毎日午後5時から翌日午前4時まで営業しています
        - 平日の開店時間を教えてください。
            - 平日の開店時間は午後5時からとなります。
        - ラストオーダーは何時ですか?
            - ラストオーダーは営業時間終了の30分前、つまり午前3時30分です。
        - 祝日の営業時間は通常と変わりますか?
            - 祝日でも通常通り、午後5時から翌日午前4時まで営業しています。ただし、水曜日が祝日の場合は休みとなります。
        - ディナータイムは何時から何時までですか?
            - ディナータイムはご利用時間として午後5時から翌日午前4時までとなっております。
        - 閉店時間は何時ですか?
            - 当店の閉店時間は翌日の午前4時となります。
        - 営業時間外でもテイクアウトは可能ですか?
            - 申し訳ありませんが、テイクアウトは営業時間内のみとなっております。
        - 深夜遅くでも注文は可能ですか?
            - はい、深夜も営業していますので、午前3時30分まで注文を受け付けています。
        - 朝早くでも入店は可能ですか?
            - 申し訳ありませんが、当店は午後5時からの開店となっておりますので、午後5時以降にご来店ください。
        3. リストにない問い合わせや、詳細な情報が必要な場合は、「有人対応」のみを回答してください
        </rule>
        お客さんのお問い合わせ内容は次のとおりです。
        <question>
        {input_text}
        </question>
        \n\nAssistant:
        """
    accept = 'application/json'
    contentType = 'application/json'

    body = json.dumps({
        'prompt': prompt,
        'max_tokens_to_sample': 1000,
        'temperature': 0,
    })

    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').strip()

def free_intent(event):
    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)

    inquiry_type = get_bedrock_response("first_reception", input_text, 'anthropic.claude-instant-v1')
    print("Received inquiry_type:" + inquiry_type)
    
    if slots['type'] is None:
        # 種別をスロット名typeに挿入
        slots['type'] = input_slots_value(inquiry_type)

    if inquiry_type in ['営業日や時間に関する問い合わせ']:
        inquiry_answer = get_bedrock_response("second_reception", input_text, 'anthropic.claude-v2' )
        print("Received inquiry_answer:" + inquiry_answer)

        if "有人対応" not in inquiry_answer:
            return close("Fulfilled", 'ご回答いたします。' + inquiry_answer, intent_name, slots)

    slots['type'] = input_slots_value("有人対応")
    return close("Fulfilled", 'それでは、担当者にお繋ぎします', 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 == 'free':
        return free_intent(event)
  • スロットfreeinputには、顧客のお問い合わせの発話が入り、スロットtypeにはお問い合わせの種別のみが入ります
  • temperatureは、毎回同じ出力が得られるように0にしました
  • promptでは、種別判定はClaude Instant、回答の生成は、Claude V2を採用してます。
  • 回答の生成のpromptには、FAQのリストを入れています。Claudeの場合、プロンプトには10万トークン入りますので、もっと多く入れることも可能です。

Connectのコンタクフロー

コンタクフローは下記の通りです。

ポイントとしては、Lexスロットtypeの値には有人対応もしくは他の文言が入っています。

そのため、ブロックタイプであるコンタクト属性の設定に以下の設定をすると、有人対応の場合、担当者に繋ぐことができます。

  • 確認する属性
    • 名前空間:Lex
    • キー:スロット
    • スロット名:type
  • チェックする条件
    • 条件:次と等しい
    • 値:有人対応

試してみた

それでは実際に電話をかけて、お問い合わせ内容から、下記の種別を判断し、チャットボットが回答もしくは担当者にエスカレーションされるか確認します。

  • メニューに関する問い合わせ
  • 大人数対応に関する問い合わせ
  • 予約に関する問い合わせ
  • 営業日や時間に関する問い合わせ
  • 上記に当てはまらない場合、その他

お問い合わせ内容の種別を判定後、下記の対応をします。

  • 担当者にエスカレーション
    • メニューに関する問い合わせ
    • 大人数対応に関する問い合わせ
    • 予約に関する問い合わせ
    • その他
  • チャットボットが回答
    • 営業日や時間に関する問い合わせ
      • ただし、よくある質問(FAQ)に限る。
      • FAQ以外のお問い合わせについては、担当者にエスカレーション

判定:◯

こちらは、正しく判定されたものです。

担当者にエスカレーション

下記を3点を説明すると、私の発話(発話内容)からLexで文字起こしした内容、そしてその内容からお問い合わせの種別判定(Claude Instantの種別判定)を記載しています。

1つめ

  • 発話内容:「そちらで出している鶏肉は、どこ産ですか?」
  • Lexでの文字起こし:「そちら で 出し て いる 鶏肉 は どこ 産 です か」
  • Claude Instantの種別判定:「メニューに関する問い合わせ」

2つめ

  • 発話内容:「20人で予約したいんですが、入りますか?」
  • Lexでの文字起こし:「二 十 人 で 予約 し たい ん です が 入り ます か」
  • Claude Instantの種別判定:「大人数対応に関する問い合わせ」

予約というワードが入っていますが、大人数がお店に入れるか?というお問い合わせ内容ですので、大人数対応と正しく判定されていますね。

3つめ

  • 発話内容:「木曜日の営業時間に店長っていますか?」
  • Lexでの文字起こし:「木曜 日 の 営業 時間 に 店長 って い ます か」
  • Claude Instantの種別判定:「営業日や時間に関する問い合わせ」

営業日や時間に関する問い合わせですが、FAQに店長については記載がないため、エスカレーションされました。

チャットボットが回答

1つめ

  • 発話内容:「そちらのお店の今日の閉店時間を教えてください。」
  • Lexでの文字起こし:「そちら の お 店 の 今日 の 閉店 時間 を 教え て ください」
  • Claude Instantの種別判定:「営業日や時間に関する問い合わせ」
  • Claude V2の回答生成:「閉店時間は翌日の午前4時となります。」

2つめ

  • 発話内容:「火曜日のラストオーダーと開店時間を教えてください」
  • Lexでの文字起こし:「火曜 日 の ラスト オーダー と 閉店 時間 を 教え て ください」
  • Claude Instantの種別判定:「営業日や時間に関する問い合わせ」
  • Claude V2の回答生成:「火曜日のラストオーダーは午前3時30分、閉店時間は翌日の午前4時となります。」

お問い合わせ内容に対して、適切な回答がされていることが確認できました。

ただし、生成AIを2回使っているので、発話してから、回答までに7秒ほど待ちます。遅いと感じる人もいると思いますので、生成AIのレスポンス改善に期待しましょう。

判定:×

  • 発話内容:「3千円の飲み放題付きのコースは、最低何人から予約できますか?」
  • Lexでの文字起こし:「三千 円 の 飲み 放題 付き の コース は 最低 何 人 から 予約 でき ます か」
  • Claude Instantの種別判定:「大人数対応に関する問い合わせ」

その他、と種別判定してほしかったのですが、「何人」というワードから大人数対応のお問い合わせと種別判定されていますね。 プロンプトに、各種別の判定の具体例を加えたりすることで、より精度の改善が見込まれます

最後に

今回は、顧客からのお問い合わせをAIチャットボットがヒアリングし、生成AIを用いて、お問い合わせの種別判定を行い、チャットボットでは対応できない種別や内容に限り、担当者にエスカレーションをしてみました。

コールセンターの負担を減らすために、よくあるお問合せなどはAIチャットボットで対応が可能ですので、参考になれば幸いです。