【Amazon Connect+LexV2】Botによる自動注文受付実装例

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

前回は下記ポストでAmazon ConnectとLexV2を組み合わせ、動的な自動応答の仕組みを試しました。
今回は機能を追加し、自動注文の仕組みを作ってみます。

blog.serverworks.co.jp

概要

前回作成したAmazon Connect + Amazon LexBot + AWS Lambdaの仕組みをベースとし、修正していくスタイルとします。

会話シナリオ

カタログを見ながらショッピングサイトへ電話注文する、というストーリーです。
商品番号と数量を話す、またはプッシュして注文する仕様とします。

下記のような会話シナリオを実現します。

動作・発話
顧客 (電話をかける)
自動応答(Connect) はい、サバワショッピングです。どのようなご用件でしょうか?
顧客 注文を追加
自動応答(LexBot) 商品番号をお願いします
顧客 101
自動応答(LexBot) 数量をお願いします
顧客 4
自動応答(LexBot) おいしい麦茶2リットル、数量4、800円です。よろしいですか?
顧客 はい
自動応答(LexBot) ありがとうございます。注文を登録しました
顧客 注文を追加
自動応答(LexBot) 商品番号をお願いします
顧客 (プッシュ操作)201
自動応答(LexBot) 数量をお願いします
顧客 (プッシュ操作)2
自動応答(LexBot) やわらかマスク50枚入り、数量2、1000円です。よろしいですか?
顧客 はい
自動応答(LexBot) ありがとうございます。注文を登録しました
顧客 注文を確定
自動応答(LexBot) 合計代金は1800円です。ご注文ありがとうございました
自動応答(Connect) 他にもご用件はありますか?
顧客 いいえ
自動応答(LexBot) ご利用いただき、ありがとうございました(切断)

構築手順

Amazon Lex V2

インテントを2つ追加します。
複数商品を注文できるよう、「注文追加」インテントで明細を追加、「注文確定」インテントで注文を確定するフローを想定しています。

「注文追加」インテントの追加

サイドメニューツリーから [インテント] を選択し、 [インテントを追加] [空のインテントを追加] をクリックします。

f:id:swx-shinsaka:20220210150548p:plain

インテントの設定(インテントの詳細)

インテント名として AddOrder を指定します。

f:id:swx-shinsaka:20220215113126p:plain

インテントの設定(サンプル発話)

注文を追加します と指定します。顧客の発話がマッチした場合に AddOrder インテント機能が開始することになります。

f:id:swx-shinsaka:20220215113323p:plain

インテントの設定(スロット)

商品番号と数量を設定します。
それぞれ、 ProductNumberQuantity と名前を指定します。
スロットタイプはどちらも数字の入力である [AMAZON.Number] を指定し、必須入力とします。

f:id:swx-shinsaka:20220210152830p:plain

インテントの設定(確認プロンプトと応答拒否)

今回は指定しません。
(Lambda側で判定して制御します)

インテントの設定(フルフィルメント)

フルフィルメントはスロット情報がすべて揃った時点で実行される設定です。
[フルフィルメントが成功した場合] の三角をクリックして展開、 [詳細オプション] をクリックします。
詳細オプション→[フルフィルメントに Lambda 関数を使用] をチェック、 [更新オプション] をクリックします。

f:id:swx-shinsaka:20220210153605p:plain

f:id:swx-shinsaka:20220210153701p:plain

インテントの設定(応答を閉じる)

今回は指定しません。
(Lambda側で判定して制御します)

インテントの設定(応答を閉じる、コードフック)

[初期化と検証に Lambda 関数を使用] をチェックします。

f:id:swx-shinsaka:20220210153854p:plain

インテントを保存

ここまで設定したら [インテントを保存] します。

f:id:swx-shinsaka:20220210154032p:plain

「注文確定」インテントの追加

インテントリストページへ戻り、もう一度 [空のインテントを追加] 操作をおこないます。

インテントの設定(インテントの詳細)

インテント名として ConfirmOrder を指定します。

f:id:swx-shinsaka:20220215114627p:plain

インテントの設定(サンプル発話)

注文を確定します と指定します。

f:id:swx-shinsaka:20220215114752p:plain

インテントの設定(スロット)

確定確認のみのため、指定しません。

インテントの設定(確認プロンプトと応答拒否)

指定しません。

インテントの設定(フルフィルメント)

「注文追加」インテントと同様、 [詳細オプション] → [フルフィルメントに Lambda 関数を使用] をチェックします。

インテントの設定(応答を閉じる)、インテントの設定(応答を閉じる、コードフック)

指定しません。

インテントを保存、構築

ここまで設定したら [インテントを保存] し、続けて [構築] をクリックします。

AWS Lambda

LexBotから呼び出されるLambda関数を作成します

関数の修正

マネジメントコンソール:AWS Lambda画面から前回作成した SabawaShopSupport 関数を開きます。

lexv2_utils.py を追加

今回はLexV2用の処理をまとめたコードを準備しました。これをコピーして使用します。

Amazon Lex V2 Response Utility Class · GitHub

新しいコードを作成、上記コードをコピーペーストし、 lexv2_utils.py というファイル名で保存します。

f:id:swx-shinsaka:20220213121415p:plain

lambda_function.py を修正

前回部分について新しい処理を利用するよう変更し、今回追加の発注部分について実装したソースコードを作成しました。
下記をコピーペーストしてLambda関数をデプロイします。

インテントごとにクラスを実装するパターンになっています。 少し説明すると、既存の注文状況確認は CheckOrder クラス、新規の注文追加は AddOrder クラス、 注文確定は ConfirmOrder クラスへそれぞれ実装し、呼び出されたインテントによって各クラスをインスタンス化、 invoke() メソッド実行によってリクエストに応じた応答を返す、という仕組みになっています。
また、Webアプリケーションのセッション保持に相当する機能として利用できるセッション属性(SessionAttributes)へ注文内容を保存するように実装し、インテントをまたいだ情報保持を実現しています。
今回は orders というセッション属性キーへ、注文明細を保持するようにしました。
(サンプルですので、完全ではないことにご注意ください)

import json
from lexv2_utils import IntentBase


def lambda_handler(event, context):
    print('in:' + json.dumps(event))
    # インテントによって分岐する
    intent_name = event['sessionState']['intent']['name']
    if intent_name == 'CheckOrder':
        return CheckOrder(event).invoke()
    elif intent_name == 'AddOrder':
        return AddOrder(event).invoke()
    elif intent_name == 'ConfirmOrder':
        return ConfirmOrder(event).invoke()


class CheckOrder(IntentBase):
    def do_fulfillment(self) -> dict:
        (customer_name, order_item, delivery_date) = self.get_order()
        message = {
            'contentType': 'PlainText',
            'content': (
                f'{customer_name}様、'
                f'ご注文の {order_item} は、'
                f'{delivery_date}、お届けの予定です')}
        return self.fulfilled_close(message)

    def get_order(self):
        # バックエンドシステムとの連携を実装(今回はダミー)
        return (
            '鈴木',
            'ミネラルウォーター1ケース',
            '明日')


class AddOrder(IntentBase):
    def _get_order_info(self):
        product_number = self.get_intent_slot_interpreted_value('ProductNumber')
        product = self.get_product(product_number)
        if not product:
            return None
        quantity = int(self.get_intent_slot_interpreted_value('Quantity'))
        product.update(quantity=quantity)
        product.update(amount=product['price'] * quantity)

        return product

    def do_fulfillment_confirm_none(self) -> dict:
        product = self._get_order_info()
        if not product:
            message = {
                'contentType': 'PlainText',
                'content': '登録されていない商品番号です。もう一度商品番号をお願いします'}
            return self.elicit_slot(message, 'ProductNumber')

        message = {
            'contentType': 'PlainText',
            'content': f'{product["name"]}、数量{product["quantity"]}、{product["amount"]}円です。よろしいですか?'}
        return self.fulfilled_confirm(message)

    def do_fulfillment_confirmed(self) -> dict:
        product = self._get_order_info()
        orders = self.get_session_value_json('orders') or []
        orders.append(product)
        self.set_session_value_json('orders', orders)

        message = {
            'contentType': 'PlainText',
            'content': 'ありがとうございます、注文を登録しました'}
        return self.fulfilled_close(message)

    def do_fulfillment_denied(self) -> dict:
        message = {
            'contentType': 'PlainText',
            'content': '注文を取り消しました'}
        return self.fulfilled_close(message)

    def get_product(self, product_number):
        products = [
            {
                'code': '101',
                'name': 'おいしい麦茶2リットル',
                'price': 200},
            {
                'code': '201',
                'name': 'やわらかマスク50枚入り',
                'price': 500}]
        for product in products:
            if product['code'] == product_number:
                return product

        return None


class ConfirmOrder(IntentBase):
    def do_fulfillment(self) -> dict:
        orders = self.get_session_value_json('orders')
        if not orders:
            message = {
                'contentType': 'PlainText',
                'content': 'ご注文がありません。注文を追加してください'}
        else:
            total_amount = sum([order.get('amount') for order in orders])
            message = {
                'contentType': 'PlainText',
                'content': f'合計代金は {total_amount} 円です。ご注文ありがとうございました'}

        return self.fulfilled_close(message)

Amazon Lex V2

Lexの画面へ戻り、本番用バージョンをデプロイします。
(Lambda利用設定は前回実施してあるのでそのままにします)

バージョンを作成

デプロイ対象となるバージョン2を作成します

f:id:swx-shinsaka:20220215153246p:plain

f:id:swx-shinsaka:20220215153518p:plain

バージョンをエイリアスに関連付け

作成したバージョン2をエイリアス Production へ関連付けします

f:id:swx-shinsaka:20220215153820p:plain f:id:swx-shinsaka:20220215154018p:plain f:id:swx-shinsaka:20220215154203p:plain

Amazon Connect

前回作成した問い合わせフローを修正します。

完成形

矢印で示した2か所のブロックが変更箇所です。

f:id:swx-shinsaka:20220215155807p:plain

顧客の入力を取得する

Amazon Lex との連携指示ブロックです。変更点は2つあります。

ひとつ目は [セッション属性] 部分です。

項目 設定
属性を使用する 選択
宛先キー orders
タイプ Lex属性
属性 orders

Lexで注文明細を保持するように実装したセッション属性キー orders を指定します。
(Lexのアウトプット orders を再度Lexのインプット orders へ渡す指定になります)

ふたつ目は [インテント] 部分で、今回の新機能である AddOrderConfirmOrder を追加します。
保存後、 ConfirmOrder の分岐先を、既存の [プロンプトの再生] へ接続します。

f:id:swx-shinsaka:20220215190946p:plain

プロンプトの再生

AddOrder からの分岐先ブロックを作成します。
今回のシナリオでは特に発話する必要がないので、無音指定しています。
ブロックは再度 [顧客の入力を取得する] へ接続します。

公開

問い合わせフローを修正後、 [公開] します。

動作確認

手元の電話から発信し、自動応答をテストします。
デモ動画をご覧ください。

youtu.be

まとめ

今回は、定型的な注文を想定した機能実装を試してみました。
このような注文受付はスマホアプリやWebで実現するのが便利だとは思いますが、依然として電話・音声で受付を選択されるお客様への対応としてはニーズがあるのではないでしょうか。

ご参考になれば幸いです。