Amazon Lexで受け答えした情報を、Lambdaを用いてDynamoDBに書き込む


# はじめに
Lexで答えた情報をLambdaを用いて、DynamoDBに書き込むことをやります。

![スクリーンショット 2022-01-15 15.58.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/ca13abec-3ae8-6ab0-3657-9db538a92b5d.png)


# 流れ
1. DynamoDBのテーブル作成
1. Lex作成
1. Lambda作成
1. LexにLambdaを設定


# 
DynamoDBのテーブル作成
以下の設定で作成します。
テストですので、オンデマンドモードにして、安く済ませます。

- テーブル名：`Lex-Write-Lambda`
- パーティションキー：`telephoneNumber`：`文字列`
- ソートキー：`date`：`文字列`
- 項目：`age`：`数値`　（テーブル作成後）
- 項目：`timeStamp`：`数値`　（テーブル作成後）
- テーブルクラス：DynamoDB 標準 – IA
- キャパシティーモード：オンデマンド
![スクリーンショット 2022-01-15 16.07.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/a77fd532-ed18-d57b-c76f-98b62b0995fc.png)

`age`と`timeStamp`を含めた項目を作成します。

![スクリーンショット 2022-01-15 19.50.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/00340616-be9f-e051-ca3d-90a0f08c3907.png)


# Lexの作成
## bot作成
botを作成します。
名前は適当に作成し、言語は日本語に設定します。それ以外はデフォルトのままです。

![スクリーンショット 2022-01-15 16.15.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/7e12beeb-4d92-fa96-3ad4-36d575af7b33.png)
![スクリーンショット 2022-01-15 16.15.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/461bd409-5f84-95df-9f0e-4c0f0d883e86.png)
![スクリーンショット 2022-01-15 16.16.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/947229fa-1100-f48e-263a-526e250cdd1d.png)

## インテント作成
インテント名は、`book`とします。
lexとの会話の流れは以下の通りです。
予約したい日付と電話番号と年齢の情報をlexに渡します。

user「予約したいです。」
　lex「あなたの電話番号を教えて下さい」
user「09011111111」
　lex「予約したい日付を教えて下さい」
user「明日」
　lex「年齢を教えて下さい？」
user「22歳です」
　lex「2022-01-16に、電話番号09011111111の年齢22歳で予約します。よろしいでしょうか？よろしければ、はい、予約をキャンセルしたい場合、いいえとお伝え下さい」
user「はい」
　lex「予約を完了しました」

### バリデーションもかけます。
年齢20未満は利用できないようにする。

user「予約したいです。」
　lex「あなたの電話番号を教えて下さい」
user「09011111111」
　lex「予約したい日付を教えて下さい」
user「明日」
　`lex「年齢を教えて下さい？」`
`user「18歳です」`
　`lex「ご利用は20歳以上とさせていただいております。」`
`user「25歳です」`
　lex「2022-01-16に、電話番号09011111111の年齢25歳で予約します。よろしいでしょうか？よろしければ、はい、予約をキャンセルしたい場合、いいえとお伝え下さい」
user「はい」
　lex「予約を完了しました」

### 発話
サンプル発話は、bookインテントが発動するトリガーとなる言葉を設定します。
![スクリーンショット 2022-01-15 16.23.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/c0820749-a7dc-b877-bdf5-bdd703587286.png)

### スロット
まず、telephoneNumberを以下の設定で作成します。
![スクリーンショット 2022-01-15 16.36.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/0f20573f-4524-0574-f687-ba0ab2298de1.png)


ここでポイントですが、　`スロットタイプ：AMAZON.PhoneNumber`は、番号のみを聞き取ります。
userが数値のみを言うと、Lexは聞き取ってくれますが、`userが「09011111111です。」と文字列も含めて答えると、Lexは聞き取れなくなります。`

そのため、telephoneNumberスロットの`詳細オプション`→`サンプル発話 -オプション`で、`{telephoneNumber}です。」と設定することで、文字列の`です`を省いた数値を聞き取ってくれるようになります。


![スクリーンショット 2022-01-15 16.33.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/52c5d16c-6b94-a9e9-205a-df83b19e3f1e.png)

#### 注意
スロットを更新を押した後、**スロットのプロンプト等を修正すると、先程設定した`サンプル発話 -オプション`が削除される場合がありますので、再度入力して更新してください。**

同様に、`date`と`age`のスロットを作成します。
必要に応じて、`詳細オプション`→`サンプル発話 -オプション`で設定しましょう。


![スクリーンショット 2022-01-15 16.37.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/9d52cc3a-e8d2-cd91-704e-7b5d3b019454.png)
![スクリーンショット 2022-01-15 16.41.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/b7f1a646-69ef-555d-ecdd-0dcc68830b09.png)

ちなみに、`AMAZON.Date`で聞き取れるのは、下記の通りです。

|反応 |言葉|
|---|---|
| ○ | 2日後 |
| ○ | 明日 |
| ○ | 明後日 |
| ○ |  17日(未来の直近の月の17日になる)|
| ○ | 来週の月曜日 |
| ✕ | 明々後日 |

### 確認プロンプトと応答拒否
以下のように設定します。
![スクリーンショット 2022-01-15 19.51.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/9c3af12e-d787-4535-6f48-5346c8c58c19.png)



## テスト
インテントを保存し、`構築`をクリック後、テストしてみましょう。
![スクリーンショット 2022-01-15 16.53.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/7e9b1124-c17d-a11a-f530-2c236e33c404.png)

テスト時の`検査`の`JSON 入力と出力`の最下部の`応答`にjsonデータがあります。
これは、Lambdaを使用する際に、Lambdaが受け取るデータになります。
後でLambdaを作成するにあたり、Lambdaのテストで、`このjsonデータを貼り付けて、Lambdaのコードを書くとスムーズにLambdaが作成できるためコピーしておきましょう。`

```json:応答
{
  "messages": [
    {
      "content": "予約を承りました。",
      "contentType": "PlainText"
    }
  ],
  "sessionState": {
    "dialogAction": {
      "type": "Close"
    },
    "intent": {
      "name": "book",
      "slots": {
        "age": {
          "value": {
            "originalValue": "22",
            "interpretedValue": "22",
            "resolvedValues": ["22"]
          }
        },
        "date": {
          "value": {
            "originalValue": "3",
            "interpretedValue": "2022-01-16",
            "resolvedValues": ["2022-01-16"]
          }
        },
        "telephoneNumber": {
          "value": {
            "originalValue": "09011111111",
            "interpretedValue": "09011111111",
            "resolvedValues": ["09011111111"]
          }
        }
      },
      "state": "Fulfilled",
      "confirmationState": "Confirmed"
    },
    "originatingRequestId": "2cc08e3e-9b67-4baa-b21f-972c09d15188"
  },
  "interpretations": [
    {
      "nluConfidence": {
        "score": 1
      },
      "intent": {
        "name": "book",
        "slots": {
          "age": {
            "value": {
              "originalValue": "22",
              "interpretedValue": "22",
              "resolvedValues": ["22"]
            }
          },
          "date": {
            "value": {
              "originalValue": "3",
              "interpretedValue": "2022-01-16",
              "resolvedValues": ["2022-01-16"]
            }
          },
          "telephoneNumber": {
            "value": {
              "originalValue": "09011111111",
              "interpretedValue": "09011111111",
              "resolvedValues": ["09011111111"]
            }
          }
        },
        "state": "Fulfilled",
        "confirmationState": "Confirmed"
      }
    },
    {
      "intent": {
        "name": "FallbackIntent",
        "slots": {}
      }
    }
  ],
  "sessionId": "586569454024704"
}
```

## 応答
`dialogAction`、`type`、`state`、`confirmationState`は、Lambdaのコードを書く上で、意識する必要がありますので、確認しておきましょう。

#### スロット値の入力をユーザーに促す

```json:スロット値の入力をユーザーに促す
 "sessionState": {
   "dialogAction": {
    "type": "ElicitSlot",
    "slotToElicit": "age"
   },
    "state": "InProgress",
    "confirmationState": "None"
 },
```


#### 確認プロンプト

```json:確認プロンプト
 "sessionState": {
   "dialogAction": {
    "type": "ConfirmIntent"
   },
    "state": "InProgress",
    "confirmationState": "None"
 },
```
####確認プロンプトの返答「はい」

```json:確認プロンプトの返答「はい」
 "sessionState": {
   "dialogAction": {
    "type": "Close"
   },
    "state": "Fulfilled",
    "confirmationState": "Confirmed"
 },
```


#### 確認プロンプトの返答「いいえ」

```json:確認プロンプトの返答「いいえ」
 "sessionState": {
   "dialogAction": {
    "type": "Close"
   },
    "state": "Failed",
    "confirmationState": "Denied"
 },
```

## FallbackIntent
インテントの発話が聞き取れなかった場合、発動します。
`応答を閉じる`に`うまく聞き取れませんでした。`などの言葉を返答するとよいでしょう。

## IAMポリシー
LambdaのIAMロール用に、以下のIAMポリシーを作成します。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": [
          "dynamodb:PutItem",
          "dynamodb:DeleteItem",
          "dynamodb:GetItem",
          "dynamodb:Scan",
          "dynamodb:Query",
          "dynamodb:UpdateItem"
      ],
      "Resource": "arn:aws:dynamodb:ap-northeast-1:xxxxxxxx:table/*"
    }
  ]
}
```

# Lambdaを作成
以下の通りに作成します。
IAMロールは、`基本的な Lambda アクセス権限で新しいロールを作成`を指定してください。
作成後、先程作成したIAMポリシーをアタッチしてください。
![スクリーンショット 2022-02-06 21.47.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/e639253f-82a2-f570-7209-2969df2a8f1c.png)


- 設定の`タイムアウト時間`は、3秒から`1分`に変更します。
- Lambdaのテストイベントは、先程のlexのjsonを貼り付けておきます。

## Node.jsバージョン

```js:lambda
// 現在時刻の取得
var dt = new Date();
// 日本の時間に修正
dt.setTime(dt.getTime() + 32400000); // 1000 * 60 * 60 * 9(hour)

// 日付を数字として取り出す
var year = dt.getFullYear();
var month = dt.getMonth() + 1;
var day = dt.getDate();
var hour = dt.getHours();
var min = dt.getMinutes();

// 値が1桁であれば '0'を追加
if (month < 10) {month = "0" + month;}
if (day < 10) {day = "0" + day;}
if (hour < 10) {hour = "0" + hour;}
if (min < 10) {min = "0" + min;}
// 出力
var dateTime = year + month + day + hour + min;
console.log("現在の日付" + dateTime);

exports.handler = (event, context, callback) => {
  try {
    dispatch(event, (response) => {
      callback(null, response);
    });
  } catch (err) {
    callback(err);
  }
};

// DynamoDBに書き込み
function putDynamoDB(intentRequest, age,date,telephoneNumber,callback){

  const AWS = require("aws-sdk");
  const dynamo = new AWS.DynamoDB.DocumentClient({
    region: "ap-northeast-1", // DynamoDBのリージョン
  });

  // DynamoDBのテーブル名
  var tableName = "Lex-Write-Lambda";
  var item = {
    age: age,
    date: date,
    telephoneNumber: telephoneNumber,
    timeStamp: dateTime,
  };
  var params = {
    TableName: tableName,
    Item: item,
	};
  // DynamoDBに書き込み処理
  dynamo.put(params).promise().then((data) => {
      console.log("書き込み完了");
    })
    .catch((err) => {
      console.log(err);
      callback(err);
    });
}
// インテントのルーティング
function dispatch(intentRequest, callback) {
  const intentName = intentRequest.sessionState.intent.name;

  if (intentName === "book") {
    return bookIntent(intentRequest, callback);
  } else if (intentName === "anotherintent") {
    return anotherintentIntent(intentRequest, callback);
  }
}
// 別のインテント
function anotherintentIntent(intentRequest, callback) {
  console.log("anotherintentログです。");
}

// bookインテント
function bookIntent(intentRequest, callback) {
  console.log('Received event:', JSON.stringify(intentRequest, null, 2));
  const intentName = intentRequest.sessionState.intent.name;
  const slots = intentRequest.sessionState.intent.slots;

  // 各スロットの値
  const date = slots.date?.value?.interpretedValue;
  const telephoneNumber = slots.telephoneNumber?.value?.interpretedValue;
  const age = slots.age?.value?.interpretedValue;

  //スロット値が埋まっていない場合、聞く
  if (typeof telephoneNumber == "undefined") {
    return callback(ElicitSlot("telephoneNumber", intentName, slots));
	}
  if (typeof date == "undefined") {
    
    return callback(ElicitSlot("date", intentName, slots));
	}
  if (typeof age == "undefined") {
    return callback(ElicitSlot("age", intentName, slots));
	}

  // バリデーション 20歳以上
  if (age < 20) {
    console.log("20歳未満と判定");
    return callback(ValidateSlot("age", "ご利用は20歳以上とさせていただいております。",intentName, slots));
  }
	
  // 確認プロンプトで「はい」と返答すると、confirmationStateのNone→Confirmedになる
  const confirmationState = intentRequest.sessionState.intent.confirmationState;
  if (confirmationState === "Confirmed"){
    
    // DynamoDBに書き込み
    putDynamoDB(intentRequest, age,date,telephoneNumber,callback);
    
    return callback(Close("Fulfilled", '予約を承りました。', intentName, slots));

  // 確認プロンプトで「いいえ」」と返答すると、confirmationStateのNone→Deniedになる
  }else if (confirmationState === "Denied") {
    return callback(Close("Failed",'追加分の予約をキャンセルしました。',intentName, slots) );
  }else{
  // 確認プロンプトに移行。confirmationState = None
  return callback(ConfirmIntent(intentName, slots));
  }
}

function Close(fulfillmentState, messages, intentName, slots) {
  return {
    "messages": [
      {
        "content": messages,
        "contentType": "PlainText"
      }
    ],
    sessionState: {
      dialogAction: {
        type: 'Close'
      },
      intent: {
        name: intentName,
        slots,
        state: fulfillmentState,
      },
    },
  };
}

function ElicitSlot(slotToElicit, intentName, slots) {
  return {
    sessionState: {
      dialogAction: {
        type: 'ElicitSlot',
        slotToElicit,
      },
      intent: {
        name: intentName,
        slots,
      },
    },
  };
}
function ValidateSlot(slotToElicit,messages, intentName, slots) {
  return {
    "messages": [
      {
        "content": messages,
        "contentType": "PlainText"
      }
    ],
    sessionState: {
      dialogAction: {
        type: 'ElicitSlot',
        slotToElicit,
      },
      intent: {
        name: intentName,
        slots,
      },
    },
  };
}

function ConfirmIntent(intentName, slots) {
  return {
    sessionState: {
      dialogAction: {
        type: 'ConfirmIntent'
      },
      intent: {
        name: intentName,
        slots,
        state: 'Fulfilled',
      },
    },
  };
}
```
下記の記事を参考にしました。
dispach関数は、インテントが複数あるときに、振り分ける役割があります。

### dialogAction

- **Close** — 会話を終了させる
- **ConfirmIntent** — Yes/Noを促す
- **Delegate** — 次のActionをLexにまかせる
- **ElicitSlot** - Slotを話すように促す
- **ElicitIntent** - Intentを話すように促す
- **Failed** - 会話失敗（指定回数を超えて要求に答えられなかった）
- **Fulfilled** - インテントが完了した事を伝える
- **ReadyForFulfillment** - 後の処理は、クライアントが処理する必要があることを伝える


### confirmationStatus
confirmationStatusも使いますので、記載しておきます。詳細は、ドキュメントを。
>confirmationStatusは、確認のプロンプトが発生した場合、それに応じてユーザーレスポンスを提供します。
確認のプロンプト時、ユーザー「はい」と答えると、この値は、Confirmed
ユーザーが「いいえ」と答えると、この値は、Denied となります。 
それ以外を答えると、このフィールドの値は None になります。

https://docs.aws.amazon.com/ja_jp/lex/latest/dg/lambda-input-response-format.html

https://press.monaca.io/hirose/8677

https://kun432.hatenablog.com/entry/intro-to-amazon-lex-4



https://dev.classmethod.jp/articles/slots-value-validation-on-lex/

## Pythonバージョン

```py
import json
import boto3
from decimal import Decimal
from datetime import datetime, timedelta
dynamodb = boto3.resource('dynamodb')
lex_write_lambda_table = dynamodb.Table("Lex-Write-Lambda")

def created():
    dateJST = datetime.today() + timedelta(hours=9)
    return dateJST

def put_item(telephoneNumber, date, age):

  response = lex_write_lambda_table.put_item(Item={
    "telephoneNumber": telephoneNumber,
    "date": date,
    "age": age,
    "timeStamp" : str(created())
  })

  print("Send ResponseMetadata:"+ json.dumps(response, default=decimal_to_int, ensure_ascii=False))

# json形式でログを出力するため、Decimalがある場合取り除く
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 validation_slot(slot_to_elicit, message_content, intent_name, slots):
    return {
        'messages': [{'contentType': 'PlainText', 'content': message_content}],
        'sessionState':{
            'dialogAction': {
                'type': 'ElicitSlot',
                'slotToElicit': slot_to_elicit,
            },
            'intent':{
                'name': intent_name,
                'slots': slots,
            }
        }
    }

def confirm_intent(intent_name, slots):
    return {
        '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
            }
        }
    }

# "book"インテント
def book_reserve(intent_request):
    print("Received event:" + json.dumps(intent_request, default=decimal_to_int, ensure_ascii=False))

    intent_name = intent_request['sessionState']['intent']['name']
    slots = intent_request['sessionState']['intent']['slots']

    print('Received intent_name:' +  json.dumps(intent_name, default=decimal_to_int, ensure_ascii=False))
    print('Received slots:' +  json.dumps(slots, default=decimal_to_int, ensure_ascii=False))

    if slots['telephoneNumber'] is None:
        return elicit_slot('telephoneNumber', intent_name, slots)
    telephoneNumber = slots['telephoneNumber']['value']['interpretedValue']

    if slots['date'] is None:
        return elicit_slot('date',intent_name, slots)
    date = slots['date']['value']['interpretedValue']

    if slots['age'] is None:
        return elicit_slot('age',intent_name, slots)
    age = slots['age']['value']['interpretedValue']

    if int(age) < 20:
        return validation_slot(
            'age',
            'ご利用は20歳以上とさせていただいております。\
            年齢を教えて下さい',
            intent_name,
            slots
            )

    confirmation_status = intent_request['sessionState']['intent']['confirmationState']
    print(confirmation_status)

    if confirmation_status == "Confirmed":

        # DynamoDBに書き込み処理
        put_item(telephoneNumber, date, age)

        return close( "Fulfilled", '予約を承りました', intent_name, slots)

    elif confirmation_status == "Denied":

        return close( "Failed", '追加分の予約をキャンセルしました。', intent_name, slots)

    elif confirmation_status == "None":

        return confirm_intent( intent_name, slots)

# インテントのルーティング
def dispatch(intent_request):
    # 他にインテントを使用する場合
    # intent_name = intent_request['sessionState']['intent']['name']
    # if intent_name == 'book':

    # "book"インテント
    return book_reserve(intent_request)

def lambda_handler(event, context):

    return dispatch(event)


```

## Lambdaのテストしてみましょう。
先程、Lexでの応答でコピーしたjsonをペーストして、テストしましょう。年齢を22歳にしていますので、`予約完了しました`と返答するはずです。

```json:テスト結果
Response
{
  "sessionState": {
    "dialogAction": {
      "type": "Close"
    },
    "intent": {
      "name": "book",
      "slots": {
        "age": {
          "value": {
            "originalValue": "30",
            "interpretedValue": "30",
            "resolvedValues": [
              "30"
            ]
          }
        },
        "date": {
          "value": {
            "originalValue": "明日",
            "interpretedValue": "2022-01-16",
            "resolvedValues": [
              "2022-01-16"
            ]
          }
        },
        "telephoneNumber": {
          "value": {
            "originalValue": "09011111111",
            "interpretedValue": "09011111111",
            "resolvedValues": [
              "09011111111"
            ]
          }
        }
      },
      "state": "Fulfilled"
    }
  },
  "messages": [
    {
      "contentType": "PlainText",
      "content": "予約完了しました"
    }
  ]
}
```

jsonの`ageのinterpretedValue`を10にすると、`ご利用は20歳以上とさせていただいております。`と返答するはずです。

```json:テスト結果
{
  "sessionState": {
    "dialogAction": {
      "type": "Close"
    },
    "intent": {
      "name": "book",
      "slots": {
        "age": {
          "value": {
            "originalValue": "90",
            "interpretedValue": "10",
            "resolvedValues": [
              "10"
            ]
          }
        },
        "date": {
          "value": {
            "originalValue": "明日",
            "interpretedValue": "2022-01-16",
            "resolvedValues": [
              "2022-01-16"
            ]
          }
        },
        "telephoneNumber": {
          "value": {
            "originalValue": "09011111111",
            "interpretedValue": "09011111111",
            "resolvedValues": [
              "09011111111"
            ]
          }
        }
      },
      "state": "Failed"
    }
  },
  "messages": [
    {
      "contentType": "PlainText",
      "content": "ご利用は20歳以上とさせていただいております。"
    }
  ]
}
```

# LexにLambdaを設定
作成したLambdaをボットに設定します。
Lambdaの設定場所がわかりにくいですね。
Lex > ボット > ボット:Lex-Write-Lambda-bot > エイリアス > エイリアス: TestBotAlias > 日本語 (JP)
![スクリーンショット 2022-01-15 17.22.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/ea4f218a-ef95-3c40-e28e-36b6ab5e94ce.png)

インテントのフルフィルメントの詳細オプション`フルフィルメントにLambda関数を使用`に`チェックしないでください。`
![スクリーンショット 2022-01-31 22.52.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/caae2b6b-122c-0acd-32bf-3915213ea819.png)


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

![スクリーンショット 2022-01-31 22.53.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/48861396-3ecc-a1d9-cc13-dfa1effb45a8.png)

![スクリーンショット 2022-01-15 17.25.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/37cf3838-fa21-e113-474a-05d39dca78b2.png)

テストしてみましょう。
![スクリーンショット 2022-01-15 17.30.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/8a15b877-a82a-2781-4f89-0a4afe94672f.png)
予約完了し、DynamoDBにも保存されており、成功ですね。

20歳未満の場合、予約できず、DBに書き込まれないようになっています。

![スクリーンショット 2022-02-01 0.09.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/81e15aef-eb0f-be09-6800-049d7d5a7b59.png)

# LeｘとLambdaでできるテクニック

https://qiita.com/holdout0521/items/a242a134c3a48f13b654

# 参考


https://qiita.com/stpete_ishii/items/7d5d7f1a6f82cc2c3b58

https://qiita.com/UpAllNight/items/a15367ca883ad4588c05

https://docs.aws.amazon.com/ja_jp/lex/latest/dg/lambda-input-response-format.html

https://docs.aws.amazon.com/ja_jp/lexv2/latest/dg/lambda.html

https://hacknote.jp/archives/11799/

https://dev.classmethod.jp/articles/amazon-lex-dialogstate/