Amazon Connectから呼び出すAmazon Lexのみで、ノーコードで留守番電話機能を実装してみた

Amazon Connectから呼び出すAmazon Lexのみで、ノーコードで留守番電話機能を実装してみた

Clock Icon2024.09.27

はじめに

Amazon Connectで留守番電話機能を実装する際、多くの記事では「ストリーミングの開始ブロック」とAWS Lambda関数を組み合わせた方法が紹介されています。

しかし、Lambdaを利用すると、コード開発や運用フェーズでのメンテナンスが必要になります。

そこで本記事では、コーディングが不要で、Amazon LexというAIチャットボットのみを利用して実装する方法を紹介します。この方法により、開発と運用の負担を大幅に軽減できます。

この実装によるユーザー体験は、以下の流れになります。

流れ 誰が アクション
1 ユーザー (電話をかける)
2 Connect 「申し訳ございません。ただいま電話に出ることができません。メッセージを録音してよければ、はい、とお伝え下さい。」
3 ユーザー 「はい」
4 Connect(Lex) 「お問い合わせ内容をお伝え下さい。無音の状態が一定時間ある場合、録音が切れますので、ご了承ください。」
5 ユーザー 伝える
6 Connect 「以上で録音を終了します。電話を切ります。」

ユーザーに「はい」と言ってもらう理由は、Lexのインテントをトリガーするためです。
ただし、最初の発話から直接録音のメッセージを伝えたい場合(つまり、「はい」という確認なしで録音を開始したい場合)は、LexからLambdaを呼び出し、必ず特定のインテントがトリガーされるよう設定する必要があります。

今回実装方法は紹介しませんが、ユーザー体験は以下の通りになります。

流れ 誰が アクション
1 ユーザー (電話をかける)
2 Connect 「申し訳ございません。ただいま電話に出ることができません。メッセージを録音しますので、お問い合わせ内容をお伝え下さい。」
3 ユーザー 伝える
4 Connect 「以上で録音を終了します。電話を切ります。」

前提条件

  • Connectインスタンス
  • 音声録音用のS3バケットを作成済み
  • 文字起こし出力用のCloudWatch Logsグループを作成済み

Lexボット作成

新しいインテントを作成します。

インテント名を適切に設定します。
サンプル発話では、以下の文言でインテントがトリガーされるよう設定しました。

cm-hirai-screenshot 2024-09-20 17.26.17

スロットでは、スロットタイプをAMAON.FreeFormInputとし、以下の通りプロンプトを設定しました。

お問い合わせ内容をお伝え下さい。無音の状態が一定時間ある場合、録音が切れますので、ご了承ください。

cm-hirai-screenshot 2024-09-20 17.27.28

その他の設定はデフォルトのまま非アクティブにします。

cm-hirai-screenshot 2024-09-20 17.28.00

ログ設定を行います。

  • 音声録音をS3バケットに保存するよう設定します。
  • 文字起こし内容をCloudWatch Logsグループに出力するよう設定します。

これらの設定により、後で録音内容や文字起こし結果を確認することができます。

cm-hirai-screenshot 2024-09-20 17.29.34

全ての設定が完了したら、Lexボットをビルドします。

Lexボットの上限緩和申請

Amazon Lexのデフォルト設定では、音声入力時間の上限が15秒に制限されています。しかし、留守番電話機能としては15秒では不十分な場合が多いでしょう。

そこで、音声入力時間の上限緩和申請を行う必要があります。申請方法については、以下の記事を参考にしてください

https://dev.classmethod.jp/articles/amazon-connect-lex-voice-input-extension/

注意点

  • 筆者の申請では、希望した時間までは上限が緩和されませんでした。
  • 音声入力時間は1分弱まで緩和されましたが、具体的な秒数は控えさせていただきます。
  • 緩和の程度はAWS側の判断によるため、申請結果は個別に異なる可能性があります。

上限緩和が承認されるまでには数日から数週間かかる場合がありますので、余裕を持って申請することをおすすめします。

Connectフロー作成

次に、Amazon Connectでフローを作成します。以下は、実際に作成したフローです。

cm-hirai-screenshot 2024-09-20 17.38.41

このフローは以下の主要なステップで構成されています。

  1. フローログの有効化
  2. テキスト読み上げ音声の設定
  3. Lexボットの呼び出し
  4. 録音完了後のメッセージ
  5. 通話の切断

フローの詳細な設定を確認したい場合は、以下のエクスポートされたJSONを参照してください。このJSONをインポートすることで、同じフローを再現できます。

フローのエクスポートJSON(クリックで展開)
{
  "Version": "2019-10-30",
  "StartAction": "17a389ed-1002-4550-9bbf-efbbe3811cf9",
  "Metadata": {
    "entryPointPosition": {
      "x": 36.8,
      "y": 65.6
    },
    "ActionMetadata": {
      "17a389ed-1002-4550-9bbf-efbbe3811cf9": {
        "position": {
          "x": 140.8,
          "y": 60.8
        }
      },
      "3c2cba41-f8a9-418f-b428-c573abb3c010": {
        "position": {
          "x": 1129.6,
          "y": 316.8
        }
      },
      "1990a178-5825-414e-bd24-2bc3dc5dc937": {
        "position": {
          "x": 357.6,
          "y": 61.6
        },
        "children": [
          "a4ed3646-c4df-4572-9095-1b57b0e2a82c"
        ],
        "overrideConsoleVoice": true,
        "fragments": {
          "SetContactData": "a4ed3646-c4df-4572-9095-1b57b0e2a82c"
        },
        "overrideLanguageAttribute": true
      },
      "a4ed3646-c4df-4572-9095-1b57b0e2a82c": {
        "position": {
          "x": 357.6,
          "y": 61.6
        },
        "dynamicParams": []
      },
      "1a41bbb2-6b65-4bf0-871b-deaf288ba7a1": {
        "position": {
          "x": 848.8,
          "y": 472
        }
      },
      "465002a2-1f89-476b-8f58-e0e2671632c7": {
        "position": {
          "x": 846.4,
          "y": 272
        }
      },
      "87155153-6569-4cbc-8796-80b66eee6955": {
        "position": {
          "x": 856.8,
          "y": 61.6
        }
      },
      "0cebb197-9920-4fcc-8410-a2f9e9fb12ff": {
        "position": {
          "x": 572,
          "y": 57.6
        },
        "parameters": {
          "LexV2Bot": {
            "AliasArn": {
              "displayName": "TestBotAlias",
              "useLexBotDropdown": true,
              "lexV2BotName": "cm-hirai-beyond-15seconds"
            }
          }
        },
        "dynamicMetadata": {
          "x-amz-lex:audio:start-timeout-ms:*:*": false,
          "x-amz-lex:audio:end-timeout-ms:*:*": false,
          "x-amz-lex:audio:max-length-ms:*:*": false
        },
        "useLexBotDropdown": true,
        "lexV2BotName": "cm-hirai-beyond-15seconds",
        "lexV2BotAliasName": "TestBotAlias",
        "conditionMetadata": [
          {
            "id": "51b72432-3fba-4460-ba06-efbbf9122828",
            "operator": {
              "name": "Equals",
              "value": "Equals",
              "shortDisplay": "="
            },
            "value": "recording"
          }
        ]
      }
    },
    "Annotations": [],
    "name": "cm-hirai-beyond-15seconds",
    "description": "",
    "type": "contactFlow",
    "status": "published",
    "hash": {}
  },
  "Actions": [
    {
      "Parameters": {
        "FlowLoggingBehavior": "Enabled"
      },
      "Identifier": "17a389ed-1002-4550-9bbf-efbbe3811cf9",
      "Type": "UpdateFlowLoggingBehavior",
      "Transitions": {
        "NextAction": "1990a178-5825-414e-bd24-2bc3dc5dc937"
      }
    },
    {
      "Parameters": {},
      "Identifier": "3c2cba41-f8a9-418f-b428-c573abb3c010",
      "Type": "DisconnectParticipant",
      "Transitions": {}
    },
    {
      "Parameters": {
        "TextToSpeechEngine": "Neural",
        "TextToSpeechStyle": "None",
        "TextToSpeechVoice": "Kazuha"
      },
      "Identifier": "1990a178-5825-414e-bd24-2bc3dc5dc937",
      "Type": "UpdateContactTextToSpeechVoice",
      "Transitions": {
        "NextAction": "a4ed3646-c4df-4572-9095-1b57b0e2a82c"
      }
    },
    {
      "Parameters": {
        "LanguageCode": "ja-JP"
      },
      "Identifier": "a4ed3646-c4df-4572-9095-1b57b0e2a82c",
      "Type": "UpdateContactData",
      "Transitions": {
        "NextAction": "0cebb197-9920-4fcc-8410-a2f9e9fb12ff",
        "Errors": [
          {
            "NextAction": "0cebb197-9920-4fcc-8410-a2f9e9fb12ff",
            "ErrorType": "NoMatchingError"
          }
        ]
      }
    },
    {
      "Parameters": {
        "Text": "エラーとなりました。"
      },
      "Identifier": "1a41bbb2-6b65-4bf0-871b-deaf288ba7a1",
      "Type": "MessageParticipant",
      "Transitions": {
        "NextAction": "3c2cba41-f8a9-418f-b428-c573abb3c010",
        "Errors": [
          {
            "NextAction": "3c2cba41-f8a9-418f-b428-c573abb3c010",
            "ErrorType": "NoMatchingError"
          }
        ]
      }
    },
    {
      "Parameters": {
        "Text": "録音されないということで承知しました。電話を切ります。"
      },
      "Identifier": "465002a2-1f89-476b-8f58-e0e2671632c7",
      "Type": "MessageParticipant",
      "Transitions": {
        "NextAction": "3c2cba41-f8a9-418f-b428-c573abb3c010",
        "Errors": [
          {
            "NextAction": "3c2cba41-f8a9-418f-b428-c573abb3c010",
            "ErrorType": "NoMatchingError"
          }
        ]
      }
    },
    {
      "Parameters": {
        "Text": "以上で録音を終了します。電話を切ります。"
      },
      "Identifier": "87155153-6569-4cbc-8796-80b66eee6955",
      "Type": "MessageParticipant",
      "Transitions": {
        "NextAction": "3c2cba41-f8a9-418f-b428-c573abb3c010",
        "Errors": [
          {
            "NextAction": "3c2cba41-f8a9-418f-b428-c573abb3c010",
            "ErrorType": "NoMatchingError"
          }
        ]
      }
    },
    {
      "Parameters": {
        "Text": "申し訳ございません。ただいま電話に出ることができません。メッセージを録音してよければ、はい、とお伝え下さい。",
        "LexV2Bot": {
          "AliasArn": "arn:aws:lex:ap-northeast-1:012345678901:bot-alias/OMRBM5O69M/TSTALIASID"
        },
        "LexSessionAttributes": {
          "x-amz-lex:audio:start-timeout-ms:*:*": "8000",
          "x-amz-lex:audio:end-timeout-ms:*:*": "4000",
          "x-amz-lex:audio:max-length-ms:*:*": "50000"
        }
      },
      "Identifier": "0cebb197-9920-4fcc-8410-a2f9e9fb12ff",
      "Type": "ConnectParticipantWithLexBot",
      "Transitions": {
        "NextAction": "1a41bbb2-6b65-4bf0-871b-deaf288ba7a1",
        "Conditions": [
          {
            "NextAction": "87155153-6569-4cbc-8796-80b66eee6955",
            "Condition": {
              "Operator": "Equals",
              "Operands": [
                "recording"
              ]
            }
          }
        ],
        "Errors": [
          {
            "NextAction": "465002a2-1f89-476b-8f58-e0e2671632c7",
            "ErrorType": "NoMatchingCondition"
          },
          {
            "NextAction": "1a41bbb2-6b65-4bf0-871b-deaf288ba7a1",
            "ErrorType": "NoMatchingError"
          }
        ]
      }
    }
  ]
}

Lexボットを呼び出すブロックでは、以下のセッション属性を設定しています:

  1. x-amz-lex:audio:start-timeout-ms:*:*: 8000

    • ユーザーが発話を開始するまでボットが待機する最大時間(ミリ秒)
  2. x-amz-lex:audio:end-timeout-ms:*:*: 4000

    • ユーザーの発話が途切れてから、ボットが発話終了と判断するまでの時間(ミリ秒)
  3. x-amz-lex:audio:max-length-ms:*:*: 50000

    • ユーザーの最大音声入力時間(ミリ秒)。ここでは50秒に設定しています。

これらの設定により、ユーザーが十分な時間をかけてメッセージを録音できるようになります。

cm-hirai-screenshot 2024-09-20 17.41.10

セッション属性の詳細は以下の記事をご参照ください

https://dev.classmethod.jp/articles/amazon-connect-lex-three-verification/

試してみる

実際に作成したシステムをテストするため、以下のメッセージを録音してみました。

もしもし、クラスメソッドと申します。

先日、購入した洗濯機の調子が悪くて連絡しました。型番はAWAの2000です。

昨日の夜から、運転すると異常な振動と大きな音がするようになりました。脱水時に特にひどくなります。

説明書を確認して、設置の水平度合いを確認したり、洗濯物の偏りがないか調整しましたが、改善されません。

おそらく保証期間内なので、修理をお願いしたいのですが、明日か明後日に来ていただくことは可能でしょうか?

私の連絡先は090-1234-5678です。午前中であれば在宅しております。

お手数ですが、ご連絡いただければ幸いです。よろしくお願いいたします。

このメッセージを実際に話したところ、録音時間はおおよそ50秒でした。これは、先ほど設定した最大音声入力時間(50秒)とほぼ一致しています。

システムによる文字起こし結果を確認したところ、15秒を大きく超える内容が正確に記録されていることが確認できました。以下は実際の文字起こし結果です。

もしもし クラス メソット と 申し ます 

先日 購入 し た 洗濯 機 の 調子 が 悪く て 連絡 し まし た 型番 は a w a の 二 千 です 

昨日 の 夜 から 運転 する と 異常 な 新郎 と 大きな 音 が する よう に なり まし た 脱水 時 に 特に し とく なり ます

説明 書 を 確認 し て 設置 の 水平 度合い を 確認 し たり 洗濯 物 の 偏り が ない か 調整 し まし た が 改善 さ れ ませ ん

おそらく 保証 期間 内 な の で 修理 を お 願い し たい の です が 明日 か 明後日 日経 化学 こと は 可能 でしょう か 

私 の 連絡 先 は 〇 九 〇 一 五 三 四 五 六 七 八 です お 膳 中 で あれ ば 在宅 し て おり ます 

お 手数 です が 連絡 いただけ ます と 幸い です よろしく お 願い いたし ます

この結果から、音声入力時間の上限緩和が正常に機能していることがわかります。

システムの動作をより詳細に確認するため、CloudWatch Logsに出力された全ログを以下に示します。このログには、文字起こし内容や音声入力時間等が含まれています。

CloudWatch Logsの全出力(クリックで展開)
{
    "timestamp": "2024-09-20T08:48:58.367Z",
    "requestId": "f29f5da7-7dde-4f35-b671-7968cffebc07-ut-1",
    "messageVersion": "2.0",
    "dialogEventLogs": [
        {
            "dialogStepLabel": "Intent/recording/Slot/free/Success",
            "nextStep": {
                "dialogAction": {
                    "type": "EndConversation"
                }
            }
        }
    ],
    "sessionId": "ff628d99-671a-497a-9607-5bfb5e0203fc",
    "requestAttributes": {
        "x-amz-lex:accept-content-types": "PlainText,SSML",
        "x-amz-lex:channels:platform": "Connect"
    },
    "isTestWorkbenchTraffic": false,
    "inputMode": "Speech",
    "responseReason": "UtteranceResponse",
    "bargeIn": "false",
    "operationName": "StartConversation",
    "interpretations": [
        {
            "intent": {
                "name": "recording",
                "state": "ReadyForFulfillment",
                "slots": {
                    "free": {
                        "value": {
                            "originalValue": "もしもし クラス メソット と 申し ます 先日 購入 し た 洗濯 機 の 調子 が 悪く て 連絡 し まし た 型番 は a w a の 二 千 です 昨日 の 夜 から 運転 する と 異常 な 新郎 と 大きな 音 が する よう に なり まし た 脱水 時 に 特に し とく なり ます 説明 書 を 確認 し て 設置 の 水平 度合い を 確認 し たり 洗濯 物 の 偏り が ない か 調整 し まし た が 改善 さ れ ませ ん おそらく 保証 期間 内 な の で 修理 を お 願い し たい の です が 明日 か 明後日 日経 化学 こと は 可能 でしょう か 私 の 連絡 先 は 〇 九 〇 一 五 三 四 五 六 七 八 です お 膳 中 で あれ ば 在宅 し て おり ます お 手数 です が 連絡 いただけ ます と 幸い です よろしく お 願い いたし ます",
                            "resolvedValues": [],
                            "interpretedValue": "もしもし クラス メソット と 申し ます 先日 購入 し た 洗濯 機 の 調子 が 悪く て 連絡 し まし た 型番 は a w a の 二 千 です 昨日 の 夜 から 運転 する と 異常 な 新郎 と 大きな 音 が する よう に なり まし た 脱水 時 に 特に し とく なり ます 説明 書 を 確認 し て 設置 の 水平 度合い を 確認 し たり 洗濯 物 の 偏り が ない か 調整 し まし た が 改善 さ れ ませ ん おそらく 保証 期間 内 な の で 修理 を お 願い し たい の です が 明日 か 明後日 日経 化学 こと は 可能 でしょう か 私 の 連絡 先 は 〇 九 〇 一 五 三 四 五 六 七 八 です お 膳 中 で あれ ば 在宅 し て おり ます お 手数 です が 連絡 いただけ ます と 幸い です よろしく お 願い いたし ます"
                        },
                        "shape": "Scalar"
                    }
                },
                "confirmationState": "None"
            },
            "interpretationSource": "Lex",
            "nluConfidence": "1.00"
        }
    ],
    "developerOverride": false,
    "bot": {
        "name": "cm-hirai-beyond-15seconds",
        "version": "DRAFT",
        "id": "OMRBM5O69M",
        "aliasName": "TestBotAlias",
        "aliasId": "TSTALIASID",
        "localeId": "ja_JP"
    },
    "sessionState": {
        "sessionAttributes": {
            "x-amz-lex:audio:start-timeout-ms:*:*": "8000",
            "x-amz-lex:audio:end-timeout-ms:*:*": "4000",
            "x-amz-lex:audio:max-length-ms:*:*": "50000"
        },
        "intent": {
            "name": "recording",
            "state": "ReadyForFulfillment",
            "slots": {
                "free": {
                    "value": {
                        "originalValue": "もしもし クラス メソット と 申し ます 先日 購入 し た 洗濯 機 の 調子 が 悪く て 連絡 し まし た 型番 は a w a の 二 千 です 昨日 の 夜 から 運転 する と 異常 な 新郎 と 大きな 音 が する よう に なり まし た 脱水 時 に 特に し とく なり ます 説明 書 を 確認 し て 設置 の 水平 度合い を 確認 し たり 洗濯 物 の 偏り が ない か 調整 し まし た が 改善 さ れ ませ ん おそらく 保証 期間 内 な の で 修理 を お 願い し たい の です が 明日 か 明後日 日経 化学 こと は 可能 でしょう か 私 の 連絡 先 は 〇 九 〇 一 五 三 四 五 六 七 八 です お 膳 中 で あれ ば 在宅 し て おり ます お 手数 です が 連絡 いただけ ます と 幸い です よろしく お 願い いたし ます",
                        "resolvedValues": [],
                        "interpretedValue": "もしもし クラス メソット と 申し ます 先日 購入 し た 洗濯 機 の 調子 が 悪く て 連絡 し まし た 型番 は a w a の 二 千 です 昨日 の 夜 から 運転 する と 異常 な 新郎 と 大きな 音 が する よう に なり まし た 脱水 時 に 特に し とく なり ます 説明 書 を 確認 し て 設置 の 水平 度合い を 確認 し たり 洗濯 物 の 偏り が ない か 調整 し まし た が 改善 さ れ ませ ん おそらく 保証 期間 内 な の で 修理 を お 願い し たい の です が 明日 か 明後日 日経 化学 こと は 可能 でしょう か 私 の 連絡 先 は 〇 九 〇 一 五 三 四 五 六 七 八 です お 膳 中 で あれ ば 在宅 し て おり ます お 手数 です が 連絡 いただけ ます と 幸い です よろしく お 願い いたし ます"
                    },
                    "shape": "Scalar"
                }
            },
            "confirmationState": "None"
        },
        "originatingRequestId": "f29f5da7-7dde-4f35-b671-7968cffebc07",
        "dialogAction": {
            "type": "Close"
        }
    },
    "inputTranscript": "もしもし クラス メソット と 申し ます 先日 購入 し た 洗濯 機 の 調子 が 悪く て 連絡 し まし た 型番 は a w a の 二 千 です 昨日 の 夜 から 運転 する と 異常 な 新郎 と 大きな 音 が する よう に なり まし た 脱水 時 に 特に し とく なり ます 説明 書 を 確認 し て 設置 の 水平 度合い を 確認 し たり 洗濯 物 の 偏り が ない か 調整 し まし た が 改善 さ れ ませ ん おそらく 保証 期間 内 な の で 修理 を お 願い し たい の です が 明日 か 明後日 日経 化学 こと は 可能 でしょう か 私 の 連絡 先 は 〇 九 〇 一 五 三 四 五 六 七 八 です お 膳 中 で あれ ば 在宅 し て おり ます お 手数 です が 連絡 いただけ ます と 幸い です よろしく お 願い いたし ます",
    "missedUtterance": false,
    "audioProperties": {
        "contentType": "audio/lpcm; sample-rate=8000; sample-size-bits=16; channel-count=1; is-big-endian=false",
        "duration": {
            "total": 52540,
            "silence": 2562,
            "voice": 49978
        },
        "s3Path": "cm-hirai-lex-recording/aws/lex/OMRBM5O69M/TSTALIASID/DRAFT/ja_JP/ff628d99-671a-497a-9607-5bfb5e0203fc/f29f5da7-7dde-4f35-b671-7968cffebc07-ut-1.wav"
    },
    "utteranceContext": {}
}

このログから以下の重要な情報を確認できます

  • 音声入力の時間(49.978秒)と無音の時間(2.562秒)
  • 文字起こし内容
  • 音声ファイルの保存先
  • セッション属性の適用状況

文字起こし結果を見ると、以下のような課題があることがわかります。

  • 各単語の間にスペースが挿入されている
  • 句読点が欠如している
  • 一部に誤字脱字がある

これらの問題により、文字起こし結果の可読性が低下しています。

より読みやすい文字起こし結果を得るためには、以下のような追加処理を検討できます。

  1. LexからAWS Lambdaを呼び出す
  2. Lambda内でAmazon Bedrockの言語モデルを利用し、以下の処理を行う
    • 不要なスペースの削除
    • 適切な句読点の挿入
    • 文章の自然な区切りの追加
    • 誤字脱字の校正

この追加処理により、人間にとってより読みやすい形式に文字起こし結果を整形できます。

ただし、この追加処理には AWS Lambda と Amazon Bedrock の利用に伴う追加コストが発生します。また、システムの構成がより複雑になるため、運用面での考慮も必要です。
実装の決定は、改善による利点と追加コスト・運用負荷のバランスを考慮して行ってください。

最後に

本記事では、Amazon ConnectとAmazon Lexを組み合わせて、コーディングなしで留守番電話機能を実装する方法を紹介しました。
この手法の利点は、従来のLambda関数を使用する方法と比較して、Lambda関数の開発が不要なため、実装が比較的簡単な点です。

この記事をシェアする

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.