Lex使用時、Lambdaでできるテクニック

# はじめに
LexとLambdaでできる応用テクニック？を書き留めます。

事前にこちらでLambdaとLexを設定しました。

https://qiita.com/holdout0521/items/58f524be7cc5d9ca2093


# LexとLambdaでできること色々
以下のスロット値の入力のコードについて、できることをまとめます。
[フルのコードは、こちらです](https://qiita.com/holdout0521/items/58f524be7cc5d9ca2093#python%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3)

Lexの流れは、以下の通りに聞かれます。
`発話 → telephoneNumber(電話番号) → date（日付） → age（年齢） → 確認 → yes/no`

```py
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
        )
```


# スロット値をすべてリセット
下記のようLambdaを設定すると、スロット値をすべてリセットできます。
年齢を聞かれた際、スロット値がリセットされ、
`telephoneNumber→date→`age(リセット)`→telephoneNumber→date→`age(リセット)`→telephoneNumber ...`とループします。

通常
`発話 → telephoneNumber → date → age → 確認 → yes/no`

年齢を聞かれた際に、リセット
`発話 → telephoneNumber → date → age(リセット) → telephoneNumber → date → age(リセット)...`

```.py
return elicit_slot(
    'age',
    intent_name,
    {
        "date": None,
        "telephoneNumber": None,
        "age": None
    }
  )
```

# Lambda側で1つのスロットに値を入れて、スロット値を聞かれないようにする
以下のようにすると、Lambda側でtelephoneNumberのスロット値に`090`を入れることができます。

通常
`発話 → telephoneNumber → date → age → 確認 → yes/no`

電話番号を事前に入力
`発話 → date → age → 確認 → yes/no`

```py
return elicit_slot(
    'date',
    intent_name,
    {
        "date": None,
        "telephoneNumber": {
            "shape": "Scalar",
            "value": {
                "originalValue": "090",
                "resolvedValues": [
                    "090"
                ],
                "interpretedValue": "090"
            }
        },
        "age": None
    }
)
```
↓実際にコードを修正して書いてみます。
`originalValue`、`resolvedValues`、`interpretedValue`の3つに値を入れる必要があります。

`telephoneNumber(聞かれない)`→date→age→確認 という流れになります。

もちろんtelephoneNumberのスロット値には、`090`が入っています。

```py
# 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,
        {
            "date": None,
            "telephoneNumber": {
                "shape": "Scalar",
                "value": {
                    "originalValue": "090",
                    "resolvedValues": [
                        "090"
                    ],
                    "interpretedValue": "090"
                }
            },
            "age": None
        }
    )

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
        )
```

↓事前に入力しておく値をメソッド化してもいいですね。

```py
def input_slots_value(value):
    return {
        "shape": "Scalar",
        "value": {
            "originalValue": value,
            "resolvedValues": [value],
            "interpretedValue": value
        }
    }

# 省略


# 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,
        {
            "date": None,
            "telephoneNumber": input_slots_value(90),
            "age": None
        }
    )
date = slots['date']['value']['interpretedValue']
telephoneNumber = slots['telephoneNumber']['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
        )
```

# Lambda側で2つのスロットに値を入れて、スロット値を聞かれないようにする

下記だと、`telephoneNumber(聞かれない)`→date→`age(聞かれない)`→確認 という流れになります。

通常
`発話 → telephoneNumber → date → age → 確認 → yes/no`

電話番号と年齢を事前に入力
`発話 → date  → 確認 → yes/no`


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

if slots['date'] is None:
    return elicit_slot(
        'date',
        intent_name,
        {
            "date": None,
            "telephoneNumber": input_slots_value(90),
            "age": None,
        }
    )
date = slots['date']['value']['interpretedValue']
telephoneNumber = slots['telephoneNumber']['value']['interpretedValue']

if slots['age'] is None:

    # ageを入力し、確認プロンプトに移行する
    slots = {
        "date": input_slots_value(date),
        "telephoneNumber": input_slots_value(telephoneNumber),
        "age": input_slots_value(40)
        }
    return confirm_intent( intent_name, slots)

age = slots['age']['value']['interpretedValue']

# if int(age) < 20:
#     return validation_slot(
#         'age',
#         'ご利用は20歳以上とさせていただいております。\
#         年齢を教えて下さい',
#         intent_name,
#         slots
#         )
```
# apiキーなどの英数字が混ざった文字列をスロット値に保存する
あるAPIとLex、Lambdaを連携する歳に、アクセスコード（予約コード）を（`8j91dcf0rfje4bdaa7ik97c0d2e948pl`）をスロット値に保存する必要があったため、書き留めます。
`confirm_intent `でreturnする際に、セッション属性値に値を渡すことができなかったため、スロット値に入れました。

通常
`発話 → telephoneNumber → date → alpha(英数字の文字列) → 確認 → yes/no`

電話番号とアクセスコードをLambda側で入力
`発話  → date  → 確認 → yes/no`

スロットタイプから、`組み込みスロットタイプを使用`の`AMAZON.AlphaNumeric`を選択します。
![スクリーンショット 2022-02-20 22.21.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/6d45f362-de8b-e3ba-46d9-c6fbbbf61d91.png)
![スクリーンショット 2022-02-20 22.21.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/4953a36e-464f-fa3b-96b3-af17fe560a99.png)
スロットタイプ設定を以下の通りにします。これは、大文字と小文字を含めた英数字に対応します。
`正規表現`:`[0-9][a-z][A-Z]`
![スクリーンショット 2022-02-20 22.21.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/74172966-0617-5c9f-1f62-afa77c7874f1.png)
APIキーのスロット値は、Lexの受け答えとして、使用しません。
[こちらのageの箇所を](https://qiita.com/holdout0521/items/a242a134c3a48f13b654#lambda%E5%81%B4%E3%81%A72%E3%81%A4%E3%81%AE%E3%82%B9%E3%83%AD%E3%83%83%E3%83%88%E3%81%AB%E5%80%A4%E3%82%92%E5%85%A5%E3%82%8C%E3%81%A6%E3%82%B9%E3%83%AD%E3%83%83%E3%83%88%E5%80%A4%E3%82%92%E8%81%9E%E3%81%8B%E3%82%8C%E3%81%AA%E3%81%84%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B)`スロット値alpha`に変えます。
![スクリーンショット 2022-02-20 22.22.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/7f14fe13-4c94-62e7-44a1-b293f2875e0e.png)

APIキーなので、本来は、確認プロンプトに表示しませんが、以下のように、英数字をスロット値に入れることができました。
ちなみに、`AMAZON.AlphaNumeric`は、100文字まで入れられます。
![スクリーンショット 2022-02-20 22.30.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/01120915-3ba2-6838-1798-1137f9e70a06.png)

https://docs.aws.amazon.com/ja_jp/lex/latest/dg/built-in-slot-alphanumeric.html

#特定の条件で、強制終了させる（キャンセルさせる）
電話番号を聞かれた際、特定の番号の場合、強制終了させることができます。

下記だと、`telephoneNumber(09)`→強制終了 という流れになります。

通常
`発話 → telephoneNumber → date → age → 確認 → yes/no`

特定の条件で、強制終了させる
`発話 → telephoneNumber（09）  → no`

`close( "Failed",`が必要になります。

![スクリーンショット 2022-02-27 16.52.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/f15a1509-fc38-ece1-04d2-4769449888f8.png)


```py
if slots['telephoneNumber'] is None:
    return elicit_slot('telephoneNumber', intent_name, slots)

telephoneNumber = slots['telephoneNumber']['value']['interpretedValue']

# 電話番号が09の場合、強制終了させる
if telephoneNumber == "09":
    return close( "Failed", '強制終了です。', intent_name, slots)

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

#省略
```

# 特定の条件で、予約完了させる
電話番号を聞かれた際、特定の番号の場合、予約完了させることができます。

下記だと、`telephoneNumber(09)`→ 予約完了 という流れになります。

通常
`発話 → telephoneNumber → date → age → 確認 → yes/no`

特定の条件で、予約完了させる
`発話 → telephoneNumber（09）  → yes`

`close( "Fulfilled",`が必要になります。

![スクリーンショット 2022-02-27 16.58.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/e8dae91b-2096-4b00-188c-68bcd32d4938.png)

```py
if slots['telephoneNumber'] is None:
    return elicit_slot('telephoneNumber', intent_name, slots)

telephoneNumber = slots['telephoneNumber']['value']['interpretedValue']

# 電話番号が09の場合、予約完了させる
if telephoneNumber == "09":
    slots = {
        "date": input_slots_value("2022-0227"),
        "telephoneNumber": input_slots_value(telephoneNumber),
        "age": input_slots_value("fmoeuweoiru93r3320io3wrjo32jr32jo32")
        }
    return close( "Fulfilled", '予約完了です。', intent_name, slots)

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

#省略
```

# 聞き取れなかった場合（解釈できない値）、強制的に予約失敗とする


通常
`発話 → telephoneNumber → date → age → 確認 → yes/no`

聞き取れなかった場合、強制的に予約失敗させる
`発話 → telephoneNumber（09）  → date（聞き取れない） →　no`


![スクリーンショット 2022-02-27 17.25.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/6500ebcd-1db7-07b3-7fc0-c8a39f70e4ad.png)


```py
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)

# 聞き取れなかった場合
if slots['date']['value']['resolvedValues']　== []:
    return close( "Failed",
                '聞き取れませんでした。最初からやり直してください。',
                intent_name,
                slots
                )

date = slots['date']['value']['interpretedValue']

if slots['age'] is None:

#省略
```
聞き取れなかった場合、lexからlambdaへの`slots`内のレスポンス

```json
{
    "date": {
        "shape": "Scalar",
        "value": {
            "originalValue": "ttt",
            "resolvedValues": []
        }
    },
    "telephoneNumber": {
        "shape": "Scalar",
        "value": {
            "originalValue": "090",
            "resolvedValues": [
                "090"
            ],
            "interpretedValue": "090"
        }
    },
    "age": null
}
```

`slots['date']['value']['resolvedValues']`：`[]`配列が空

`slots['date']['value']['originalValue']`：`lexが聞き取った言葉`

`slots['date']['value']['interpretedValue']`：`なし` Lexが解釈できなかったということです。

# 参考

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