Amazon Connect と、LambdaやLexとの連携方法

# はじめに
下記のような構成で、connectのシステムを構築した際、
お問い合わせフローから、LambdaやLexを使用する際のパラメータの渡し方などのまとめます。

パラメータを`問い合わせ属性`と呼びます。

>Amazon Connectでは、顧客とのやり取りはそれぞれ問い合わせ として処理されます。やり取りには、音声通話、チャット、または Amazon Lex ボットを使用した自動的な対話があります。
各問い合わせは、特定のやり取りに固有の、いくつかのデータを保持することができます。このデータは、問い合わせ属性としてアクセスされます。
例:
- 顧客の名前。
- エージェントの名前
- 電話やチャットなど、問い合わせに使用されるチャネル
- その他


![スクリーンショット 2022-01-30 18.01.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/b63d3fb3-8a3b-7ace-2fe4-82400f4d9b26.png)

# Connectのお問い合わせフローにLexとLambdaを使用可能にする設定

Connectのお問い合わせフロータブから、LexとLambdaが追加できます。
![スクリーンショット 2022-01-30 18.15.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/1983274e-0369-c7a8-d016-d7596300de87.png)

# ConnectでのLexの連携設定
以下の2つの設定は、必須です！
## 1つめ
お問い合わせフローの`顧客の入力を取得する`から以下のように、Lexで使用する`bot名`と`インテント`を選択します。

注意点として、使用したい`インテント`を選択したとしても、`bot`内のインテントは、すべて発動される可能性がありますので、1つのbotに多くのインテントを入れない方がよいです。

特に、インテントが発動するトリガー部分である発話が似ているインテントは、botごとを分けたほうがよいです。

![スクリーンショット 2022-01-30 18.03.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/286cc45a-079e-1de8-2d5f-4aae164182a6.png)

## 2つめ
Lexの言語が日本語の場合、connectの音声設定も合わせる必要があります。
Lex側
![スクリーンショット 2022-02-19 19.14.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/db373a75-0b9f-cb0f-a500-10b5990d4b3d.png)
connectの設定

-  `言語`：`日本語`
-  `音声`：`Mizuki`
-  `言語属性を設定`：`現在選択せれている言語を属性として使用します`

![スクリーンショット 2022-02-17 11.25.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/ed5cc149-f027-d4db-f953-e54e3a409adf.png)


#LexからLambdaに値を渡す
Lexで応答した情報（スロット値）をLambdaに渡すときは、以下のフローを作成します
![スクリーンショット 2022-01-30 18.19.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/35f8173c-3e25-1130-9b4d-9bd31d6b7db7.png)

`顧客の入力を取得する`でLexの設定した後のフローにおいて、`AWS Lambda 関数を呼び出す`を使用します。
例えばLexのスロットで`スロット名:age`があった場合、Lambdaがその`ageの値`を受け取るためには、以下のようにします。

- `AWS Lambda関数を呼び出す`
- `属性を使用する`
- `宛先キー：任意(Lambdaで受け取る値)`
- `タイプ：Lexスロット`
- `属性：age`
- `タイムアウト値:8秒`

![スクリーンショット 2022-01-30 20.52.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/9b068df4-f8f7-0c59-f49a-86c4ef3d7e4e.png)

`タイムアウト値は、必ず8秒にしましょう。`
3秒だと、間に合わずタイムアウトエラーになります。

`属性`は、Lexのスロット名である`age`を選択し、`タイプは、Lexスロット`を選択する必要があります。

![スクリーンショット 2022-01-30 18.17.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/a2702cd1-c12f-da41-9c39-16fa9c2f98f7.png)


このとき、Lambdaが受け取るjsonデータは以下のものになります。
（LambdaがConnectで受け取るjsonデータのキーは、すべて同じです。）

```json
{
  "Details": {
    "ContactData": {
      "Attributes": {},
      "Channel": "VOICE",
      "ContactId": "da689669-5a1b-48c9-bc85-4dfd31a9e416",
      "CustomerEndpoint": {
        "Address": "+09011111111",
        "Type": "TELEPHONE_NUMBER"
      },
      "CustomerId": "",
      "Description": "",
      "InitialContactId": "da689669-5a1b-48c9-bc85-4dfd31a9e416",
      "InitiationMethod": "INBOUND",
      "InstanceARN": "arn:aws~~~",
      "LanguageCode": "ja-JP",
      "MediaStreams": {
        "Customer": {
          "Audio": ""
        }
      },
      "Name": "",
      "PreviousContactId": "da689669-5a1b-48c9-bc85-4dfd31a9e416",
      "Queue": "",
      "References": {},
      "SystemEndpoint": {
        "Address": "+xxxxxxxxxx",
        "Type": "TELEPHONE_NUMBER"
      }
    },
    "Parameters": {
      "age": "20"
    }
  },
  "Name": "ContactFlowEvent"
}
```

https://docs.aws.amazon.com/ja_jp/connect/latest/adminguide/connect-lambda-functions.html#function-contact-flow

`"Parameters": {"age": "20"}`値は、ここで受け取っていますね。

`ageと20`を`問い合わせ属性のキーと値`と呼びます。

Lambdaでこの値を受け取り方は、このようになります。

```py:Lambda(Python)
def lambda_handler(event, context):

age = event['Details']['Parameters']['age']
# age = 20
```

```js:Lambda(Node.js)
const age = event.Details.Parameters.age
```

Lambdaで値を受け取れるか確認する方法は、上記のjsonデータをlambdaのテストイベントにペーストして、テストを実行してみるとよいでしょう。





# Lambdaで発信電話番号を受け取る
connectに電話してきた発信電話番号を受け取り、lambdaで使用する場合、以下の設定で受け取れます。

- `AWS Lambda関数を呼び出す`
- `属性を使用する`
- `宛先キー：任意(Lambdaで受け取る値)`
- `タイプ：システム`
- `属性：顧客電話番号`

![スクリーンショット 2022-01-30 20.00.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/4a0f2bca-04c7-0a7c-6a60-1d8de2c332aa.png)


```json
{
  "Details": {
    "ContactData": {
      "Attributes": {},
      "Channel": "VOICE",
      "ContactId": "da689669-5a1b-48c9-bc85-4dfd31a9e416",
      "CustomerEndpoint": {
        "Address": "+09011111111",
        "Type": "TELEPHONE_NUMBER"
      },
　　　　　　　　　　　省略
```

`属性の顧客電話番号`のキーは、`Address`になります。

```py:Lambda(Python)
user_phone = event["Details"]["ContactData"]["CustomerEndpoint"]["Address"]
#user_phone = +09011111111
```

```js:Lambda(Node.js)
const user_phone = event.Details.ContactData.CustomerEndpoint.Address
```

# LambdaからLambdaに値を渡す
![スクリーンショット 2022-01-30 18.38.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/8cbb6db5-8ca3-0059-14cf-0560b5a95181.png)
LambdaからLambdaに値を渡すには、関数のコードに`return`が必要になります。

1つめのLambdaで`return {"reservation": "false"}`した場合、2つ目のLambdaで値を受け取るには以下の設定になります。

- `AWS Lambda関数を呼び出す`
- `属性を使用する`
- `宛先キー：任意(Lambdaで受け取る値)`
- `タイプ：外部`
- `属性：reservation`


![スクリーンショット 2022-01-30 18.50.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/ad7ea8cc-af58-5717-2ace-a43d81ddbbe7.png)



Lambdaの値を受け取る場合、`タイプは外部`になります。

2つめのLambdaで受け取るjsonデータは、Lexの時と同じです。

`"Parameters": {"reservation": "true"}`値は、このように受け取ります。

```py
reservation = event['Details']['Parameters']['reservation']
# reservation = true
```

複数受け取る場合、以下のようにパラメータをまとめましょう

```py
Parameters = event['Details']['Parameters']

reservation = Parameters['reservation']
age = Parameters['age']
```
Lambdaで受け取る値は、属性でなく、テキストの使用もできます。

![スクリーンショット 2022-01-30 22.07.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/3b741feb-5b1c-eb2a-3023-45bc9d656b6d.png)


`テキストの使用`も先程と同様に値を受け取れますので、省略します。

# 問い合わせ属性を音声に反映
Lambdaで`return {"age": "20"}`し、その値を`コンタクト属性の設定`を使用し、ユーザー定義として、属性を保存します。
その属性を使用し、`プロンプトの再生`で音声を再生します。

![スクリーンショット 2022-01-30 19.10.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/91e5f910-29e3-59d9-76b6-fb2a2f536656.png)

`コンタクト属性の設定`
- `属性を使用する`
- `宛先タイプ：ユーザー定義`
- `宛先キー：任意(Lambdaで受け取る値)`
- `タイプ：外部`
- `属性：age`

![スクリーンショット 2022-01-30 19.03.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/f6dd6aab-ad3f-d031-3489-218fd86ed3a7.png)

`ユーザー定義`したものは、jsonデータの`Attributes`内に入ります。

```json
{
  "Details": {
    "ContactData": {
      "Attributes": {
        "age": "20"
      },
```

プロンプトのテキストの中で「$」記号を使うと、`コンタクト属性に設定`されている値をプロンプトに埋め込むことができます。
 `「$.Attributes」`で参照したい属性が「ユーザー定義」属性であることを指定し、最後のピリオドに続いて「属性名」を指定します。

`プロンプトの再生`に以下のテキストを入れると、音声が属性の値を認識します。

![スクリーンショット 2022-01-30 19.08.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/881d5a8f-2f8a-ee64-e058-5e0c77c00248.png)



```:テキスト
お客様の年齢は、 $.Attributes.age 歳ですね。
```

```:ageが20の場合、音声
お客様の年齢は、 20歳ですね。
```
**注意点
プロンプトのテキストに `「$.Attributes.属性」`を埋め込む際、両端に半角スペースを入れないと、音声が認識せず、その箇所のみ無視されます。**

```:テキスト
             ↓ 半角スペース        ↓ 半角スペース
お客様の年齢は、   $.Attributes.age   歳ですね。
```

ちなみに、ユーザー定義だけでなく、外部のタイプなども使用できます

- タイプ：ユーザー定義：$.Attributes.[宛先キー]
- タイプ：外部：$.External.[宛先キー]
- タイプ：Lexのインテント名:$.Lex.[宛先キー]
- タイプ：Lexのスロット名:$.Lex.Slots[宛先キー]

Lexの値も使用できるようです。

>Amazon Lex での例
・ Amazon Lex ボットから属性を参照するには、参照する Amazon Lex ボットの一部を形式 $.Lex.[宛先キー] に含めて 、($.Lex.IntentName などとして) 使用します。
・ 顧客からの Amazon Lex ボットスロットへの入力を参照するには、$.Lex.Slots.slotName を使用します。この際、slotName はボットのスロット名と置き換えます。

https://docs.aws.amazon.com/ja_jp/connect/latest/adminguide/how-to-reference-attributes.html




# Lambdaの実行結果によって、フローを分岐させる
年齢が20歳を超えているどうかでフローを分岐させたい場合があります。
Lambda返す値が`return {"age": "20"}`もしくは`return {"age": "19"}`の場合、フローを分岐させるためには、`コンタクト属性を確認する`の設定が必要です。


`コンタクト属性を確認する`
- `確認する属性`
- `宛先タイプ：ユーザー定義`
- `属性：age`
- `チェックする条件`：`以上 20`



![スクリーンショット 2022-01-30 20.27.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/b1924f02-fe27-9719-340f-74654b112cb1.png)
![スクリーンショット 2022-01-30 20.27.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/fdab2f61-cd30-6503-3dda-ade17e72b4fb.png)


ageが20以上の場合と20歳未満で分岐できました。

ちなみに、`ageの属性値を外部タイプとして定義している場合`、`$.External.age`が使えます。

![スクリーンショット 2022-01-31 22.02.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/a67aa01c-eaec-63b9-4a11-211560f49609.png)

# Lambdaの外部タイプが無効になるパターン
下記フローのように、Lambdaを連続して使用すると、1つめのLambdaの外部タイプの値は、プロンプト再生に使用できません。（値を渡せません）

![スクリーンショット 2022-01-31 22.07.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/e9b34ee8-cd73-9471-b275-9046562f0c15.png)
しかし、1つめのLambdaの後に、ユーザー定義で`age`を定義することで、フロー内のどのタイミングでも`age`を呼び出すことができます。
![スクリーンショット 2022-01-31 22.18.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/8e527efc-be9c-6cda-85b1-f6d33e6697f7.png)


# Lambdaのコードの注意点
Connectで実際にテストし、Lambda実行時にエラーになった場合、CloudWatch Logsでエラー原因を確認します。
その際、Lambdaのログは、エラーになっておらず、Connectのログを見ると、エラーになっている場合、Lambdaに`return`を使用していない可能性があります。
`return`がないと、connectが失敗するため、returnすることがなくても、`適当な値をreturnしましょう。`

# 参考記事
問い合わせ属性まとめ一覧↓

https://docs.aws.amazon.com/ja_jp/connect/latest/adminguide/connect-attrib-list.html

プロンプト再生のSSMLタイプを使用すると、音声の大きさや速度を調整できます。↓


https://docs.aws.amazon.com/ja_jp/polly/latest/dg/ssml-synthesize-speech-cli.html#example-common-ssml-tags

https://docs.aws.amazon.com/ja_jp/connect/latest/adminguide/supported-ssml-tags.html

https://dev.classmethod.jp/articles/amazon-connect-lambda-message/

https://dev.classmethod.jp/articles/amazon-connect-bootcamp-step1/

https://docs.aws.amazon.com/ja_jp/connect/latest/adminguide/connect-lambda-functions.html#process-function-response

https://future-architect.github.io/articles/20181202/