Amazon Connectコンタクト検索の制限を解決し、コンタクトごとの会話履歴をDynamoDBに自動保存・CSV形式で一括エクスポートできるようにしてみた
はじめに
コールセンター運用において、各コンタクトごとの対応履歴の確認や管理、定期的なレポート作成は重要な業務です。
管理者は一定期間の通話内容を分析して業務改善に活かしたり、エージェントは特定顧客の過去対応履歴を素早く把握して適切な対応を行う必要があります。
本記事では、Amazon Connectの通話データを活用して、一定期間の各コンタクトごとの会話履歴をAmazon DynamoDBに自動保存し、CSV形式でエクスポートする方法を紹介します。
これらの業務要件を効率的に実現したいところですが、Amazon Connect標準機能では以下の制限があります。
- Contact Lensの要約機能の日本語非対応:会話内容の文字起こしと要約機能が標準で搭載されているが、要約機能は日本語に対応していない
- 会話履歴の一覧確認制限:一定期間の各コンタクトごとの会話履歴を確認する際、1ページあたり100件まで表示、各コンタクトの会話履歴は個別にトグルをクリックする必要がある
- 会話履歴のエクスポート制限:コンタクト検索ページから基本情報はCSV出力できるが、会話履歴は含まれないため、一定期間での全コンタクトの会話履歴エクスポートができない
そこで本記事では、これらの制限を解決するシステムを構築します。本システムは、以下のような課題を抱えている組織におすすめです。
- 一定期間の各コンタクトごとの会話履歴や要約を効率的に確認・CSV形式でエクスポートしたい
- 通話履歴を柔軟に検索・フィルタリングして業務に活用したい
- CRMは利用せず、特定の発信元電話番号の過去の対応履歴を素早く把握したい
本システムの機能
本システムでは、以下の機能でAmazon Connect標準機能の制限を解決します。
会話履歴の自動保存・検索・エクスポート機能
- 自動保存:DynamoDBに会話履歴(文字起こし・日本語要約・メタデータ)を自動保存
- 柔軟な検索:期間や発信元電話番号によるフィルタリング(1ページで各コンタクトの会話履歴・要約まで表示)
- 一括エクスポート:CSV形式での一括エクスポート
操作性の向上
- DynamoDBコンソールでは1ページあたり300件まで表示可能(Amazon Connect標準の100件から改善)
- 会話履歴を個別クリックなしで一覧表示
- CSVファイルはExcelなどのファイル形式に容易に変換可能
システム構成
処理の流れは以下の通りです。
- Contact Lensが会話を文字起こしし、Amazon S3に保存
- S3のPUTイベントがAWS CloudTrailを通じてAmazon EventBridgeにイベントを送信
- EventBridgeがAWS Step Functionsを起動
- Step Functionsが以下のデータを取得
- S3から文字起こしデータを取得
- コンタクト詳細からコンタクトデータを取得
- Amazon Bedrock (Claude) で会話の要約を生成
- 生成された要約と文字起こしなどのコンタクトデータをDynamoDBに保存
利用上の注意点
基本的な制限
途中で切断するなどでオペレーターと顧客の会話が成立していない場合、Contact Lensによる文字起こしが実行されません。この場合、S3に文字起こしデータが保存されないため、本システムがトリガーされず、DynamoDBへのデータ保存も行われません。
技術的制限
本システムには以下の技術的制限があります。
- Step Functions ペイロードサイズ制限:上限256KB(日本語文字数の目安:約8-10万文字)
- DynamoDB 項目サイズ制限:上限400KB(日本語文字数の目安:約13-14万文字)
実際の運用では、Step Functionsの256KB制限の方が先に上限に達しますが、これは極端に長時間の通話の場合のみです。
検証結果では4万文字程度の文章をステートマシンに正常処理でき、DynamoDBへの保存も問題なく行えることを確認しています。
これは通常の電話対応(4万文字を電話で話すと数時間程度)では十分な容量であり、一般的なコールセンター業務で制限に達することはほとんどありません。
DynamoDBテーブル作成
AWS CloudShellを開き以下を実行します。テーブル名は個別に変更してください。
aws dynamodb create-table \
--table-name cm-hirai-connect-contacts \
--attribute-definitions \
AttributeName=contact_id,AttributeType=S \
--key-schema \
AttributeName=contact_id,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--region ap-northeast-1
DynamoDBでは以下の属性を保存されます。
- contact_id(パーティションキー):コンタクトID
- agent_name:対応したエージェント名
- caller_phone_number:発信元電話番号
- contact_datetime:コンタクトの日時
- contact_url:コンタクト詳細ページのURL
- summary:会話の文字起こしの要約
- transcript:会話の文字起こし原文
- ttl:Time to Live (TTL) 1ヶ月
ttlのみ数値で、他は文字列です。
Connectフロー設定
フローは以下のように、キューに転送させるシンプルなフローを利用します。
Step Functions
以下のステートマシン定義を使用します。
{
"Comment": "Amazon Connect会話履歴をDynamoDBに保存するワークフロー - 改善版",
"StartAt": "DefineConstants",
"QueryLanguage": "JSONata",
"States": {
"DefineConstants": {
"Type": "Pass",
"Comment": "ワークフローで使用する定数を定義。インスタンス情報、DynamoDB、Bedrockの設定値を一元管理",
"Assign": {
"instanceId": "xxxxx",
"instanceAlias": "xxxx",
"dynamodbTable": "cm-hirai-connect-contacts",
"bedrockModel": "anthropic.claude-3-5-sonnet-20240620-v1:0",
"ttlDays": 30,
"maxTokens": 1000,
"temperature": 0.5,
"timezone": "Asia/Tokyo"
},
"Next": "GetTranscriptFromS3"
},
"GetTranscriptFromS3": {
"Type": "Task",
"Comment": "EventBridgeから受信したS3イベント情報を使用して、文字起こしJSONファイルをS3から取得",
"Resource": "arn:aws:states:::aws-sdk:s3:getObject",
"Arguments": {
"Bucket": "{% $states.context.Execution.Input.detail.requestParameters.bucketName %}",
"Key": "{% $states.context.Execution.Input.detail.requestParameters.key %}"
},
"Assign": {
"transcriptData": "{% $parse($states.result.Body) %}",
"s3Key": "{% $states.context.Execution.Input.detail.requestParameters.key %}"
},
"Next": "ExtractContactIdAndTranscript"
},
"ExtractContactIdAndTranscript": {
"Type": "Pass",
"Comment": "S3キーからContactIDを抽出し、JSONから会話内容を整形。Connect詳細画面のURLも生成",
"Assign": {
"contactId": "{% $substringBefore($split($s3Key, '/')[-1], '_analysis') %}",
"transcriptText": "{% $join($map($transcriptData.Transcript,function($t){'[' & ($t.ParticipantId = 'AGENT' ? 'AGENT' : 'CUSTOMER') & ']' & $t.Content}),' ') %}",
"contactUrl": "{% 'https://' & $instanceAlias & '.my.connect.aws/contact-trace-records/details/' & $substringBefore($split($s3Key, '/')[-1], '_analysis') & '?tz=' & $timezone %}"
},
"Next": "CheckTranscriptText"
},
"CheckTranscriptText": {
"Type": "Choice",
"Comment": "文字起こしテキストが存在するかチェック。空の場合は処理を中断してエラーとする",
"Choices": [
{
"Condition": "{% $length($transcriptText) > 0 %}",
"Next": "GetContactDetails"
}
],
"Default": "NoTranscriptFound"
},
"NoTranscriptFound": {
"Type": "Fail",
"Comment": "文字起こしテキストが見つからない場合のエラー終了",
"Error": "NoTranscriptFound",
"Cause": "No transcript text found in the file."
},
"GetContactDetails": {
"Type": "Task",
"Comment": "Amazon Connect APIを使用してコンタクト詳細情報を取得。発信者番号、対応日時、エージェント名を取得",
"Resource": "arn:aws:states:::aws-sdk:connect:describeContact",
"Arguments": {
"InstanceId": "{% $instanceId %}",
"ContactId": "{% $contactId %}"
},
"Assign": {
"callerPhoneNumber": "{% $states.result.Contact.CustomerEndpoint.Address %}",
"contactDateTime": "{% $states.result.Contact.InitiationTimestamp %}",
"agentName": "{% $states.result.Contact.Attributes.Agent ? $states.result.Contact.Attributes.Agent : 'Unknown' %}"
},
"Next": "GenerateSummary"
},
"GenerateSummary": {
"Type": "Task",
"Comment": "Amazon Bedrock(Claude)を使用して会話内容の日本語要約を生成。文字数制限を適用して長すぎる文字起こしを制限",
"Resource": "arn:aws:states:::bedrock:invokeModel",
"Arguments": {
"ModelId": "{% $bedrockModel %}",
"ContentType": "application/json",
"Accept": "application/json",
"Body": {
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": "{% $maxTokens %}",
"temperature": "{% $temperature %}",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "{% '以下の通話内容を要約してください。\n\n【通話内容】\n' & $substring($transcriptText, 0, 4000) & '\n\n【要約時に含める要素】\n- 問い合わせ概要:顧客が何について問い合わせたか\n- 詳細確認:エージェントがどのような情報を確認したか\n- 対応内容:エージェントがどのように対応したか\n- 結果:最終的な解決策や顧客の反応\n\n【出力のルール】\n- 前置きや説明文は不要。要約文のみを出力する\n- 重要な情報(日付、注文番号、金額など)は具体的に含める\n- 専門用語や略語は避け、誰にでも分かりやすい表現を使用する\n- 顧客の感情の変化(不安→安心など)も可能な限り含める\n- 全体で100〜200文字程度に収める\n- 「まもなく解決予定」「対応完了」など、明確な結論で締めくくる\n- 挨拶のみや要約に値する内容がない場合は「要約対象となる具体的な問い合わせ内容がありませんでした。」と出力する\n\n【出力例】\n顧客が先週購入したヘッドフォン(注文番号A-1)の不具合について問い合わせ。エージェントは購入履歴と保証内容を確認し、製品が保証期間内であることを確認。無償交換または返金の選択肢を提案し、顧客は返金を希望。エージェントは返金手続きを開始し、5営業日以内に全額が返金される予定と案内。顧客は対応の迅速さに満足し、手続き完了。' %}"
}
]
}
]
}
},
"Assign": {
"summary": "{% $states.result.Body.content[type=\"text\"].text %}"
},
"Next": "CalculateTTL"
},
"CalculateTTL": {
"Type": "Pass",
"Comment": "DynamoDB TTL用のタイムスタンプを計算。対応日時から指定日数(デフォルト30日)後の値を設定",
"Assign": {
"ttlTimestamp": "{% $round($toMillis($contactDateTime) / 1000 + ($ttlDays * 86400)) %}"
},
"Next": "SaveToDynamoDB"
},
"SaveToDynamoDB": {
"Type": "Task",
"Comment": "すべての情報をDynamoDBに保存。コンタクト情報、文字起こし、要約、メタデータを含むレコードを作成",
"Resource": "arn:aws:states:::aws-sdk:dynamodb:putItem",
"Arguments": {
"TableName": "{% $dynamodbTable %}",
"Item": {
"contact_id": {
"S": "{% $contactId %}"
},
"caller_phone_number": {
"S": "{% $callerPhoneNumber %}"
},
"transcript": {
"S": "{% $transcriptText %}"
},
"summary": {
"S": "{% $summary %}"
},
"contact_datetime": {
"S": "{% $string($contactDateTime) %}"
},
"agent_name": {
"S": "{% $agentName %}"
},
"contact_url": {
"S": "{% $contactUrl %}"
},
"ttl": {
"N": "{% $string($ttlTimestamp) %}"
}
}
},
"End": true
}
}
}
DefineConstantsステートでは以下のパラメータを各自変更してください。
- instanceId:インスタンスID
- instanceAlias:インスタンス名
- dynamodbTable:先程作成したテーブル名
- bedrockModel:モデルID
- ttlDays:TTLの値
- maxTokens:レスポンスのトークン数制限
- timezone:タイムゾーン(Asia/Tokyo)
timezoneは、DynamoDBに保存する項目contact_url
で利用します。
各ステートの処理概要
- DefineConstants:ワークフロー定数の定義
- GetTranscriptFromS3:S3から文字起こしファイルを取得
- ExtractContactIdAndTranscript:コンタクトIDと会話内容の抽出
- CheckTranscriptText:文字起こしテキストの存在チェック
- GetContactDetails:Connect APIからコンタクト詳細情報を取得
- GenerateSummary:Bedrockを使用して会話要約を生成
- CalculateTTL:DynamoDB TTL用のタイムスタンプ計算
- SaveToDynamoDB:全情報をDynamoDBに保存
会話内容が保存されるS3バケットのS3 URIは例えば以下のとおりです。aa165854-149e-460f-94b3-facf03ae934e
がコンタクトIDになります。
- s3://amazon-connect-xxxxxxxxxx/Analysis/Voice/2025/01/01/aa165854-149e-460f-94b3-facf03ae934e_analysis_2025-01-01T01_01_01Z.json
要約生成について
プロンプト内容は以下の通りです。
プロンプトでは以下のように文字起こしの先頭から4000文字分だけを切り出しています。
以下の通話内容を要約してください。
【通話内容】
' & $substring($transcriptText, 0, 4000) & '
【要約時に含める要素】
- 問い合わせ概要:顧客が何について問い合わせたか
- 詳細確認:エージェントがどのような情報を確認したか
- 対応内容:エージェントがどのように対応したか
- 結果:最終的な解決策や顧客の反応
【出力のルール】
- 前置きや説明文は不要。要約文のみを出力する
- 重要な情報(日付、注文番号、金額など)は具体的に含める
- 専門用語や略語は避け、誰にでも分かりやすい表現を使用する
- 顧客の感情の変化(不安→安心など)も可能な限り含める
- 全体で100〜300文字程度に収める
- 「まもなく解決予定」「対応完了」など、明確な結論で締めくくる
- 挨拶のみや要約に値する内容がない場合は「要約対象となる具体的な問い合わせ内容がありませんでした。」と出力する
【出力例】
顧客が先週購入したヘッドフォン(注文番号A-1)の不具合について問い合わせ。エージェントは購入履歴と保証内容を確認し、製品が保証期間内であることを確認。無償交換または返金の選択肢を提案し、顧客は返金を希望。エージェントは返金手続きを開始し、5営業日以内に全額が返金される予定と案内。顧客は対応の迅速さに満足し、手続き完了。
先頭4000文字のみを対象とする理由
プロンプトでは文字起こしの先頭から4000文字分だけを切り出しています。これは、Bedrockがトークン数に応じて課金されるため、必要最小限の文字数に絞ることでコストを抑制するためです。
日本語で4000文字は約7千トークン程度で、電話での会話では20分以上の通話時間に相当します。
Amazon Bedrock Claude Sonnet 3.5を利用する場合、入力トークンのコストは0.003USD × 7 = 0.021USD程度です(出力トークンのコストは要約文のため、省略)。
コスト重視ではなく全文を要約したい場合は、この制限を外すことも可能です。
IAMポリシーの設定
ステートマシン作成時にIAMロールとポリシーが自動作成されますが、一部のIAMポリシーは手動で作成する必要があります。
以下のIAMポリシーを適用してください。amazon-connect-xxxxxx
は、Connectのデータストレージ用S3バケット名です。
アカウントID
やConnectインスタンスID
も各自変更してください。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::amazon-connect-xxxxxx/Analysis/Voice/*"
]
},
{
"Effect": "Allow",
"Action": [
"connect:DescribeContact"
],
"Resource": [
"arn:aws:connect:ap-northeast-1:アカウントID:instance/ConnectインスタンスID/contact/*"
]
}
]
}
EventBridge ルール作成
EventBridgeルールを作成します。このルールは、Contact LensがS3に文字起こしファイルを保存した際に、Step Functionsを自動起動するためのものです。
以下のイベントパターンを設定します。amazon-connect-xxxxxxxxxx
の部分は、各自のS3バケット名に変更してください。
{
"source": ["aws.s3"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventSource": ["s3.amazonaws.com"],
"eventName": ["PutObject"],
"requestParameters": {
"bucketName": ["amazon-connect-xxxxxxxxxx"],
"key": [{
"prefix": "Analysis/Voice/"
}]
}
}
}
文字起こしが保存されているS3バケット名は各自で変更してください。Contact Lensの文字起こしファイルは以下のパスで保存されます。
- s3://amazon-connect-xxxxxxxxxx/Analysis/Voice/2025/01/01/contact's_ID_analysis_2025-01-01T01_01_01Z.json
ターゲットは先程作成したステートマシンに設定します。
動作確認
実際に電話での会話を行い、システムの動作を確認します。
電話での会話が終了すると、システムが自動的に会話を分析し、DynamoDBに以下の情報が保存されました。
- contact_id(パーティションキー):コンタクトID
- agent_name:対応したエージェント名
- caller_phone_number:発信元電話番号
- contact_datetime:コンタクトの日時
- contact_url:コンタクト詳細ページのURL
- summary:会話の文字起こしの要約
- transcript:会話の文字起こし原文
- ttl:Time to Live (TTL) 1ヶ月
会話を2分程度した場合、S3バケットに保存されるまでに7分程度かかりました。そのため、会話終了後、DynamoDBに要約文が表示されるまでに7分程度かかりました。
なお、EventBridgeからステートマシンに渡されるイベント内容は、以下の通りです。一部省略しています。
{
"version": "0",
"id": "18587533-cc77-def2-7754-ccac36dbd29b",
"detail-type": "AWS API Call via CloudTrail",
"source": "aws.s3",
"account": "アカウントID",
"time": "2025-04-24T23:43:29Z",
"region": "ap-northeast-1",
"resources": [
],
"detail": {
"eventTime": "2025-04-24T23:43:29Z",
"eventSource": "s3.amazonaws.com",
"eventName": "PutObject",
"awsRegion": "ap-northeast-1",
"resources": [
{
"accountId": "アカウントID",
"type": "AWS::S3::Bucket",
"ARN": "arn:aws:s3:::amazon-connect-xxxxxx"
},
{
"type": "AWS::S3::Object",
"ARN": "arn:aws:s3:::amazon-connect-xxxxxx/Analysis/Voice/2025/04/24/aa165854-149e-460f-94b3-facf03ae934e_analysis_2025-02-13T02_47_18Z (3).json"
}
]
}
}
期間フィルタリング
以下のフィルタリング設定により、1/15~1/18の期間のConnectの対応履歴を確認できます。CSV形式でダウンロードも可能です。
- contact_datetime 次以上 2024-01-15T00:00:00Z
- contact_datetime 次以下 2024-01-18T00:00:00Z
CSV形式からExcelファイルへの変換方法については、以下のリンクを参照してください。
発信元電話番号での検索
発信元電話番号でフィルタリングし、過去のやり取りを遡って確認できます。
なお、十分な会話がなかった場合、要約には「要約対象となる具体的な問い合わせ内容がありませんでした。」が出力されます。
特定のフローに限定する場合
DynamoDBへの保存処理を特定のフローに限定させたい場合対象のフローに以下のとおり[コンタクト属性の設定]ブロックを追加します。例えば、キー名report
、値on
を設定します。
ステートマシンのGetContactDetailsステートにてコンタクト詳細情報の中にreport
がon
で取得できるので、この条件が存在している場合に限り、要約やDynamoDBに保存させる処理を行うことで限定できます。
{
"Contact": {
"AgentInfo": {
},
"Attributes": {
"report": "on",
"Agent": "hirai"
},
おわりに
Amazon Connectの会話履歴をDynamoDBに自動保存し、CSV形式でエクスポート機能を構築する方法を紹介しました。
このシステムにより、Amazon Connect標準機能の制限を以下のように解決できます。
- 日本語での会話要約:Contact Lensでは日本語要約に対応していないが、Bedrockを活用して日本語での会話要約を自動生成
- 会話履歴確認の操作性向上:DynamoDBコンソールでは300件/ページ表示、個別クリック不要で会話履歴を一覧表示
- 会話履歴のCSV形式エクスポート:一定期間での全コンタクトの会話履歴をCSV形式で一括エクスポート可能
Contact Lensの文字起こし機能とBedrockの要約機能を組み合わせることで、コールセンター業務の効率化を図ることができます。
参考