【Amazon Connect】ClaudeのToolsで音声対話からAPI実行

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

はじめに

こんにちは、サーバーワークスのディベロップメントサービス2課の池田です。

近年、労働人口の減少が深刻化する中、カスタマーサービスの自動化や効率化が求められています。このような状況において、音声対話システムにも自動化が求められています。
特に、電話での注文や問い合わせに対して自動応答システムを導入することで、オペレーターの負担を軽減すると同時に、限られた人員で顧客満足度の向上を実現することが可能です。

今回は、Amazon Connectを活用した音声対話システムに、ClaudeのTools機能を組み合わせて、通話の内容を元にAPIの呼び出しを実装する方法をご紹介します。これにより、音声で顧客情報の登録や注文処理を自動化することが可能になります。

Toolsとは

Toolsは、Claudeが対話中にAPIを呼び出すことを可能にする機能です。これにより、ユーザーとの対話の中でリアルタイムにデータを取得したり、特定のアクションを実行したりすることができます。

具体的には、対話の文脈に応じてAPIを呼び出し、その結果をユーザーに返すことができます。これにより、以下のような利点があります。

  • リアルタイムデータの取得: 最新の情報を取得してユーザーに提供できます。
  • 動的な対話: ユーザーの入力に基づいて柔軟に対応できます。
  • 自動化されたアクション: データの登録や更新などの操作を自動で行えます。

シナリオ

今回は、電話を通じてピザの注文を自動化するシナリオを実現します。
お客様からの電話に対し、音声対話を通じて必要な情報を取得し、APIを使用して顧客情報や注文内容を登録します。

以下は、お客様と自動応答システムとの想定される会話の流れです。

発言者 発言内容
お客様 (電話をかける)
自動応答 サバワピザです。お客様情報の確認をいたします。お電話番号を教えてください。
お客様 08012345678です。
自動応答 お客様のお電話番号は08012345678でよろしいでしょうか?
お客様 はい。
Lambda (ToolでAPIを実行してユーザ情報取得)
自動応答 お客様情報が見つかりませんでした。お客様情報を登録しますのでお名前、郵便番号、住所を教えていただけますか?
お客様 名前は山田太郎です。郵便番号は131-0045です。
自動応答 ありがとうございます。次に住所を教えてください。
お客様 東京都墨田区押上1丁目1−2です。
自動応答 お客様のお名前は山田太郎様。郵便番号は131-0045。住所は東京都墨田区押上1丁目1−2でよろしいでしょうか?
お客様 はい。
Lambda (ToolでAPIを実行してユーザ情報登録)
自動応答 ありがとうございます。お客様情報を登録しました。次にご注文するピザを教えてください。
お客様 注文するピザはクアトロ・フォルマッジ、Lサイズを1枚お願いします。
自動応答 ご注文を確認いたします。ご注文内容はクアトロ・フォルマッジのLサイズを1枚でよろしいでしょうか?
お客様 はい。以上で大丈夫です。
Lambda (ToolでAPIを実行して注文情報登録)
自動応答 ご注文を承りました。それではご到着までお待ちください。ご注文ありがとうございました。

このように、音声対話を通じてお客様から必要な情報を収集し、自動的に注文処理を行うシステムを構築します。

前提条件

  • Amazon Connectのインスタンスが作成済みであること
  • Claude 3.5 Sonetを有効化していること

構築手順

1. Lambdaを作成

AWS マネジメントコンソールより下記のLambdaを作成します。
ランタイムはPython、名前を[bedrock-tool]として作成します。

import boto3
import json
import os
from decimal import Decimal
import urllib.request
    
# Bedrock クライアントの初期化
bedrock_client = boto3.client('bedrock-runtime', region_name='ap-northeast-1')
model_id = "anthropic.claude-3-5-sonnet-20240620-v1:0"
    
def get_user(phone_number) -> str:
    # TODO:ユーザ情報取得のAPI実行。今回の記事では省略します。
    return "none"
    
def create_user(post_code,address,name,phone_number) -> str:
    # TODO:ユーザ情報登録のAPI実行。今回の記事では省略します。
    return "success"
    
def create_order(phone_number,order) -> str:
    # TODO:オーダー登録のAPI実行。今回の記事では省略します。
    return "success"
    
def get_claude_response(user_prompt, session_attributes):
    
    # 会話履歴の初期化
    conversation_history = ""
    
    # session_attributes から質問と回答を取得し、プロンプトを作成
    question_count = int(session_attributes.get("question_count", "0"))
    for i in range(1, question_count + 1):
        question_key = f"question{i}"
        answer_key = f"answer{i}"
        question = session_attributes.get(question_key, "")
        answer = session_attributes.get(answer_key, "")
        if question and answer:
            conversation_history += f"\n\nHuman: {question}\n\nAssistant: {answer}"
    
    # 現在のユーザー入力を追加
    conversation_history += f"\n\nHuman: {user_prompt}\n\nAssistant:"
    
    # リクエストペイロードの作成
    body = json.dumps({
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 2000,
        "temperature": 0.1,
        "top_p": 0.999,
        "top_k": 300,
        "system": """
                # 要件
                - あなたはサバワピザの注文を聞くベテランオペレータです。
                - あなたの回答の内容を音声で読み上げて回答しています。それを踏まえて回答してください。
                - あなたは高額な報酬をいただいているのでやる気に満ち満ちでいます。
                - 会話は端的に短くオペレータとして会話してください。
                - 各toolを実行するのは1回のみです。複数回実行はできません。
                - 下記のようにtoolを利用する前は必ず、確認を取るようにしてください。必要なパラメータが揃った時に確認してください。
                    - お客様のお電話番号は〇〇でよろしいでしょうか?
                    - お客様情報を確認します。お名前は〇〇様。郵便番号は〇〇。住所は〇〇でよろしいでしょうか?
                    - ご注文を確認いたします。ご注文内容は〇〇で良いでしょうか?
                
                # 注文を受ける流れ
                1. はじめにお客様の電話番号を聞いてget_userを実行します。
                2. 次に、お客様情報が存在しない場合、create_userを実行します。お客様情報が存在する場合はこのステップはスキップします。
                3. create_userが完了したら、ピザの注文を行うcreate_orderを実行します。
                """,
        "tools": [
            {
                "name": "get_user",
                "description": "お客様情報を取得します。",
                "input_schema": {
                    "type": "object",
                    "properties": {
                        "phone_number": {
                            "type": "string",
                            "description": "お客様の電話番号を指定します。例: 08012345678"
                        }
                    },
                    "required": ["phone_number"]
                }
            },
            {
                "name": "create_user",
                "description": "お客様情報を登録します。",
                "input_schema": {
                    "type": "object",
                    "properties": {
                        "post_code": {
                            "type": "string",
                            "description": "お客様の郵便番号を指定します。例: 332-0001"
                        },
                        "address": {
                            "type": "string",
                            "description": "お客様の住所を指定します。例: 東京都荒川区西日暮里1-2-3"
                        },
                        "name": {
                            "type": "string",
                            "description": "お客様の名前を指定します。例: ヤマダ タロウ"
                        },
                        "phone_number": {
                            "type": "string",
                            "description": "お客様の電話番号を指定します。例: 08012345678"
                        }
                    },
                    "required": ["post_code", "address", "name", "phone_number"]
                }
            },
            {
                "name": "create_order",
                "description": "ピザのオーダーを登録します。",
                "input_schema": {
                    "type": "object",
                    "properties": {
                        "phone_number": {
                            "type": "string",
                            "description": "お客様の電話番号を指定します。例: 08012345678"
                        },
                        "order": {
                            "type": "string",
                            "description": "オーダーを指定します。例: マルゲリータ Lサイズ 1枚"
                        }
                    },
                    "required": ["phone_number", "order"]
                }
            }
        ],
            "messages": [
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": conversation_history
                    }
                ]
            }
        ]
    })
    
    try:
        # Bedrock API の呼び出し
        response = bedrock_client.invoke_model(
            modelId=model_id,
            contentType="application/json",
            accept="application/json",
            body=body
        )
    
        # レスポンスの読み取り
        response_body = json.loads(response["body"].read().decode())
    
        # ツールが使用された場合の処理
        for content in response_body.get("content", []):
            if content.get("type") == "tool_use":
                tool_name = content.get("name")
                tool_input = content.get("input", {})
    
                if tool_name == "get_user":
                    phone_number = tool_input.get("phone_number", "")
                    if get_user(phone_number) is "none":
                        return "お客様情報が見つかりませんでした。お客様情報を登録しますのでお名前、郵便番号、住所を教えていただけますか?"
                    else:
                        return "ユーザが見つかりました。"

                elif tool_name == "create_user":
                    post_code = tool_input.get("post_code", "")
                    address = tool_input.get("address", "")
                    name = tool_input.get("name", "")
                    phone_number = tool_input.get("phone_number", "")
                    if create_user(post_code, address, name, phone_number) is "success":
                        return "ありがとうございます。お客様情報を登録しました。次にご注文するピザを教えてください。"
                    else:
                        return "お客様情報の登録に失敗しました。"
    
                elif tool_name == "create_order":
                    phone_number = tool_input.get("phone_number", "")
                    order = tool_input.get("order", "")
                    if create_order(phone_number, order) is "success":
                        return "exit"
                    else:
                        return "オーダーの登録に失敗しました。"
    
                else:
                    return "未知のツールが指定されました: {tool_name}"
    
        # 通常の応答を返す
        if "content" in response_body:
            return "\n".join([result.get("text", "") for result in response_body.get("content", [])])
        else:
            return "APIからの無効なレスポンス形式です。"
    
        completion = "\n".join([result.get("text", "") for result in response_body.get("content", [])])
        return completion
    
    except Exception as e:
        return f"エラーが発生しました: {str(e)}"
    
def main(event):
    input_text = event['inputTranscript']
    session_state = event.get('sessionState', {})
    session_attributes = session_state.get('sessionAttributes', {})
    
    # 質問と回答を保存するためのカウンタを取得
    question_count = int(session_attributes.get("question_count", "0"))
    
    # 入力が終了の場合の処理
    if input_text == "終了":
        event['messages'] = [{'contentType': 'PlainText', 'content': 'お電話ありがとうございました。電話を切ります。'}]
        event['sessionState']['dialogAction'] = {'type': 'Close'}
        return event
    
    # Claude APIを呼び出して応答を取得
    response_text = get_claude_response(input_text, session_attributes)

    # オーダー完了時の処理
    if response_text == "exit":
        event['messages'] = [{'contentType': 'PlainText', 'content': 'ご注文を承りました。それではご到着までお待ちください。ご注文ありがとうございました。'}]
        event['sessionState']['dialogAction'] = {'type': 'Close'}
        return event
    
    # 質問と回答をセッション属性に保存
    question_key = f"question{question_count + 1}"
    answer_key = f"answer{question_count + 1}"
    session_attributes[question_key] = input_text
    session_attributes[answer_key] = response_text
    session_attributes["question_count"] = str(question_count + 1)
    
    # メッセージと状態を直接設定
    event['messages'] = [{'contentType': 'PlainText', 'content': response_text}]
    event['sessionState']['dialogAction'] = {'type': 'ConfirmIntent'}
    event['sessionState']['sessionAttributes'] = session_attributes
    
    return event
    
def lambda_handler(event, context):
    
    return main(event)

2. Amazon Lex Botの作成

  • 1.Amazon Lex画面で[create bot]をクリック

  • 2.次の設定で、[Next]をクリック

  • 3.次の設定で、[Done]をクリック

3.インテント作成

NewIntentの画面に遷移するので、[utterances]に適当な値を入力、[Add utterance]をクリックし、[save intent]をクリック。
※今回インテントの代わりにtoolを使うため、このインテントは利用しません。

4.FallbackIntentの設定

FallbackIntentの画面で、[Closing response]を[Active]にして、[Use a Lambda function for initialization and validation]にチェックを入れて、[Save intent]をクリックします。

5.Lex BotにLambdaを設定

[Aliases]をクリックし、[Japanese]クリックして、先ほど作成したLambdaを設定して[Save]をクリックします。

6.ビルド

[Build]をクリックします。

7.Amazon Lexテスト

[Test]をクリックして[テスト]と入力します。
下記のように、ピザオペレータの回答があれば正常です。

8.Amazon ConnectにBotを追加

Amazon Connectのインスタンスのメニューから[問い合わせフロー]をクリックし、作成したBotを選択し、[Amazon Lexボットを追加]をクリックします。

9.フロー作成

次の問い合わせフローを作成します。

各ブロックの設定は下記のとおりです。

  • 音声の設定

  • 顧客の入力を取得する

動作確認

www.youtube.com

終わり

この記事では、Amazon Connectを活用し、生成AIのClaudeのTools機能を組み合わせて音声対話システムを構築する方法をご紹介しました。

今回は、ClaudeのTools機能を利用して、実装しましたが、Amazon Lexでも音声対話システムを構築できます。
Lexは事前定義されたフローでの対応が得意ですが、ClaudeのTools機能はAPI呼び出しを活用して柔軟な対応が可能です。
本記事でご紹介した方法を参考にしていただき、業務課題の解決にお役立ていただければ幸いです。

サーバーワークスは、生成AIとAmazon Connectを活用した顧客対応業務の効率化と顧客満足度向上を目指すPoC支援キャンペーンを開始しました。
お申し込みは2025年2月28日までで、先着10社限定となります。詳細はこちらをご覧ください。

"; doc.innerHTML = entry_notice + doc.innerHTML; }
' } }) e.innerHTML = codeBlock; });