Amazon Connectの通話終了直後に、AWS Step Functionsで即文字起こし内容を要約してコンタクト詳細に表示させてみた

Amazon Connectの通話終了直後に、AWS Step Functionsで即文字起こし内容を要約してコンタクト詳細に表示させてみた

2025.12.24

はじめに

Amazon Connect Contact Lensによる音声の文字起こしには、以下の2種類があります。

  • 通話後の分析
    • 文字起こしの精度を最大限に高めるために推奨されます。
  • リアルタイムの分析
    • 通話中にリアルタイムで文字起こしが行われます。

以前、Lambdaを利用して、Amazon Connectで通話終了直後に、文字起こし内容を要約してコンタクト詳細に表示させる方法を紹介しました。

https://dev.classmethod.jp/articles/amazon-connect-realtime-transcript-summary/

今回は、Lambda関数コードを管理せず、AWS Step Functionsを利用し、通話終了直後にコンタクト詳細へ要約を保存する方法を紹介します。
一部の設定を変更すれば、コンタクト詳細への保存だけでなく、CRMやAmazon DynamoDBへの保存といった連携も可能です。

なお、「リアルタイムの分析」を利用するデメリットは、「通話後の分析」よりも文字起こし精度が低い点にありますので、十分な検証が必要です。

以前、精度の比較をしたことがありますので、ご参考ください。

https://dev.classmethod.jp/articles/amazon-connect-contact-lens-transcription-accuracy-2024-7/#%25E6%25A4%259C%25E8%25A8%25BC%25E7%25B5%2590%25E6%259E%259C

構成

構成は以下のとおりです。

cm-hirai-screenshot 2025-12-23 16.12.13

処理の流れは以下のとおりです。

  1. 通話終了後、EventBridgeルールが起動し、Step Functionsを実行
  2. Step Functionsが、EventBridgeルールから渡されたイベント情報をもとに、ListRealtimeContactAnalysisSegments APIでリアルタイム文字起こしを取得
  3. Step Functionsが、Amazon Bedrockで要約を生成
  4. Step Functionsが、connect:UpdateContactAttributes APIを使い、要約をコンタクト属性としてConnectに保存

Step Functions

ステートマシンの全体像は以下のとおりです。
cm-hirai-screenshot 2025-12-23 16.16.32

{
  "QueryLanguage": "JSONata",
  "Comment": "Amazon Connect Real-time Summary",
  "StartAt": "Initialize",
  "States": {
    "Initialize": {
      "Type": "Pass",
      "Comment": "EventBridgeの入力から必要なIDを抽出し、変数を初期化します",
      "Assign": {
        "contactId": "{% $states.input.detail.contactId %}",
        "instanceId": "{% $split($states.input.detail.instanceArn, '/')[-1] %}",
        "transcriptText": "",
        "nextToken": null
      },
      "Next": "GetSegments"
    },
    "GetSegments": {
      "Type": "Task",
      "Resource": "arn:aws:states:::aws-sdk:connectcontactlens:listRealtimeContactAnalysisSegments",
      "Comment": "リアルタイム分析セグメントを取得します",
      "Arguments": {
        "InstanceId": "{% $instanceId %}",
        "ContactId": "{% $contactId %}",
        "MaxResults": 100,
        "NextToken": "{% $nextToken %}"
      },
      "Retry": [
        {
          "ErrorEquals": [
            "Connect.ThrottlingException",
            "ThrottlingException",
            "Connect.TooManyRequestsException"
          ],
          "IntervalSeconds": 2,
          "MaxAttempts": 10,
          "BackoffRate": 2,
          "JitterStrategy": "FULL"
        },
        {
          "ErrorEquals": [
            "Connect.InternalServiceException",
            "Connect.ServiceUnavailableException"
          ],
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "BackoffRate": 2
        }
      ],
      "Assign": {
        "currentSegments": "{% $states.result.Segments %}",
        "nextToken": "{% $exists($states.result.NextToken) ? $states.result.NextToken : null %}"
      },
      "Next": "ProcessSegments"
    },
    "ProcessSegments": {
      "Type": "Pass",
      "Comment": "取得したセグメントからテキストを抽出し、変数に追記します",
      "Assign": {
        "transcriptText": "{% $transcriptText & $join($currentSegments.Transcript[Content != null].('[' & ParticipantRole & '] ' & Content), '\n') & '\n' %}"
      },
      "Next": "CheckLoop"
    },
    "CheckLoop": {
      "Type": "Choice",
      "Comment": "NextTokenがある場合はループします",
      "Choices": [
        {
          "Condition": "{% $nextToken != null %}",
          "Next": "WaitBeforeNextPage"
        }
      ],
      "Default": "CheckTranscriptContent"
    },
    "WaitBeforeNextPage": {
      "Type": "Wait",
      "Comment": "APIスロットリング回避のための待機",
      "Seconds": 1,
      "Next": "GetSegments"
    },
    "CheckTranscriptContent": {
      "Type": "Choice",
      "Comment": "会話内容が空、または極端に短い(10文字以下)場合は要約をスキップします",
      "Choices": [
        {
          "Condition": "{% $length($trim($transcriptText)) < 10 %}",
          "Next": "SkipSummary"
        }
      ],
      "Default": "GenerateSummary"
    },
    "SkipSummary": {
      "Type": "Pass",
      "Comment": "会話内容がなかったため処理を終了します",
      "End": true
    },
    "GenerateSummary": {
      "Type": "Task",
      "Resource": "arn:aws:states:::aws-sdk:bedrockruntime:converse",
      "Comment": "Bedrock RuntimeのConverse APIで要約を生成します",
      "Arguments": {
        "ModelId": "jp.anthropic.claude-sonnet-4-5-20250929-v1:0",
        "Messages": [
          {
            "Role": "user",
            "Content": [
              {
                "Text": "{% '通話記録の要約をお願いします。\n\n通話内容:\n' & $transcriptText & '\n\n要約内容:\n1. 顧客の問い合わせ内容\n2. エージェントの対応内容\n3. 結論(解決・今後の対応など)\n\n制約:\n- 通話内容に忠実であること\n- 推測はしないこと\n- 具体的な情報(日付、注文番号など)は明記すること\n\n出力形式:\n- 3~4 文\n- 自然な文章\n- 前置きなし、要約のみ' %}"
              }
            ]
          }
        ],
        "InferenceConfig": {
          "MaxTokens": 1000,
          "Temperature": 0.1
        }
      },
      "Assign": {
        "summaryText": "{% $states.result.Output.Message.Content[0].Text %}"
      },
      "Next": "UpdateContactAttributes"
    },
    "UpdateContactAttributes": {
      "Type": "Task",
      "Resource": "arn:aws:states:::aws-sdk:connect:updateContactAttributes",
      "Comment": "要約結果をConnectのコンタクト属性に保存します",
      "Arguments": {
        "InstanceId": "{% $instanceId %}",
        "InitialContactId": "{% $contactId %}",
        "Attributes": {
          "realtimesummary": "{% $summaryText %}"
        }
      },
      "End": true
    }
  }
}

このステートマシンは、以下の処理を行います。

  1. 初期化 (Initialize): EventBridgeから渡されたイベント情報から、コンタクトIDとインスタンスIDを抽出します。
  2. 文字起こしの取得 (GetSegments / ProcessSegments): ListRealtimeContactAnalysisSegments APIを使用し、リアルタイム文字起こしデータを取得します。ページネーション(NextToken)に対応しており、会話が長い場合でもループ処理ですべてのセグメントを取得し、テキストデータとして結合します。
  3. 要約の生成 (GenerateSummary): 取得した会話テキストをAmazon Bedrock(Claudeモデル)に渡し、要約を生成します。会話内容が極端に短い場合はスキップする判定も入れています。
  4. コンタクト属性の更新 (UpdateContactAttributes): 生成された要約を、Amazon Connectのコンタクト属性(realtimesummary)に保存します。

ListRealtimeContactAnalysisSegments APIには、1秒あたり1リクエストというレート制限(RateLimit)があります。

ListRealtimeContactAnalysisSegments: RateLimit は 1 秒あたり 1 リクエスト、BurstLimit は 1 秒あたり 2 リクエストになります。
https://docs.aws.amazon.com/ja_jp/connect/latest/adminguide/amazon-connect-service-limits.html

そのため、ステートマシン内ではループ処理の間に Wait ステート(1秒待機)を挟むことで、APIスロットリングエラーを回避する設計にしています。また、万が一スロットリングが発生した場合に備え、Retry(再試行)設定も行っています。

また、ListRealtimeContactAnalysisSegments APIは、チャネルが音声のみサポートしており、音声データは24時間以内に取得する必要があります。

ListRealtimeContactAnalysisSegments: 音声のコンタクトに使用します。
https://docs.aws.amazon.com/ja_jp/connect/latest/adminguide/contact-lens-api.html

Voice data is retained for 24 hours. You must invoke this API during that time.
https://docs.aws.amazon.com/connect/latest/APIReference/API_connect-contact-lens_ListRealtimeContactAnalysisSegments.html

なお、「リアルタイムの分析」の文字起こしはAPIで取得できますが、「通話後の分析」の文字起こしはAPIで取得できません。

要約のプロンプトは以下です。

通話記録の要約をお願いします。

通話内容:
$transcriptText

要約内容:
1. 顧客の問い合わせ内容
2. エージェントの対応内容
3. 結論(解決・今後の対応など)

制約:
- 通話内容に忠実であること
- 推測はしないこと
- 具体的な情報(日付、注文番号など)は明記すること

出力形式:
- 3~4 文
- 自然な文章
- 前置きなし、要約のみ

IAMポリシー

マネジメントコンソールでステートマシンを作成する際、実行ロールに以下のポリシーを手動で追加してください。

cm-hirai-screenshot 2025-12-23 15.16.43

Amazon ConnectインスタンスARNは各自変更ください。

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Action": "bedrock:InvokeModel",
			"Resource": "*"
		},
		{
			"Effect": "Allow",
			"Action": [
                "connect:ListRealtimeContactAnalysisSegments",
                "connect:UpdateContactAttributes"
            ],
			"Resource": [
				"arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c",
				"arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c/contact/*"
			]
		}
	]
}

Connectフロー

Connectフローでは、フローブロック「記録と分析の動作を設定」で「リアルタイムおよび通話後の分析」を有効化します。その他の設定は、エージェントに接続されるConnectフローであれば問題ありません。

yi6rsipfouusvkqsy4jg

なお、「リアルタイムおよび通話後の分析」を設定した場合のトランスクリプトは、リアルタイム分析の精度のみが適用されます。通話後に精度の高いトランスクリプトへの更新は行われませんので、ご注意ください。詳細は以下の記事をご参照ください。

https://dev.classmethod.jp/articles/amazon-connect-realtime-post-call-analysis-transcript-accuracy/

EventBridgeルールの作成

通話終了後にトリガーするため、以下のイベントパターンで作成します。Connectインスタンスで制限しています。Amazon ConnectインスタンスARNは各自変更ください。

{
  "source": ["aws.connect"],
  "detail-type": ["Amazon Connect Contact Event"],
  "detail": {
    "eventType": ["DISCONNECTED"],
    "channel": ["VOICE"],
    "instanceArn": ["arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c"]
  }
}

さらに、Connectフロー上でコンタクト属性を設定することで、特定のConnectフローに制限することもできます。

ターゲットは、先ほど作成したStep Functionsを指定します。

動作確認

会話内容は以下のとおりです。

Customer:ウェブサイトにログインしようとしたんですが、パスワードを受け取ったメールのリンクをクリックしても、「リンクが無効です」というエラーが出るんです。

Agent:それはご不便をおかけして申し訳ありません。どのメールプロバイダーをご利用でしょうか?

Customer:Gmail です。

Agent:ありがとうございます。リンクを開く際に、ブラウザを変えてみたり、別のデバイスで試すことはされましたか?また、リンクが届いたメールが古い可能性もあります。

Customer:はい、スマホとパソコン両方で試しましたが、同じエラーが出ます。メールは15分前に届いたものです。

Agent:承知しました。それではこちらでパスワードリセットリンクを再発行いたします。新しいリンクをお送りしますので、届き次第クリックしていただけますか?

Customer:はい、お願いします。

Agent:新しいリンクを送りました。届いたメールのリンクをクリックしてみてください。

Customer:今、ログインできました。ありがとうございます。

エージェントとの会話終了後、即、コンタクト詳細ページに要約が出力されました。

cm-hirai-screenshot 2025-11-28 10.32.01

コンタクト属性(realtimesummary)
1. 顧客の問い合わせ内容 顧客がウェブサイトにログインしようとした際、パスワードリセット用のメールに記載されたリンクをクリックしても「リンクが無効です」というエラーが表示された。スマホとパソコンの両方で試したが解決せず、メールは15分前に届いていた。 
2. エージェントの対応内容 エージェントは使用しているメールプロバイダー(gmail)を確認し、別のブラウザーやデバイスでの試行状況を確認した。その後、新しいパスワードリセットリンクを発行した。 
3. 結論 新しいパスワードリセットリンクの発行により、顧客は無事にログインできた。問題は解決した。

Step Functionsの実行履歴は以下のとおりです。

cm-hirai-screenshot 2025-12-23 16.32.45

なお、エージェントとの会話が10文字以内の場合、以下のとおりスキップして終了します。

cm-hirai-screenshot 2025-12-24 8.21.16

ちなみに、EventBridgeからStep Functionsに渡されるイベント例は以下です。

{
  "version": "0",
  "id": "fd4717b3-0721-bbbd-29d0-c3ff649f91d9",
  "detail-type": "Amazon Connect Contact Event",
  "source": "aws.connect",
  "account": "111111111111",
  "time": "2025-12-23T06:50:05Z",
  "region": "ap-northeast-1",
  "resources": [
    "arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c",
    "arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c/contact/1d8a29b3-2da4-4df0-a093-1d21bd3ff8d1"
  ],
  "detail": {
    "eventType": "DISCONNECTED",
    "contactId": "1d8a29b3-2da4-4df0-a093-1d21bd3ff8d1",
    "contactAssociationId": "1d8a29b3-2da4-4df0-a093-1d21bd3ff8d1",
    "channel": "VOICE",
    "instanceArn": "arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c",
    "initiationMethod": "INBOUND",
    "queueInfo": {
      "queueArn": "arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c/queue/ba8d05d9-27b3-406e-b089-f5707174697e",
      "enqueueTimestamp": "2025-12-23T06:49:13.698Z",
      "queueType": "STANDARD"
    },
    "agentInfo": {
      "agentArn": "arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c/agent/104b4727-a2ef-4f12-8daf-6068ce627667",
      "connectedToAgentTimestamp": "2025-12-23T06:49:20.571Z",
      "agentInitiatedHoldDuration": 0
    },
    "initiationTimestamp": "2025-12-23T06:49:09.311Z",
    "connectedToSystemTimestamp": "2025-12-23T06:49:10.057Z",
    "disconnectTimestamp": "2025-12-23T06:50:05.511Z",
    "tags": {
      "aws:connect:instanceId": "3ff2093d-af96-43fd-b038-3c07cdd7609c",
      "aws:connect:systemEndpoint": "+811111111111"
    },
    "segmentAttributes": {
      "connect:Purpose": {
        "valueMap": {
          "analytics": {
            "valueList": [
              {
                "valueString": "connect:Subtype"
              }
            ]
          },
          "contact-attributes-search": {
            "valueList": [
              {
                "valueString": "connect:Subtype"
              }
            ]
          }
        }
      },
      "connect:Subtype": {
        "valueString": "connect:Telephony"
      }
    },
    "contactLens": {
      "conversationalAnalytics": {
        "configuration": {
          "enabled": true,
          "channelConfiguration": {
            "analyticsModes": [
              "RealTime"
            ]
          },
          "languageLocale": "ja-JP",
          "redactionConfiguration": {
            "behavior": "Disable",
            "policy": "None"
          }
        }
      }
    },
    "systemEndpoint": {
      "type": "TELEPHONE_NUMBER",
      "address": "+811111111111"
    },
    "recordings": [
      {
        "storageType": "S3",
        "location": "amazon-connect-1b33d85eab5b/connect/connectインスタンス名/CallRecordings/ivr/2025/12/23/1d8a29b3-2da4-4df0-a093-1d21bd3ff8d1_20251223T06:49_UTC.wav",
        "mediaStreamType": "AUDIO",
        "participantType": "IVR",
        "startTimestamp": "2025-12-23T06:49:10.057Z",
        "stopTimestamp": "2025-12-23T06:49:20.550Z",
        "status": "AVAILABLE"
      }
    ],
    "contactDetails": {}
  }
}

まとめ

Amazon ConnectのContact Lens分析機能を利用することで、通話終了直後に要約を表示する方法を紹介しました。

「通話後の分析」では要約表示まで5~10分かかりますが、今回の方法では通話終了と同時に要約が確認できます。

ただし、「リアルタイムの分析」は「通話後の分析」よりも文字起こし精度が低いため、本番導入前に十分な検証が必要です。

また、今回のコードを一部修正することで、CRMへの自動保存など、さまざまな用途に応用できます。

この記事をシェアする

FacebookHatena blogX

関連記事