Amazon Connectで自動ヒアリングした内容をBedrockで要約し、メール通知する(EventBridge + Step Functions)
はじめに
本記事では、Amazon ConnectとAmazon Lexを活用して自動ヒアリングした内容をAmazon Bedrockで要約し、メール通知する方法を、Amazon EventBridgeとAWS Step Functionsを用いて実装する手順を解説します。
この実装によるユーザー体験の流れは、以下の通りです。
ステップ | 実行者 | アクション |
---|---|---|
1 | ユーザー | 電話をかける |
2 | Connect | 「申し訳ございません。ただいま電話に出ることができません。メッセージを録音してよければ、はい、とお伝えください。」 |
3 | ユーザー | 「はい」と応答 |
4 | Connect(Lex) | 「ご要件、お名前、住所の3点をお伝えください。無音の状態が一定時間ある場合、録音が切れますので、ご了承ください。」 |
5 | ユーザー | 必要事項を伝える |
6 | Step Functions | 録音内容をBedrockで要約し、メール通知する |
7 | Connect | 「以上で録音を終了します。電話を切ります。」 |
以下に構成図を示します。
処理の内容は以下のとおりです。
- Lexボットでお問い合わせ内容をヒアリングする
- ヒアリング内容をコンタクト属性として保存する
- キー名:recording、値はヒアリング内容のテキスト
- コンタクト属性の保存イベントがEventBridgeに送信され、Step Functionsステートマシンをトリガーする
- ステートマシンでは以下の処理を行う
- ステートマシンに渡されたコンタクトIDとインスタンスIDをもとにコンタクト属性(recording)を取得する
- Bedrockを使用して、お問い合わせ内容の文章を整形する
- 整形した文章とコンタクトID、発信元電話番号等をAmazon SNSでメール送信する
前回、AWS Lambdaを利用して、ConnectフローからLambdaを呼び出す方法で要約しメール通知する方法をご紹介しました。
今回は、Lambdaを利用せず、EventBridgeとAWS Step Functionsステートマシンを使用して、ローコードかつ非同期で実装してみます。
コンタクト属性を取得する方法
ヒアリング内容をコンタクト属性として保存し、ステートマシンでヒアリング内容をもとにBedrockで文章を整形させるためには、ステートマシンにコンタクト属性を渡す必要があります。
ただし、ConnectからEventBridgeに送信される問い合わせイベントには、コンタクト属性に関する情報が含まれていません。
そのため、ConnectからEventBridgeルールを経由して、ステートマシンに直接コンタクト属性を渡すことはできません。
ステートマシンでコンタクト属性(今回はrecording
)を取得するには、「コンタクト属性の設定」で保存されたイベントをEventBridgeに送信し、EventBridgeルールからステートマシンをトリガーして、コンタクトIDとインスタンスIDを渡します。
その後、ステートマシンで2つの情報をもとにgetContactAttributes
を実行することで、コンタクト属性を取得できます。
前提条件
- SNSトピック作成済み
- Connectインスタンスは作成済み
- LexとConnectフローの構築、およびLexの音声入力時間の上限緩和については、以下の参考記事をもとに対応済みとします。
Lex
作成済みのLexボットのスロットfree
に以下のプロンプトを修正します。
修正前
お問い合わせ内容をお伝え下さい。無音の状態が一定時間ある場合、録音が切れますので、ご了承ください。
修正後
ご要件、お名前、住所の3点をお伝え下さい。無音の状態が一定時間ある場合、録音が切れますので、ご了承ください。
ステートマシン作成
以下のステートマシンを作成します。ワークフローとしては、以下の図の通りです。
以下のコードをそのまま貼り付けてください。
TopicArn
は、各自変更ください。
{
"Comment": "Voicemail to Email Notifier State Machine with JSONata",
"StartAt": "GetContactAttributes",
"QueryLanguage": "JSONata",
"States": {
"GetContactAttributes": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:connect:getContactAttributes",
"Arguments": {
"InstanceId": "{% $states.input.detail.tags.\"aws:connect:instanceId\" %}",
"InitialContactId": "{% $states.input.detail.contactId %}"
},
"Assign": {
"recording": "{% $states.result.Attributes.recording %}",
"InitialContactId": "{% $states.input.detail.contactId %}",
"Time": "{% $states.input.detail.initiationTimestamp %}",
"CallerPhoneNumber": "{% $states.input.detail.tags.\"aws:connect:systemEndpoint\" %}"
},
"Next": "Choice",
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"Next": "Pass1"
}
]
},
"Pass1": {
"Type": "Pass",
"Comment": "No recording attribute found, ending workflow.",
"End": true
},
"Choice": {
"Type": "Choice",
"Choices": [
{
"Condition": "{% $recording != null %}",
"Next": "Bedrock InvokeModel"
}
],
"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,
"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:012345678901:test"
},
"End": true
},
"Pass": {
"Type": "Pass",
"Comment": "No recording attribute found, ending workflow.",
"End": true
}
}
}
IAMロールは自動作成されます。
IAMロールには以下を追加で適用しました。
- AmazonConnectReadOnlyAccess
ステートマシンで処理している内容は以下のとおりです。
- EventBridgeからステートマシンをトリガーする
- コンタクトIDとConnectインスタンスIDが渡される
- コンタクトIDとConnectインスタンスIDをもとに、Connectの
GetContactAttributes
でコンタクト属性を取得する- コンタクト属性(キー名:recording、値:文字起こし内容)
- キー名がrecordingのコンタクト属性の値が
null
ではないか確認 - BedrockのClaudeを利用して、文字起こし内容を整形する
- 整形した内容をSNSトピックを通じてメール送信する
今回設定したステートマシンのコードの詳細は、以下の記事で解説しています。
Connectフロー
Connectフローでは、以下の3つのブロックを追加します。
「コンタクトのタグ」ブロックでは、フローIDを設定します。
タグキー名やタグ値は、EventBridgeのイベントパターンに一致していれば任意の値で構いません。
次に、「コンタクト属性の設定」ブロックを追加します。このブロックでは、recording
キーにLexでヒアリングした内容が格納されます。
続いて、2つ目の「コンタクトのタグ」ブロックで、先ほど設定したタグを解除します。タグを解除しない場合、以降のフローで「コンタクト属性の設定」ブロックを追加した際に、そのブロックもEventBridgeのイベントパターンに一致し、意図せずメールが送信されてしまう可能性があります。そのため、他の「コンタクト属性の設定」ブロックを追加する場合に備え、イベントパターンに一致しないようタグを削除しておきます。
タグの解除
「プロンプトの設定」の後に「コンタクトのタグ」ブロックでタグを解除する理由は、問い合わせイベントが順番通りに配信されるとは限らないためです。
EventBridge イベントは順序付けされていませんが、データの使用に役立つタイムスタンプがあります
https://docs.aws.amazon.com/ja_jp/connect/latest/adminguide/contact-events.html#subscribe-contact-events
「プロンプトの設定」の前に「コンタクトのタグ」ブロックでタグを解除すると、「コンタクト属性の設定」でrecording
キーを設定した際に、EventBridgeからステートマシンがトリガーされませんでした。
これは、おそらくrecording
キーの保存イベントよりも先に、タグ解除イベントがEventBridgeに送信されたためと推測されます。
flow-idタグが解除された問い合わせイベント(コンタクト属性の更新)が送信されたため、EventBridgeはイベントパターンに一致しない
作成した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": [
"93d38bc7-7f3c-4179-bab6-8424dbb36d2f"
],
"overrideConsoleVoice": true,
"fragments": {
"SetContactData": "93d38bc7-7f3c-4179-bab6-8424dbb36d2f"
},
"overrideLanguageAttribute": true
},
"93d38bc7-7f3c-4179-bab6-8424dbb36d2f": {
"position": {
"x": 357.6,
"y": 61.6
},
"dynamicParams": []
},
"17a389ed-1002-4550-9bbf-efbbe3811cf9": {
"position": {
"x": 140.8,
"y": 60.8
}
},
"465002a2-1f89-476b-8f58-e0e2671632c7": {
"position": {
"x": 820.8,
"y": 271.2
}
},
"1a41bbb2-6b65-4bf0-871b-deaf288ba7a1": {
"position": {
"x": 819.2,
"y": 460.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": "6677abb4-f238-4092-9f4b-d924c77efa5c",
"operator": {
"name": "Equals",
"value": "Equals",
"shortDisplay": "="
},
"value": "recording"
}
]
},
"31e60028-3050-4544-a814-051fbdbc179f": {
"position": {
"x": 828.8,
"y": 53.6
}
},
"acf5bea6-926a-42f6-a9c7-2f9cf4af97ef": {
"position": {
"x": 1055.2,
"y": 55.2
},
"parameters": {
"Attributes": {
"recording": {
"useDynamic": true
}
}
},
"dynamicParams": [
"recording"
]
},
"3c2cba41-f8a9-418f-b428-c573abb3c010": {
"position": {
"x": 1749.6,
"y": 156
}
},
"a1441575-8861-43f3-91a3-06ab8a938b2d": {
"position": {
"x": 1274.4,
"y": 57.6
}
},
"c8498933-3133-4456-87a4-56abac68f155": {
"position": {
"x": 1497.6,
"y": 67.2
}
}
},
"Annotations": [],
"name": "cm-hirai-voicemail-to-email-notifier-for-stepfunctions",
"description": "",
"type": "contactFlow",
"status": "PUBLISHED",
"hash": {}
},
"Actions": [
{
"Parameters": {
"TextToSpeechEngine": "Neural",
"TextToSpeechStyle": "None",
"TextToSpeechVoice": "Kazuha"
},
"Identifier": "1990a178-5825-414e-bd24-2bc3dc5dc937",
"Type": "UpdateContactTextToSpeechVoice",
"Transitions": {
"NextAction": "93d38bc7-7f3c-4179-bab6-8424dbb36d2f"
}
},
{
"Parameters": {
"LanguageCode": "ja-JP"
},
"Identifier": "93d38bc7-7f3c-4179-bab6-8424dbb36d2f",
"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": "録音されないということで承知しました。電話を切ります。"
},
"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": {
"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": "31e60028-3050-4544-a814-051fbdbc179f",
"Condition": {
"Operator": "Equals",
"Operands": [
"recording"
]
}
}
],
"Errors": [
{
"NextAction": "465002a2-1f89-476b-8f58-e0e2671632c7",
"ErrorType": "NoMatchingCondition"
},
{
"NextAction": "1a41bbb2-6b65-4bf0-871b-deaf288ba7a1",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"Tags": {
"flow-id": "75c8b24a-9ea2-49a7-ab12-0f5f235d3890"
}
},
"Identifier": "31e60028-3050-4544-a814-051fbdbc179f",
"Type": "TagContact",
"Transitions": {
"NextAction": "acf5bea6-926a-42f6-a9c7-2f9cf4af97ef",
"Errors": [
{
"NextAction": "acf5bea6-926a-42f6-a9c7-2f9cf4af97ef",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"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": {},
"Identifier": "3c2cba41-f8a9-418f-b428-c573abb3c010",
"Type": "DisconnectParticipant",
"Transitions": {}
},
{
"Parameters": {
"Text": "以上で録音を終了します。電話を切ります。"
},
"Identifier": "a1441575-8861-43f3-91a3-06ab8a938b2d",
"Type": "MessageParticipant",
"Transitions": {
"NextAction": "c8498933-3133-4456-87a4-56abac68f155",
"Errors": [
{
"NextAction": "c8498933-3133-4456-87a4-56abac68f155",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"TagKeys": [
"flow-id"
]
},
"Identifier": "c8498933-3133-4456-87a4-56abac68f155",
"Type": "UntagContact",
"Transitions": {
"NextAction": "3c2cba41-f8a9-418f-b428-c573abb3c010",
"Errors": [
{
"NextAction": "3c2cba41-f8a9-418f-b428-c573abb3c010",
"ErrorType": "NoMatchingError"
}
]
}
}
]
}
EventBridge
EventBridgeでは以下のイベントパターンを設定し、トリガーターゲットは、ステートマシンを設定します。
{
"source": ["aws.connect"],
"detail-type": ["Amazon Connect Contact Event"],
"detail": {
"eventType": ["CONTACT_DATA_UPDATED"],
"instanceArn": [
"arn:aws:connect:ap-northeast-1:123456789012:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c"
],
"tags": {
"flow-id": ["75c8b24a-9ea2-49a7-ab12-0f5f235d3890"]
},
"updatedProperties": ["UserDefinedAttributes"]
}
}
updatedProperties
では、「ユーザー定義のコンタクト属性が更新されたイベント」を基にフィルターしています。
一方、tags
では、「ユーザー定義のコンタクト属性が更新されたイベント」の中から、フロー内で設定したタグを基にフィルターを行います。
これら2つの条件を組み合わせることで、コンタクト属性の更新イベントのうち、ユーザー定義で設定したコンタクト属性recording
の更新に対してのみ、EventBridgeを経由してステートマシンをトリガーすることが可能になります。
CONTACT_DATA_UPDATED
は、発行されるイベントタイプの1つです。
CONTACT_DATA_UPDATED - 音声通話、チャット、またはタスクで 1 つ以上の問い合わせプロパティが更新されました。スケジュールされたタイムスタンプ (タスクのみ)、ユーザー定義の属性とタグ、ルーティング条件が更新されるか、ステップの有効期限が切れています。また、Contact Lens が特定の問い合わせに対して有効になっているかどうか。
試してみた
架電して発話すると、以下のようなメールが通知されました。
いくつかのサンプル発話を用意し、それぞれのメール通知内容を確認しました。結果は、Lambdaを使用した以下の記事と同じですので、ぜひご参照ください。
最後に
本記事では、Amazon Connect、Amazon Lex、Amazon Bedrockを組み合わせて、自動ヒアリング内容を要約し、メール通知する仕組みを解説しました。
Lambdaを利用せずローコードでできる点がよいですね。
参考