Amazon Connect と Amazon Lex で電話スケジュール予約を学習のためにつくってみた

# はじめに

Amazon Connect を使って、音声のスケジュール予約システムを作って動かしてみました。個人の学習のために作ったので、インターネット上に公開はしていません。作りにあたって、いろいろノウハウを得たので、それを紹介します。



# 概要図

まず簡単に概要図を出します。

![image-20230402122308719.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/7a4c8708-2848-73f1-eda0-16bfeba93b97.png)



一番左の登場人物「ユーザー」を起点に、右側に利用している AWS サービスを記載しています。電話を受け付ける Amazon Connect, お客様とのコミュニケーションを支援する Amazon Lex, スケジュールを確認・予約するための Lambda 関数, データストアとして DynamoDB があります。

ユーザーは Amazon Connect で管理している電話番号にかけることで、自動音声が流れます。自動音声の中でスケジュールの希望日を伝えることや、ボタンを押すことで予約が可能です。



それぞれのサービスの設定方法を紹介しましょう。



# Amazon Connect

Amazon Connect の管理画面で、新しい contact flow を作成します。

![image-20230326165726913.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/53773133-1847-4edf-81c3-5f47635bf75a.png)



Set voice を配置し、日本語の読み上げを有効化します。

- Voice : Kazuha
- Set language attribute : ON 

![image-20230402120236101.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/18db5c43-977a-e30f-ec4e-5dd0cbbb9030.png)



次に、Customer Input を配置し、ここから Amazon Lex を呼びだします。

![image-20230402120428136.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/d4caf458-521c-a9d7-be1f-4065a271a1b3.png)



呼び出す Lex Bot を指定します。以下の画像にある `try1` と書かれているのが、LexBot の名前です。自分で作成した Bot を指定してください。

![image-20230402120557537.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/1fe3b56e-d501-4e28-636e-9a31a6608230.png)



Amazon Connect の Flow はこんな感じです。

![image-20230402120703136.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/cf7a17ba-a771-b5e2-8e78-be4519c5e30d.png)



# Amazon Lex

## Lex Bot の作成

Amazon Lex のページを開き、Create Bot を押します。

![image-20230326134742975.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/cfc5abf3-bf39-fe3e-91ac-2448327e7764.png)



Bot の名前を適当に指定します。

![image-20230402120851594.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/d6bc603d-fc4b-62aa-0759-88a5506c44e1.png)



適当に IAM Role を選択して、Next を押します。

![image-20230326134942466.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/3710ae89-4e1a-b032-c32f-6bfa04ae4a75.png)



Lex 上で利用される言語や、Voice interaction を選びます。Amazon Connect と一致させるために、Kazuha を選びます。

![image-20230326135058640.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/bca6aefb-45db-9e93-9e17-9bb72575a3b1.png)



Lex Bot が作成されました。

![image-20230326135125398.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/5bb0469a-ee8a-3048-a160-f7336a7aacea.png)



## Lex Intent の作成

作成した Bot 上で、新たな Intent を作成します。

![image-20230402121227158.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/3623e673-fa2e-1ba8-02cb-8a9ab3117bb3.png)



いきなり全体像を出しますが、このように Intents の Flow を作成します。

- utterances は「1」とする。Amazon Connect 側で「スケジュール予約を希望ですか？希望の場合は 1 を押してください」とガイドが流れる。その後ユーザーが 1 を押すことで、この Intents に紐づけがされる。
- Slot は 2 つ
- それぞれの Slot にユーザー側の入力を受け付けた時、Lambda 関数を動かす

![image-20230402121357238.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/4b28d68b-fae1-dd8e-e835-825660511b80.png)



Lex Bot と Lambda 関数を紐づけるために、Alias のページを選択します。

![image-20230402122749505.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/39c6cdf5-ce35-acdf-dce9-bd5e4f8588d5.png)



Japanese を選びます

![image-20230402122813312.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/4e5f8b9e-3edc-e9fa-1e8e-e6ffa83c453b.png)



ここで紐づける Lambda 関数を指定します。

![image-20230402122843254.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/e6ff0250-bb55-59d7-3f7d-a7b5a5e90c31.png)



Intent 側の設定で、次のチェックも必要です。

![image-20230326190151011.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/d1cb9825-c844-f7ef-9d2d-15c337aa3def.png)



# Lambda 関数

スケジュール予約を司る重要な Lambda 関数です。Lex で Lambda 関数を利用するうえで、理解する概念があります。Amazon Lex の Bot に Lambda 関数を紐づけられるのですが、基本的には  1 対 1 で紐づきがされます。Bot 側で複数の Intent, 複数の Slot が有る場合は、どの Intent, Slot で呼び出されたのかを Lambda コード内で意識する必要があります。「この Intent の、この Slot だから、この処理をしよう」といったように制御が必要です。

![image-20230402123846202.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/208131af-928d-2f6e-8843-43b130d27f15.png)

この記事では、Intent が 1 個なので Intent については固定にしています。Slot は複数の Slot があるため、Lambda 関数内の `sessionAttributes.nextSlot` で管理をしています。Lex から呼び出された Lambda 関数内で `sessionAttributes` を指定することで、次に呼び出される Lambda に指定した文字列を受け渡すことが出来ます。`sessionAttributes.nextSlot`に次処理するべき Slot 名を指定することで、呼び出された Lambda 関数側で適切な処理が出来るようにしています。

　

`sessionAttributes.nextSlot` に関係する部分を一部抜粋します。Lambda 関数が受け取った event の中に、`sessionAttributes.nextSlot` が格納されています。空白の場合は初回呼び出しだと判断して、「Date」スロットを処理します。`sessionAttributes.nextSlot`が「Schedule」のときは、「Schedule」スロットの処理に移ります。

```python
def router(event):
    # sessionAttributes の nextSlot を取得。
    # sessionAttributes の nextSlot は、この Lambda 関数が処理するべき Slot が示されている。
    # Lambda 関数内で sesstionAttributes を設定すると、次に呼び出される Lambda 関数に引き継がれる。
    # 空白の場合は、この Intent が初めて呼びだされた実行なり、「Date」Slot を処理するべきものと識別する。(想定外なものがあるかもしれない)
    if "nextSlot" in event['sessionState']['sessionAttributes']:
        nextSlot = event['sessionState']['sessionAttributes']['nextSlot']
    else:
        nextSlot = date_slotname

    # 処理すべき Slot が 「Date」のとき
    if nextSlot == date_slotname:
        # 省略
        # sessionAttributes の nextSlot に次処理すべき Slot の名前を入れる。
        sessionAttributes = {
            "nextSlot": schedule_slotname
        }
        # 省略
    # 処理すべき Slot が 「Schedule」のとき
    if nextSlot == schedule_slotname:
        # 省略

def lambda_handler(event, context):
    print("============ print event ============")
    logger.info(json.dumps(event))

    response = router(event)
    return response
```



お客様の予約希望日を電話で教えてもらったあとに、具体的な予約可能な候補日時をレスポンスするためのコードが以下です。お客様の音声から認識された日付文字列が `event['sessionState']['intent']['slots']['Date']['value']['interpretedValue']` にはいっています。これを取得して DynamoDB 上で検索をします。

検索して取得してきた日付のうち、`free` 列が `true` のものを実際の候補としてお客様に読み上げます。

```python
    # 処理すべき Slot が 「Date」のとき
    if nextSlot == date_slotname:
        slots = event["sessionState"]["intent"]["slots"]
        name = event["sessionState"]["intent"]["name"]

        # お客様の希望のスケジュールを取得
        nextSlot = event['sessionState']['intent']['slots']['Date']['value']['interpretedValue']

        # DynamoDB から候補スケジュールを取得
        response = table.query(
            KeyConditionExpression=Key('busyoid').eq('busyo1')
            & Key('date-start').between(nextSlot + ' 00:00:00', nextSlot + ' 23:59:59')
        )

        items = response['Items']
        free_schedule_dict = {}

        # 候補スケジュールの読み上げテキストを生成
        if len(items) == 0:
            messagesContent = "候補の日は空きのスケジュールがありませんでした"
        else:
            messagesContent = "空きスケジュールを音声で読み上げます。希望の番号を押してください。" + SSML_break
            guidenumber = 1
            for item in items:
                if item['free']:
                    free_schedule_dict[guidenumber] = item['date-start']
                    messagesContent += str(guidenumber) + \
                        SSML_break + item['date-start'] + SSML_break
                    guidenumber = guidenumber + 1
```



Lambda が return するときに、重要なのが以下のポイントです。Lambda が return する値によって、Lex 側の動作指定ができます。`dialogAction.type` を `ElicitSlot` と指定し、`"slotToElicit": "Schedule"    ` を return しています。こうすることで、この Lambda 関数の処理実行後に Lex Bot 側の動作を指定できます。「Schedule というスロット名を、つぎに処理せよ」という命令になっています。これによって、例えば正常時にはスロット A に移動して、異常時にはもう一度繰り返す、みたいな制御が可能です。

詳細が気になる方は、[こちらの Document](https://docs.aws.amazon.com/lexv2/latest/dg/lambda.html) を参照してください。

```python
        session_state = {
            "dialogAction": {
                "type": "ElicitSlot",
                "slotToElicit": "Schedule"                
            },
            "intent": {
                "confirmationState": "Confirmed",
                "slots": slots,
                "name": name
            },
            "sessionAttributes": sessionAttributes
        }
        # 省略
        # return response
        response = {
            "sessionState": session_state,
            "messages": messages,
            "requestAttributes": request_attributes,
            "sessionId": sessionid
        }

        print("============ print response ============")
        logger.info(json.dumps(response))

        return response
```



Lambda 関数が「Schedule」スロットの時に呼ばれるときの処理です。お客様の希望スケジュールを `event['sessionState']['intent']['slots']['Schedule']['value']['interpretedValue']` から受け取り、実際に DynamoDB へ予約を行います。

```python
    # 処理すべき Slot が 「Schedule」のとき
    if nextSlot == schedule_slotname:
        slots = event["sessionState"]["intent"]["slots"]
        name = event["sessionState"]["intent"]["name"]

        # お客様の希望のスケジュールを取得
        guidenumber = event['sessionState']['intent']['slots']['Schedule']['value']['interpretedValue']
        startdate = event['sessionState']['sessionAttributes'][guidenumber]

        # DynamoDB のデータを更新
        key = {
            'busyoid': 'busyo1',
            'date-start': startdate
        }

        # 更新する項目の設定を指定
        update_expression = 'SET #f = :val1'
        expression_attribute_names = {'#f': 'free'}
        expression_attribute_values = {':val1': False}

        response = table.update_item(
            Key=key,
            UpdateExpression=update_expression,
            ExpressionAttributeNames=expression_attribute_names,
            ExpressionAttributeValues=expression_attribute_values
        )
```





# DynamoDB

DynamoDB のテーブル構成を以下のように組みます。簡易的な動作試験なので、6/7 と  6/8 のデータしか入れません。free が true の行がスケジュールが空いていて、false の行がスケジュールが埋まっていることを示しています。

この記事では、時間枠は固定になっています。

![image-20230402130443342.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/7b73517a-2b96-c771-277d-a56cd79a4262.png)



DynamoDB のテーブルを作成します。

![image-20230330125603255.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/35aac9dc-18e7-ea11-3c8c-a7ab53caa9d8.png)



Table 構成を指定します。

![image-20230330214814626.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/6def9ac7-1942-ca47-68b5-7dcddaac9ea5.png)



Create Table を押します。

![image-20230330214824536.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/606245a4-f7b3-84b7-45f1-c6571a732ea1.png)



実際のデータは、Python コードから書き込みます。以下のコードを実行すれば、DynamoDB テーブル上に item が作成されます。

```python
import sys
import os
import boto3
from boto3.dynamodb.conditions import Key

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('connect-lex-schedule-booking')


def main():
    truncate_dynamo_items(table)
    create_dynamo_items(table)


def create_dynamo_items(dynamodb_table):
    response = table.put_item(
    Item={
            'busyoid': 'busyo1',
            'date-start': '2023-06-07 10:00:00',
            'date-end': '2023-06-07 12:00:00',
            'personid': 'suzuki',
            'free': False
        }
    )

    response = table.put_item(
    Item={
            'busyoid': 'busyo1',
            'date-start': '2023-06-07 13:00:00',
            'date-end': '2023-06-07 15:00:00',
            'personid': 'suzuki',
            'free': True
        }
    )

    response = table.put_item(
    Item={
            'busyoid': 'busyo1',
            'date-start': '2023-06-07 15:00:00',
            'date-end': '2023-06-07 17:00:00',
            'personid': 'suzuki',
            'free': True
        }
    )

    response = table.put_item(
    Item={
            'busyoid': 'busyo1',
            'date-start': '2023-06-08 10:00:00',
            'date-end': '2023-06-08 12:00:00',
            'personid': 'suzuki',
            'free': True
        }
    )

    response = table.put_item(
    Item={
            'busyoid': 'busyo1',
            'date-start': '2023-06-08 13:00:00',
            'date-end': '2023-06-08 15:00:00',
            'personid': 'suzuki',
            'free': True
        }
    )

    response = table.put_item(
    Item={
            'busyoid': 'busyo1',
            'date-start': '2023-06-08 15:00:00',
            'date-end': '2023-06-08 17:00:00',
            'personid': 'suzuki',
            'free': True
        }
    )

def truncate_dynamo_items(dynamodb_table):
    
    # データ全件取得
    delete_items = []
    parameters   = {}
    while True:
        response = dynamodb_table.scan(**parameters)
        delete_items.extend(response["Items"])
        if ( "LastEvaluatedKey" in response ):
            parameters["ExclusiveStartKey"] = response["LastEvaluatedKey"]
        else:
            break

    # キー抽出
    key_names = [ x["AttributeName"] for x in dynamodb_table.key_schema ]
    delete_keys = [ { k:v for k,v in x.items() if k in key_names } for x in delete_items ]

    # データ削除
    with dynamodb_table.batch_writer() as batch:
        for key in delete_keys:
            batch.delete_item(Key = key)

    return 0


if __name__ == '__main__':
    ret = main()
    sys.exit(ret)
```



データが格納されました。

![image-20230330220406237.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/49c17316-1553-c983-f0d2-bdaad38b8e10.png)



# 動作確認

本来であれば Amazon Connect の電話している様子をお見せできればいいのですが、録画環境が無かったので Lex 上の文字でやりとりを行います。

DynamoDB 上の次のテーブルを予約していきます。

![image-20230402131034436.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/4d265df3-9784-5642-81e8-f4990dc4820c.png)



Lex のテストを開き、予約の希望を伝えます。すると、候補日を 3 つ教えてくれるので、「1」 を入力します。

![image-20230402133052202.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/8e2e4b28-a296-1483-cb0d-8d310989b001.png)



「1」を入力すると予約が完了しました。

![image-20230402133108872.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/3f9ed75d-8a60-a6de-43e5-14ca0eb2fad0.png)



DynamoDB 上でも free が false となっていることがわかりました。

![image-20230402133134352.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/e5e8f2fc-c9d2-18ba-cc7a-bb7d26498f9d.png)



# 検証を通じてわかったこと

- Amazon Connect から Lex を呼びだすときに、utterances をお客様に入力してもらう必要がある。「スケジュール予約の場合は 1 を押してください」といったガイドを出すことで、スムーズな utterances を引き出すことが可能
- Amazon Connect から Lambda 関数を呼び出すときのタイムアウトは最大 8 秒。Lambda 関数側で処理が遅くならないよう注意が必要。データベースの読み書きなど。
- 1 個の Lex Alias に、1 個の Lambda 関数が紐づく。Lambda 関数の実装面で「いまはどの Intent なのか」「いまはどの Slot なのか」を理解しながら処理のルーティングを制御しないといけない。Intent や Slot ごとに Lambda 関数を紐づけられるわけではないのが、ちょっと注意。
- Lexv2 から呼び出される Lambda 関数で生成したメッセージを Lexv2 側に表示したいときは、dialogAction Type が `Delegate` だと動作しなかった。`ElicitSlot` だと動作した。



# 付録1 : Lambda のソースコード

```python
import json
from logging import getLogger, INFO
import boto3
from boto3.dynamodb.conditions import Key

logger = getLogger(__name__)
logger.setLevel(INFO)

booking_schedule_intentname = "Booking-Schedule-Intent"
date_slotname = "Date"
schedule_slotname = "Schedule"
SSML_break = "<break time=\"500ms\"/>"

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('connect-lex-schedule-booking')

def router(event):
    # Lambda が起動された Intent 名を取得
    intent_name = event['sessionState']['intent']['name']

    # 想定外の Intent の場合は、なにもせず終了
    if intent_name != booking_schedule_intentname:
        return

    # sessionAttributes の nextSlot を取得。
    # sessionAttributes の nextSlot は、この Lambda 関数が処理するべき Slot が示されている。
    # Lambda 関数内で sesstionAttributes を設定すると、次に呼び出される Lambda 関数に引き継がれる。
    # 空白の場合は、この Intent が初めて呼びだされた実行なり、「Date」Slot を処理するべきものと識別する。(想定外なものがあるかもしれない)
    if "nextSlot" in event['sessionState']['sessionAttributes']:
        nextSlot = event['sessionState']['sessionAttributes']['nextSlot']
    else:
        nextSlot = date_slotname

    # 処理すべき Slot が 「Date」のとき
    if nextSlot == date_slotname:
        slots = event["sessionState"]["intent"]["slots"]
        name = event["sessionState"]["intent"]["name"]

        # お客様の希望のスケジュールを取得
        nextSlot = event['sessionState']['intent']['slots']['Date']['value']['interpretedValue']

        # DynamoDB から候補スケジュールを取得
        response = table.query(
            KeyConditionExpression=Key('busyoid').eq('busyo1')
            & Key('date-start').between(nextSlot + ' 00:00:00', nextSlot + ' 23:59:59')
        )

        items = response['Items']
        free_schedule_dict = {}

        # 候補スケジュールの読み上げテキストを生成
        if len(items) == 0:
            messagesContent = "候補の日は空きのスケジュールがありませんでした"
        else:
            messagesContent = "空きスケジュールを音声で読み上げます。希望の番号を押してください。" + SSML_break
            guidenumber = 1
            for item in items:
                if item['free']:
                    free_schedule_dict[guidenumber] = item['date-start']
                    messagesContent += str(guidenumber) + \
                        SSML_break + item['date-start'] + SSML_break
                    guidenumber = guidenumber + 1

        # Lex に渡すメッセージを生成する。この content が Lex で認識されて読み上げられる。 
        messages = [
            {
                "contentType": "SSML",
                "content": "<speak>" + messagesContent + "</speak>"
            }
        ]

        # sessionAttributes の nextSlot に次処理すべき Slot の名前を入れる。
        sessionAttributes = {
            "nextSlot": schedule_slotname
        }

        for guidenumber, datestart in free_schedule_dict.items():
            sessionAttributes[guidenumber] = datestart
            

        session_state = {
            "dialogAction": {
                "type": "ElicitSlot",
                "slotToElicit": "Schedule"                
            },
            "intent": {
                "confirmationState": "Confirmed",
                "slots": slots,
                "name": name
            },
            "sessionAttributes": sessionAttributes
        }

        request_attributes = event["requestAttributes"] if "requestAttributes" in event else {
        }

        sessionid = event["sessionId"] if "sessionId" in event else {
        }



        # return response
        response = {
            "sessionState": session_state,
            "messages": messages,
            "requestAttributes": request_attributes,
            "sessionId": sessionid
        }

        print("============ print response ============")
        logger.info(json.dumps(response))

        return response
    
    # 処理すべき Slot が 「Schedule」のとき
    if nextSlot == schedule_slotname:
        slots = event["sessionState"]["intent"]["slots"]
        name = event["sessionState"]["intent"]["name"]

        # お客様の希望のスケジュールを取得
        guidenumber = event['sessionState']['intent']['slots']['Schedule']['value']['interpretedValue']
        startdate = event['sessionState']['sessionAttributes'][guidenumber]

        # DynamoDB のデータを更新
        key = {
            'busyoid': 'busyo1',
            'date-start': startdate
        }

        # 更新する項目の設定を指定
        update_expression = 'SET #f = :val1'
        expression_attribute_names = {'#f': 'free'}
        expression_attribute_values = {':val1': False}

        response = table.update_item(
            Key=key,
            UpdateExpression=update_expression,
            ExpressionAttributeNames=expression_attribute_names,
            ExpressionAttributeValues=expression_attribute_values
        )

        # Lex に渡すメッセージを生成する。この content が Lex で認識されて読み上げられる。
        messages = [
            {
                "contentType": "PlainText",
                "content": "無視されるメッセージ。dialogAction.Delegate の場合は、この messages は読み上げられない"
            }
        ]

        session_state = {
            "dialogAction": {
                "type": "Delegate"
            },
            "intent": {
                "confirmationState": "Confirmed",
                "slots": slots,
                "name": name
            },
            "state": "Fulfilled"
        }

        request_attributes = event["requestAttributes"] if "requestAttributes" in event else {
        }

        sessionid = event["sessionId"] if "sessionId" in event else {
        }

        # return response
        response = {
            "sessionState": session_state,
            "messages": messages,
            "requestAttributes": request_attributes,
            "sessionId": sessionid
        }

        print("============ print response ============")
        logger.info(json.dumps(response))

        return response
    # 想定外の sessionAttributes.nextSlot
    else:
        return None



def lambda_handler(event, context):
    print("============ print event ============")
    logger.info(json.dumps(event))

    response = router(event)
    return response
```





# 参考 URL

Lambda 関数で Amazon Connect の変数を指定
https://qiita.com/novelworks/items/8b5cd9e0dae9a472c966

[Amazon Lex] Amazon Lexが日本語対応となったので、Amazon Connectから使用して居酒屋の電話予約をボット化してみました
https://dev.classmethod.jp/articles/amazon-lex-with-amazon-connect/

【Amazon Connect+LexV2】Botによる自動注文受付実装例
https://blog.serverworks.co.jp/amazon-connect-lex-order

【Amazon Connect+LexV2】Lambdaを使用した動的な自動応答実装例
https://blog.serverworks.co.jp/amazon-connect-lex-dynamic-content

Lex + Lambda で年齢のバリデーション
https://qiita.com/hirai-11/items/58f524be7cc5d9ca2093

曖昧な時間の確認
https://qiita.com/hirai-11/items/6caa99f83b85d7863d86

電話番号を Lambda 関数に渡す
https://qiita.com/hirai-11/items/3d81a9a5a9131d270d4d

予約可能時間のバリデーション
https://dev.classmethod.jp/articles/slots-value-validation-on-lex/

Amazon Lexv2 と Lambda 関数
https://docs.aws.amazon.com/ja_jp/lexv2/latest/dg/lambda.html
https://docs.aws.amazon.com/ja_jp/lexv2/latest/dg/paths-code-hook.html

Lex から Lambda を呼びだす
https://repost.aws/ja/knowledge-center/lex-dialogflow-fulfillment-lambda

Amazon Lex Workshop
https://catalog.us-east-1.prod.workshops.aws/workshops/94f60d43-15b7-45f4-bbbc-17889ae64ea0/en-US/banker-bot/create-first-lambda

初期のインテント指定できそう
https://blog.usize-tech.com/visual-builder-on-amazon-lex/

Amazon Connect + Amazon Lex + Lambda を連携する
https://ac.geekfeed.co.jp/amazon-lex-lambda-data-link/

Lex から呼び出される Lambda のサンプルコード
https://github.com/aws-samples/amazon-lex-v2-lambdahook-for-booktripbot/blob/main/lambda_function.py

Lexv1 の Document : ElicitSlot の詳細な説明が載っている。Lexv2 でも考え方は参考になりそうかも
https://docs.aws.amazon.com/ja_jp/lex/latest/dg/lambda-input-response-format.html