Amazon Connectで自動ヒアリングした内容をBedrockで要約し、メール通知する(Kinesis Data Stream + Step Functions)
はじめに
本記事では、Amazon ConnectとAmazon Lexを活用し、自動ヒアリングした内容をAmazon Bedrockで要約してメール通知する方法を、Amazon Kinesis Data Streams(以下、KDS)とAWS Step Functionsを用いて実装する手順を解説します。
この実装によるユーザー体験の流れは、以下の通りです。
ステップ | 実行者 | アクション |
---|---|---|
1 | ユーザー | 電話をかける |
2 | Connect | 「申し訳ございません。ただいま電話に出ることができません。メッセージを録音してよければ、はい、とお伝えください。」 |
3 | ユーザー | 「はい」と応答 |
4 | Connect(Lex) | 「ご要件、お名前、住所の3点をお伝えください。無音の状態が一定時間ある場合、録音が切れますので、ご了承ください。」 |
5 | ユーザー | 必要事項を伝える |
6 | Step Functions | 録音内容をBedrockで要約し、メール通知する |
7 | Connect | 「以上で録音を終了します。電話を切ります。」 |
以下に構成図を示します。
処理の内容は以下のとおりです。
- Lexボットでお問い合わせ内容をヒアリングする
- ヒアリング内容をコンタクト属性として保存する
- キー名:recording、値はヒアリング内容のテキスト
- 切断直後、問い合わせレコードがKDSにストリーミングされ、EventBridge Pipes経由でStep Functionsステートマシンを起動する
- 問い合わせレコードには、コンタクト属性(recording)、コンタクトID、発信元電話番号、日時などが保存されている
- ステートマシンでは以下の処理を行う
- ステートマシンに渡された問い合わせレコードから、ヒアリング内容であるコンタクト属性(recording)を取得する
- Bedrockを使用して、お問い合わせ内容の文章を整形する
- 整形した文章とコンタクトID、発信元電話番号等をAmazon SNSでメール送信する
以前、AWS Lambdaを利用する方法や、EventBridgeとAWS Step Functionsステートマシンを利用する方法をご紹介しました。
Lambdaを利用する方法
EventBridgeとAWS Step Functionsステートマシンを利用する方法
AWS Lambdaを利用する方法では、Lambdaのコード開発が必要となるため、運用負荷がかかります。また、EventBridgeとAWS Step Functionsステートマシンを利用する方法では、ConnectからEventBridgeへのイベント送信がベストエフォートであるため、イベントが送信されずメール通知が行われない可能性があります。
今回は、ローコードで確実にメール送信が行われるKDSとAWS Step Functionsステートマシンを使用して実装します。
Connectは、各通話ごとに問い合わせレコード(Contact Trace Record: CTR)として通話記録を保存します。
Connectでは、KDSに問い合わせレコードを出力できるため、EventBridge Pipes経由でStep Functionsステートマシンを起動できます。
前提条件
- SNSトピック作成済み
- Connectインスタンスは作成済み
- LexとConnectフローの構築、およびLexの音声入力時間の上限緩和については、以下の参考記事をもとに対応済みとします。
Lex
作成済みのLexボットのスロットfree
に以下のプロンプトを修正します。
修正前
お問い合わせ内容をお伝え下さい。無音の状態が一定時間ある場合、録音が切れますので、ご了承ください。
修正後
ご要件、お名前、住所の3点をお伝え下さい。無音の状態が一定時間ある場合、録音が切れますので、ご了承ください。
KDS
KDSは、プロビジョンドモード、シャード1で作成します。
Connectのデータストリーミングを有効化します。
ステートマシン
EventBridge Pipes内でKDSから渡される内容は、以下のとおりです。
{
"eventSource": "aws:kinesis",
"eventVersion": "1.0",
"eventID": "shardId-000000000000:xxxxxxxxx",
"eventName": "aws:kinesis:record",
"invokeIdentityArn": "arn:aws:iam::xxxxxxxxxxxx:role/service-role/Amazon_EventBridge_Pipe_cm-hirai-voicemail-to-email-sf_cbcfa279",
"awsRegion": "ap-northeast-1",
"eventSourceARN": "arn:aws:kinesis:ap-northeast-1:xxxxxxxxxxxx:stream/cm-hirai",
"kinesisSchemaVersion": "1.0",
"partitionKey": "19a1c22c-399d-4c6b-b766-9832832c9005",
"sequenceNumber": "xxxxxxxxx",
"data": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"approximateArrivalTimestamp": 1733793205.123
}
上記のうちdata
はbase64でエンコードされているため、デコードしてJSONデータをパースすることで、以下のような値が取得できます。
文字起こし内容のコンタクト属性(recording)も確認できます。
{
"data": {
"AWSAccountId": "012345678901",
"AWSContactTraceRecordFormatVersion": "2017-03-10",
"Agent": null,
"AgentConnectionAttempts": 0,
"AnsweringMachineDetectionStatus": null,
"Attributes": {
"recording": "お問い合わせ内容〇〇"
},
"Campaign": {
"CampaignId": null
},
"Channel": "VOICE",
"ConnectedToSystemTimestamp": "2024-12-10T01:11:43Z",
"ContactDetails": {},
"ContactId": "19a1c22c-399d-4c6b-b766-9832832c9005",
"CustomerEndpoint": {
"Address": "+81xxxxxxxx",
"Type": "TELEPHONE_NUMBER"
},
"CustomerVoiceActivity": null,
"DisconnectReason": "CONTACT_FLOW_DISCONNECT",
"DisconnectTimestamp": "2024-12-10T01:12:22Z",
"InitialContactId": null,
"InitiationMethod": "INBOUND",
"InitiationTimestamp": "2024-12-10T01:11:43Z",
"InstanceARN": "arn:aws:connect:ap-northeast-1:012345678901:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c",
"LastUpdateTimestamp": "2024-12-10T01:13:24Z",
"MediaStreams": [
{
"Type": "AUDIO"
}
],
"NextContactId": null,
"PreviousContactId": null,
"Queue": null,
"Recording": null,
"Recordings": null,
"References": [],
"ScheduledTimestamp": null,
"SegmentAttributes": {
"connect:Subtype": {
"ValueInteger": null,
"ValueMap": null,
"ValueString": "connect:Telephony"
}
},
"SystemEndpoint": {
"Address": "+81xxxxxxxx",
"Type": "TELEPHONE_NUMBER"
},
"Tags": {
"aws:connect:instanceId": "3ff2093d-af96-43fd-b038-3c07cdd7609c",
"aws:connect:systemEndpoint": "+81xxxxxxxx"
},
"TaskTemplateInfo": null,
"TransferCompletedTimestamp": null,
"TransferredToEndpoint": null,
"VoiceIdResult": null
}
}
以下のステートマシンを作成します。ワークフローとしては、以下の図の通りです。
以下のコードをそのまま貼り付けてください。
TopicArn
は、各自変更ください。
{
"Comment": "Voicemail to Email Notifier State Machine with JSONata",
"StartAt": "Base64Decode",
"QueryLanguage": "JSONata",
"States": {
"Base64Decode": {
"Type": "Pass",
"Next": "ParseJSON",
"Assign": {
"decodedData": "{% $base64decode($states.input.data) %}"
}
},
"ParseJSON": {
"Type": "Pass",
"Next": "Choice",
"Assign": {
"data": "{% $parse($decodedData) %}",
"InitialContactId": "{% $parse($decodedData).ContactId %}",
"Time": "{% $parse($decodedData).InitiationTimestamp %}",
"CallerPhoneNumber": "{% $parse($decodedData).SystemEndpoint.Address %}"
}
},
"Choice": {
"Type": "Choice",
"Choices": [
{
"Condition": "{% $data.Attributes.recording != null %}",
"Next": "Bedrock InvokeModel",
"Assign": {
"recording": "{% $data.Attributes.recording %}"
}
}
],
"Default": "Pass"
},
"Bedrock InvokeModel": {
"Type": "Task",
"Resource": "arn:aws:states:::bedrock:invokeModel",
"Arguments": {
"ModelId": "arn:aws:bedrock:ap-northeast-1::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0",
"Body": {
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 1000,
"temperature": 0,
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "{% '以下の音声文字起こしテキストから、ご要件、名前、住所の3つの情報を抽出し、以下のフォーマットで整理してください:\n\nご要件:[内容を自然な日本語で記述]\n\nお名前:[名前を記述]\n\nご住所:[住所を記述]\n\nなお、情報が不足している場合は「情報なし」と記載してください。\n余計な説明は不要です。上記フォーマットのみを返してください。\n\nテキスト:\n' & $recording %}"
}
]
}
]
}
},
"Next": "SNS Publish",
"Assign": {
"bedrock_text": "{% $states.result.Body.content[type=\"text\"].text %}"
}
},
"SNS Publish": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"Arguments": {
"Message": "{% 'お問い合わせ情報\n\n Contact ID: ' & $InitialContactId & '\n\n・日時:' & $Time & '\n\n・電話番号:' & $CallerPhoneNumber & '\n\n・お問い合わせ内容(整形済み文章)' & '\n\n' & $bedrock_text & '\n\n・お問い合わせ内容(未整形文章)' & '\n\n' & $recording %}",
"TopicArn": "arn:aws:sns:ap-northeast-1:xxxxxxxxxxx:cm-hirai"
},
"End": true
},
"Pass": {
"Type": "Pass",
"Comment": "No recording attribute found, ending workflow.",
"End": true
}
}
}
IAMロールは自動作成されます。
ステートマシンで処理している内容は以下のとおりです。
- KDSからEventBridge Pipes経由でステートマシンをトリガーする
- ヒアリング内容であるコンタクト属性(recording)やコンタクトID、発信元電話番号などが渡される
- コンタクト属性(recording)がnullではないか確認
- BedrockのClaudeを利用して、文字起こし内容を整形する
- 整形した内容をSNSトピックを通じてメール送信する
ConnectインスタンスからKDSへのストリーミングは、特定のフローに絞ることはできず、Connectインスタンス単位で有効化されます。そのため、コンタクト属性(recording)がnullではないかを確認する必要があります。
EventBridge Pipes
EventBridge Pipesを作成します。
ソースにはKDSを設定します。
ターゲットにはStep Functionsを設定します。
IAMロール名は自動で作成されます。
Connectフロー
Connectフローでは、以下の1つのブロックを追加します。
「コンタクト属性の設定」ブロックを追加します。このブロックでは、recording
キーにLexでヒアリングした内容が格納されます。
作成したConnectフローのjsonファイル(クリックで展開)
{
"Version": "2019-10-30",
"StartAction": "17a389ed-1002-4550-9bbf-efbbe3811cf9",
"Metadata": {
"entryPointPosition": {
"x": 36.8,
"y": 65.6
},
"ActionMetadata": {
"1990a178-5825-414e-bd24-2bc3dc5dc937": {
"position": {
"x": 357.6,
"y": 61.6
},
"children": [
"dfd9756f-f2b0-42ee-9abd-dbd0c4d41340"
],
"overrideConsoleVoice": true,
"fragments": {
"SetContactData": "dfd9756f-f2b0-42ee-9abd-dbd0c4d41340"
},
"overrideLanguageAttribute": true
},
"dfd9756f-f2b0-42ee-9abd-dbd0c4d41340": {
"position": {
"x": 357.6,
"y": 61.6
},
"dynamicParams": []
},
"17a389ed-1002-4550-9bbf-efbbe3811cf9": {
"position": {
"x": 140.8,
"y": 60.8
}
},
"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": "2e6d6b24-8464-46a6-8170-1e1714c401a3",
"operator": {
"name": "Equals",
"value": "Equals",
"shortDisplay": "="
},
"value": "recording"
}
]
},
"465002a2-1f89-476b-8f58-e0e2671632c7": {
"position": {
"x": 820.8,
"y": 271.2
}
},
"1a41bbb2-6b65-4bf0-871b-deaf288ba7a1": {
"position": {
"x": 819.2,
"y": 460.8
}
},
"3c2cba41-f8a9-418f-b428-c573abb3c010": {
"position": {
"x": 1259.2,
"y": 526.4
}
},
"acf5bea6-926a-42f6-a9c7-2f9cf4af97ef": {
"position": {
"x": 819.2,
"y": 53.6
},
"parameters": {
"Attributes": {
"recording": {
"useDynamic": true
}
}
},
"dynamicParams": [
"recording"
]
},
"a1441575-8861-43f3-91a3-06ab8a938b2d": {
"position": {
"x": 1044.8,
"y": 51.2
}
}
},
"Annotations": [],
"name": "cm-hirai-voicemail-to-email-notifier-for-stepfunctions-from-KDS",
"description": "",
"type": "contactFlow",
"status": "published",
"hash": {}
},
"Actions": [
{
"Parameters": {
"TextToSpeechEngine": "Neural",
"TextToSpeechStyle": "None",
"TextToSpeechVoice": "Kazuha"
},
"Identifier": "1990a178-5825-414e-bd24-2bc3dc5dc937",
"Type": "UpdateContactTextToSpeechVoice",
"Transitions": {
"NextAction": "dfd9756f-f2b0-42ee-9abd-dbd0c4d41340"
}
},
{
"Parameters": {
"LanguageCode": "ja-JP"
},
"Identifier": "dfd9756f-f2b0-42ee-9abd-dbd0c4d41340",
"Type": "UpdateContactData",
"Transitions": {
"NextAction": "0cebb197-9920-4fcc-8410-a2f9e9fb12ff",
"Errors": [
{
"NextAction": "0cebb197-9920-4fcc-8410-a2f9e9fb12ff",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"FlowLoggingBehavior": "Enabled"
},
"Identifier": "17a389ed-1002-4550-9bbf-efbbe3811cf9",
"Type": "UpdateFlowLoggingBehavior",
"Transitions": {
"NextAction": "1990a178-5825-414e-bd24-2bc3dc5dc937"
}
},
{
"Parameters": {
"Text": "申し訳ございません。ただいま電話に出ることができません。メッセージを録音してよければ、はい、とお伝え下さい。",
"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"
},
"LexV2Bot": {
"AliasArn": "arn:aws:lex:ap-northeast-1:012345678901:bot-alias/OMRBM5O69M/TSTALIASID"
}
},
"Identifier": "0cebb197-9920-4fcc-8410-a2f9e9fb12ff",
"Type": "ConnectParticipantWithLexBot",
"Transitions": {
"NextAction": "1a41bbb2-6b65-4bf0-871b-deaf288ba7a1",
"Conditions": [
{
"NextAction": "acf5bea6-926a-42f6-a9c7-2f9cf4af97ef",
"Condition": {
"Operator": "Equals",
"Operands": [
"recording"
]
}
}
],
"Errors": [
{
"NextAction": "465002a2-1f89-476b-8f58-e0e2671632c7",
"ErrorType": "NoMatchingCondition"
},
{
"NextAction": "1a41bbb2-6b65-4bf0-871b-deaf288ba7a1",
"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": "1a41bbb2-6b65-4bf0-871b-deaf288ba7a1",
"Type": "MessageParticipant",
"Transitions": {
"NextAction": "3c2cba41-f8a9-418f-b428-c573abb3c010",
"Errors": [
{
"NextAction": "3c2cba41-f8a9-418f-b428-c573abb3c010",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {},
"Identifier": "3c2cba41-f8a9-418f-b428-c573abb3c010",
"Type": "DisconnectParticipant",
"Transitions": {}
},
{
"Parameters": {
"Attributes": {
"recording": "$.Lex.Slots.free"
},
"TargetContact": "Current"
},
"Identifier": "acf5bea6-926a-42f6-a9c7-2f9cf4af97ef",
"Type": "UpdateContactAttributes",
"Transitions": {
"NextAction": "a1441575-8861-43f3-91a3-06ab8a938b2d",
"Errors": [
{
"NextAction": "a1441575-8861-43f3-91a3-06ab8a938b2d",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"Text": "以上で録音を終了します。電話を切ります。"
},
"Identifier": "a1441575-8861-43f3-91a3-06ab8a938b2d",
"Type": "MessageParticipant",
"Transitions": {
"NextAction": "3c2cba41-f8a9-418f-b428-c573abb3c010",
"Errors": [
{
"NextAction": "3c2cba41-f8a9-418f-b428-c573abb3c010",
"ErrorType": "NoMatchingError"
}
]
}
}
]
}
試してみた
架電して発話すると、以下のメールが通知されました。
いくつかのサンプル発話を用意し、それぞれのメール通知内容を確認しました。結果は、Lambdaを使用した以下の記事と同じですので、ぜひご参照ください。
最後に
本記事では、Amazon Connect、Amazon Lex、Amazon Bedrockを組み合わせて、自動ヒアリング内容を要約し、メール通知する構築方法について解説しました。
Lambdaを利用せずローコードでできる点がよいですね。
ただし、問い合わせレコードは、確実にKDSへ配信されますが、重複される可能性はありますので、その点はご注意ください。
Amazon Connect は、少なくとも 1 回問い合わせレコードを配信します。
問い合わせレコードは、最初の配信後に新しい情報が到着するなど、複数の理由で再度配信される場合があります。
https://docs.aws.amazon.com/ja_jp/connect/latest/adminguide/ctr-data-model.html#important-things-to-know-ctr-data-model
参考