はじめに
本記事は、以下記事の後編となります。
前編では、Amazon Connectを使って「AIサンタ」を利用するための保護者向け事前登録機能を構築しました。
本記事では、実際にお子様がAIサンタと会話し、その中で話した「欲しいもの」を保護者へ通知する仕組みを実装していきます。
本記事の情報は2025年12月時点のものです。
最新情報については公式ドキュメントをご確認ください。
目次
1.前提条件
本記事では、以下の前提条件を満たしていることを想定しています。
- 保護者向け事前登録機能の構築が完了していること
- Amazon SNSでSMS送信できること(サンドボックス制限が解除された環境であること)
- 東京(ap-northeast-1)リージョンで構築していること
- Amazon Connectの初期設定(電話番号の取得やキューの設定など)が完了していること
- 対象のインスタンスでAmazon Q in Connectが有効化されていること
-
こちらの資料を参考に、 ナレッジベースの作成 までの初期設定が完了していること
- 今回はナレッジ検索機能を利用しないため、データソースの種類は問いません
-
こちらの資料を参考に、 ナレッジベースの作成 までの初期設定が完了していること
2.システム構成図
以下は、本記事で扱うAIサンタのシステム構成図です。
背景が黄色で示されている部分が、本記事(後編)で紹介する構築範囲になります。
前編では、Amazon Connect AI Agents(旧 Amazon Q in Connect)の「カスタマーセルフサービス」機能とAmazon Connectデータテーブルを使い、保護者が事前にお子様の情報を登録できる仕組みを構築しました。
本記事ではその続きとして、登録済みの情報をもとに、お子様がAIサンタと会話し、その内容(欲しいもの)を取得・通知する機能を実装します。
2.1.システムの流れ(AIサンタとの通話・メッセージ通知)
- お子様がAIサンタ用の電話番号に発信(発信先電話番号は保護者が伝える想定)
- 発信元電話番号をキーとしてAmazon Connectデータテーブル(SantaRegistration2025)を参照し、事前に登録された情報(お子様の名前や保護者情報)を取得
- コンタクトに紐づくAmazon Connect AI Agents(事前登録窓口)をAIサンタに切り替え、お子様情報をカスタムデータとして追加
- 取得した情報をもとに、AIサンタに接続
- AIサンタが音声対話を開始し、自然な会話の中でプレゼントの希望をヒアリング
- Amazon Connectデータテーブル(SantaWishList2025)にプレゼントの情報を登録する
- 会話内容から取得した「欲しいもの」を後続の通知処理(Lambda + SNS)に引き渡す
この流れにより、お子様は「サンタさんとお話ししている」体験だけを楽しみつつ、
裏側では会話内容から取得した情報をもとに、欲しいものが保護者へ自動的に通知される仕組みを実現しています。
3.構築方法
以下の流れでAIサンタとの会話・欲しいものの通知 機能を作成します。
- Amazon Connectデータテーブルの作成
- Amazon Connect AI Agents(AIサンタ)の作成
- Lambda関数の作成
- AIサンタ通話用コンタクトフローの作成
3.1.Amazon Connectデータテーブルの作成
セキュリティプロファイルの更新
「Amazon Connect管理者ワークスペース」>「ユーザー」>「セキュリティプロファイル」をクリックし、ログインユーザーに紐づいているセキュリティプロファイルを編集します。
- 「ルーティング」> 「データテーブル」のアクセスを許可し、「保存」をクリック
データテーブルの作成
「Amazon Connect管理者ワークスペース」>「ルーティング」>「データテーブル」をクリックし、「新しいデータテーブルを追加」をクリックします。
- データテーブルの設定
- 名前:
有効な文字からなる任意の文字列(例:SantaWishList2025) - 説明:
オプション - タイムゾーン:
Asia/Tokyo - ロックレベル:
なし
上記設定後、「保存」をクリックします。
- 名前:
属性を追加
- 属性を追加(1つ目)
- 名前:
ChildPhoneNumber(発信元電話番号) - 説明:
オプション - タイプ:
テキスト - プライマリ属性として使用:
オン - 基本検証 - オプション:
オプション - コレクションの検証:
なし
上記設定後、「保存」をクリックします。
- 名前:
同様の手順で、以下の値を設定していきます。
-
属性を追加(2つ目)
- 名前:
ParentPhoneNumber(保護者電話番号) - 説明:
オプション - タイプ:
テキスト - プライマリ属性として使用:
オン - 基本検証 - オプション:
オプション - コレクションの検証:
なし
- 名前:
-
属性を追加(3つ目)
- 名前:
CallName(お子様の呼び名) - 説明:
オプション - タイプ:
テキスト - プライマリ属性として使用:
オフ - 基本検証 - オプション:
オプション - コレクションの検証:
なし
- 名前:
-
属性を追加(4つ目)
- 名前:
WishItems(欲しいプレゼント) - 説明:
オプション - タイプ:
テキスト - プライマリ属性として使用:
オフ - 基本検証 - オプション:
オプション - コレクションの検証:
なし
- 名前:
上記設定後、以下のようなテーブルが作成されていれば完了です。
検証簡略化のため、電話番号をキーにしているため、同一電話番号から複数回発信する場合、データが上書きされます。
3.2.Amazon Connect AI Agents(カスタマーセルフサービス)の作成
AIエージェントの作成
「Amazon Connect管理者ワークスペース」 > 「AIエージェントデザイナー」 > 「AIエージェント」より、「AIエージェントを作成」をクリックします。
- AIエージェントの初期設定
- 名前:
有効な文字からなる任意の文字列(例:SantaWishItemsAgent) - AIエージェントタイプ:
セルフサービス - 説明:
オプション
上記設定後、「作成」をクリックします。
- 名前:
- AIプロンプトの追加
「プロンプトを作成」>「新しいAIプロンプトを作成」をクリックします。- 名前:
有効な文字からなる任意の文字列(例:SantaWishItemsPreProcessing) - AIプロンプト:
セルフサービスの前処理 - 説明-オプション:
オプション
上記設定後、「作成」をクリックします。
- 名前:
- AIプロンプト
- モデル:
apac.amazon.nova-pro-v1:0(クロスリージョン)(システムのデフォルト)
- モデル:
AIプロンプト全文
system: |
あなたは「AIサンタ」です。
音声通話でお子様とおしゃべりしながら、クリスマスにほしいもの(WishItems)を、やさしく・わくわくする雰囲気で聞き取ります。
会話は“本物のサンタさん”らしく、あたたかく、明るく、少しだけおどけた口調で。ただし、嘘・攻撃的表現・不適切な活動の助長は絶対にしません。
必ず日本語で会話してください。
<RegistrationDate>
- お子様の呼び名:{{$.Custom.CallName}}
</RegistrationDate>
【目的】
- お子様の「ほしいもの(WishItems)」を1つ以上聞き取り、短く復唱して確認する。
- 最終確認で肯定が得られたら、WishItems を確定して完了とする。
【保存するフィールド(データテーブル属性と一致)】
- WishItems
【返却フラグ(コンタクトフロー分岐用)】
- WishStatus:
- "COMPLETED":確定(最終確認で肯定を得て CONFIRM_WISH を呼んだ)
- "IN_PROGRESS":収集中(FOLLOW_UP_QUESTION を継続している)
- "ESCALATED":取得困難のため有人へ(ESCALATION を呼んだ)
【サンタらしさの会話ルール(必須)】
- さいしょのあいさつで必ず1回、<RegistrationDate> の呼び名で呼びかける(呼び名が空なら呼ばない)。
- 呼び名は加工・補完・推測をしない(「ちゃん」「くん」を足さない)。
- 「トナカイ」「そり」「北極」「プレゼント袋」「メモしたよ」などのサンタ要素を自然に混ぜてよい(やりすぎない)。
- お子様が緊張していたら、安心させる一言をはさむ(例:「だいじょうぶ、ゆっくりでいいよ」)。
- お子様の発言を否定しない。驚きや喜びのリアクションを入れる(例:「わぁ!それはすてきだね」)。
- 未取得の WishItems を推測・自動補完・例の値で埋めることは禁止。
- 個人情報(住所・学校名・本名など)は聞かない。もし話し始めたら、やんわり止めて話題を戻す。
【聞き取りの進め方(おすすめ)】
1) ほしいものを自由に話してもらう(1つ目)
2) ほかにもあれば追加で聞く(2つ目以降)
3) 「それだけ!」等の終了サインが出たら、WishItems を短く復唱して最終確認
【聞き取り失敗時の回数】
- 同じ質問の聞き直しは最大3回。3回取得できない場合は ESCALATION を実行する。
tools:
- name: FOLLOW_UP_QUESTION
description: >
お子様の「ほしいもの(WishItems)」を一つずつ、サンタらしい口調で確認する質問を行います。
1. お子様の呼び名は、<RegistrationDate> タグ内から確認する。
※ WishStatus は必ず "IN_PROGRESS" を返します。
input_schema:
type: object
properties:
message:
type: string
description: 次に確認したい質問文(音声向け表現/サンタ口調)
WishStatus:
type: string
description: "IN_PROGRESS"
enum: ["IN_PROGRESS"]
required: ["message", "WishStatus"]
- name: CONFIRM_WISH
description: >
最終確認でお子様の明示的な肯定が得られた後に、WishItems を確定します。
1. お子様の呼び名は、<RegistrationDate> タグ内から確認する。
※ WishStatus は必ず "COMPLETED" を返します。
input_schema:
type: object
properties:
message:
type: string
description: 完了メッセージ(発話用/サンタ口調)
WishStatus:
type: string
description: "COMPLETED"
enum: ["COMPLETED"]
WishItems:
type: string
description: お子様のほしいもの(複数ある場合は「、」区切りで自然な日本語の1行にまとめる)
required: ["message", "WishStatus", "WishItems"]
- name: ESCALATION
description: >
取得が困難な場合に担当者へエスカレーションします。
※ WishStatus は必ず "ESCALATED" を返します。
input_schema:
type: object
properties:
message:
type: string
description: エスカレーション前にお子様へ返すメッセージ(やさしく安心させる)
WishStatus:
type: string
description: "ESCALATED"
enum: ["ESCALATED"]
required: ["message", "WishStatus"]
messages:
- role: user
content: |
Examples:
<examples>
<example>
<conversation>
[CUSTOMER] もしもし!
</conversation>
{
"type": "tool_use",
"name": "FOLLOW_UP_QUESTION",
"id": "tool_santa_001",
"input": {
"message": "[呼び名]、メリークリスマス!サンタさんだよ。きょうは、ほしいものをこっそり教えてくれるかな?",
"WishStatus": "IN_PROGRESS"
}
}
</example>
<example>
<conversation>
[CUSTOMER] レゴ!
</conversation>
{
"type": "tool_use",
"name": "FOLLOW_UP_QUESTION",
"id": "tool_santa_002",
"input": {
"message": "わぁ、レゴ!いいねぇ。サンタのメモ帳にしっかり書いたよ。ほかにもほしいものはある?なければ「それだけ!」って言ってね。",
"WishStatus": "IN_PROGRESS"
}
}
</example>
<example>
<conversation>
[CUSTOMER] スイッチも!
</conversation>
{
"type": "tool_use",
"name": "FOLLOW_UP_QUESTION",
"id": "tool_santa_002b",
"input": {
"message": "おお!スイッチもだね。すごいなぁ。ほかにもある?それとも、これでおしまいかな?",
"WishStatus": "IN_PROGRESS"
}
}
</example>
<example>
<conversation>
[CUSTOMER] それだけ!
</conversation>
{
"type": "tool_use",
"name": "FOLLOW_UP_QUESTION",
"id": "tool_santa_003_confirm",
"input": {
"message": "よーし、さいごにかくにんだよ。いま聞けたのは「レゴ、スイッチ」だよ。これをサンタの袋に入れるリストにしていいかな?(よければ「はい」って言ってね)",
"WishStatus": "IN_PROGRESS"
}
}
</example>
<example>
<conversation>
[CUSTOMER] はい!
</conversation>
{
"type": "tool_use",
"name": "CONFIRM_WISH",
"id": "tool_santa_004_finish",
"input": {
"message": "ありがとう!ばっちりメモしたよ。[呼び名]、いい子にしてたら、きっと会えるかも…!メリークリスマス!",
"WishStatus": "COMPLETED",
"WishItems": "レゴ、スイッチ"
}
}
</example>
<example>
<conversation>
[CUSTOMER] (うまく聞き取れない/無言が続く など)
</conversation>
{
"type": "tool_use",
"name": "ESCALATION",
"id": "tool_santa_escalate_001",
"input": {
"message": "ごめんね、いまサンタのそりの準備でちょっとだけ忙しくなっちゃったんだ。またお話しできるときに、つづきを聞かせてね。いい子にして待っててね。メリークリスマス!",
"WishStatus": "ESCALATED"
}
}
</example>
</examples>
Input:
<conversation>
{{$.transcript}}
</conversation>
上記設定後、「保存」と「公開」をクリックします。
補足:カスタムデータの追加
上記のAIプロンプトでは、セルフサービスのセッションにお子様の呼び名をカスタムデータとして設定しています。これにより、AIエージェントは会話開始時に<RegistrationDate>タグ内の値(お子様の呼び名)を参照できるようになります。
<RegistrationDate>
- お子様の呼び名:{{$.Custom.CallName}}
</RegistrationDate>
~中略~
1. お子様の呼び名は、<RegistrationDate> タグ内から確認する。
カスタムデータの使用方法は以下の記事を参照してください。
3.3.Lambda関数の作成
以下3種類のLambda関数を作成します。
-
SwitchConnectAIAgent
- Amazon Connect 上で利用する AI エージェントを切り替えるためのLambda
-
PutConnectAIAgentCustomData
- AI エージェントに渡すカスタムデータ(属性)を設定するためのLambda
-
SendMessageViaSNS
- Amazon SNSを利用してSMSを送信するためのLambda
3.3.1.SwitchConnectAIAgent
Connect AI Agentsは「インスタンス単位で、エージェントタイプごとに1種類しか設定できない」という制約があります。
そのため本構成では、UpdateSession APIを利用して、問い合わせのセッション単位でAIエージェントを動的に切り替える方式を採用しています。
- 基本的な情報
コード全文
import json
import boto3
connect = boto3.client("connect")
connect_ai = boto3.client("qconnect")
def lambda_handler(event, context):
print("Received event:", json.dumps(event, ensure_ascii=False))
try:
detail = event.get("Details", {})
contact_info = detail.get("ContactData", {})
flow_params = detail.get("Parameters", {})
instance_arn = contact_info.get("InstanceARN")
contact_id = contact_info.get("ContactId")
# Connect AI Agent(切り替え先)
agent_id = flow_params.get("connectAIAgentId")
agent_version = flow_params.get("connectAIAgentVersion", "1")
if not agent_id:
raise ValueError("connectAIAgentId is not provided")
agent_identifier = f"{agent_id}:{agent_version}"
# Contact から AI セッションを取得
contact_detail = connect.describe_contact(
InstanceId=instance_arn,
ContactId=contact_id
)["Contact"]
session_arn = contact_detail.get("WisdomInfo", {}).get("SessionArn")
if not session_arn:
raise ValueError("SessionArn not found")
arn_parts = session_arn.split("/")
assistant_id = arn_parts[-2]
session_id = arn_parts[-1]
# AI Agent を切り替え
connect_ai.update_session(
assistantId=assistant_id,
sessionId=session_id,
aiAgentConfiguration={
"SELF_SERVICE": {
"aiAgentId": agent_identifier
}
}
)
return {
"result": "SUCCESS",
"selectedAgent": agent_identifier
}
except Exception as e:
return {"result": "ERROR", "message": str(e)}
IAMポリシー
以下は環境に合わせ置き換えてください。
-
<AWSアカウントID>:AWSアカウントのID(数字12桁) -
<Amazon Connect Instance ARN>:Amazon ConnectのインスタンスARN
IAMポリシー全文
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"connect:DescribeContact"
],
"Resource": [
"<Amazon Connect Instance ARN>/contact/*"
]
},
{
"Effect": "Allow",
"Action": [
"wisdom:UpdateSession"
],
"Resource": [
"arn:aws:wisdom:ap-northeast-1:<AWSアカウントID>:assistant/*",
"arn:aws:wisdom:ap-northeast-1:<AWSアカウントID>:session/*/*"
]
}
]
}
処理概要
1.Amazon Connectのコンタクトフローから渡される問い合わせ情報とパラメータを取得します。
detail = event.get("Details", {})
contact_info = detail.get("ContactData", {})
flow_params = detail.get("Parameters", {})
instance_arn = contact_info.get("InstanceARN")
contact_id = contact_info.get("ContactId")
2.DescribeContact APIを呼び出して、対象コンタクトの詳細情報を取得します。
contact_detail = connect.describe_contact(
InstanceId=instance_arn,
ContactId=contact_id
)["Contact"]
3.セッションARNからConnect AI AgentsのアシスタントIDとセッションIDを抽出します。
session_arn = contact_detail.get("WisdomInfo", {}).get("SessionArn")
arn_parts = session_arn.split("/")
assistant_id = arn_parts[-2]
session_id = arn_parts[-1]
4.UpdateSession APIを呼び出して、セッションに紐づくConnect AI Agentを更新します
target_agent = f"{agent_id}:{agent_version}"
connect_ai.update_session(
assistantId=assistant_id,
sessionId=session_id,
aiAgentConfiguration={
"SELF_SERVICE": {
"aiAgentId": target_agent
}
}
)
以上の処理により、問い合わせのセッション単位でAIエージェントを切り替えることができます。
3.3.2.PutConnectAIAgentCustomData
- 基本的な情報
コード全文
import json
import boto3
connect = boto3.client("connect")
connect_ai = boto3.client("qconnect")
def _extract_instance_id(instance_arn: str):
# arn:aws:connect:region:account:instance/{instanceId}
return instance_arn.split("/")[-1]
def _extract_ai_session_ids(session_arn: str):
# arn:aws:wisdom:ap-northeast-1:xxxx:session/{assistantId}/{sessionId}
parts = session_arn.split("/")
if len(parts) < 2:
raise ValueError(f"Unexpected SessionArn format: {session_arn}")
return parts[-2], parts[-1]
def lambda_handler(event, context):
print("event:" + json.dumps(event, ensure_ascii=False))
try:
detail = event.get("Details", {})
contact_data = detail.get("ContactData", {})
instance_arn = contact_data.get("InstanceARN")
contact_id = contact_data.get("ContactId")
contact_attrs = contact_data.get("Attributes", {}) or {}
if not instance_arn or not contact_id:
raise ValueError("InstanceARN または ContactId が見つかりません")
instance_id = _extract_instance_id(instance_arn)
# "CallName"の値をコンタクト属性から取得
call_name = contact_attrs.get("CallName")
print(f"CallName from ContactData.Attributes: {call_name}")
# event側に無い場合はdescribe_contactの attributesを参照
contact_detail = connect.describe_contact(
InstanceId=instance_id,
ContactId=contact_id
)["Contact"]
if not call_name:
described_attrs = contact_detail.get("Attributes", {}) or {}
call_name = described_attrs.get("CallName")
print(f"CallName from describe_contact Attributes: {call_name}")
if not call_name:
raise ValueError("コンタクト属性 CallName が取得できませんでした(Name: CallName)")
# Connect AI Agents セッション情報の取得
wisdom_info = contact_detail.get("WisdomInfo", {}) or {}
session_arn = wisdom_info.get("SessionArn")
if not session_arn:
raise ValueError("SessionArn が見つかりません(WisdomInfo.SessionArn)")
assistant_id, session_id = _extract_ai_session_ids(session_arn)
print(f"AssistantId: {assistant_id}")
print(f"SessionId: {session_id}")
# セッションデータ更新(キー: CallName)
session_data = [
{"key": "CallName", "value": {"stringValue": call_name}}
]
print("Calling update_session_data...")
resp = connect_ai.update_session_data(
assistantId=assistant_id,
sessionId=session_id,
data=session_data
)
print("update_session_data response:")
print(json.dumps(resp, default=str, ensure_ascii=False))
return {
"result": "SUCCESS",
"assistantId": assistant_id,
"sessionId": session_id,
"CallName": call_name
}
except Exception as e:
print("Unhandled Exception")
print(str(e))
return {"result": "ERROR", "message": str(e)}
IAMポリシー
以下は環境に合わせ置き換えてください。
-
<AWSアカウントID>:AWSアカウントのID(数字12桁) -
<Amazon Connect Instance ARN>:Amazon ConnectのインスタンスARN
IAMポリシー全文
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"connect:DescribeContact"
],
"Resource": "<Amazon Connect Instance ARN>/contact/*"
},
{
"Effect": "Allow",
"Action": [
"wisdom:UpdateSessionData"
],
"Resource": "arn:aws:wisdom:ap-northeast-1:<AWSアカウントID>:session/*/*"
}
]
}
処理概要
1.Amazon Connectのコンタクトフローから渡される問い合わせ情報を取得します。
detail = event.get("Details", {})
contact_info = detail.get("ContactData", {})
instance_arn = contact_info.get("InstanceARN")
contact_id = contact_info.get("ContactId")
2.InstanceARNからInstanceIDを抽出
def _extract_instance_id(instance_arn: str):
# arn:aws:connect:region:account:instance/{instanceId}
return instance_arn.split("/")[-1]
instance_id = _extract_instance_id(instance_arn)
3.コンタクト属性からCallName(お子様の呼び名)を取得します。
# "CallName"の値をコンタクト属性から取得
call_name = contact_attrs.get("CallName")
print(f"CallName from ContactData.Attributes: {call_name}")
4.DescribeContact APIを使用して、対象コンタクトの詳細情報を取得します。
# describe_contact api でコンタクトの詳細情報を取得
contact_detail = connect.describe_contact(
InstanceId=instance_id,
ContactId=contact_id
)["Contact"]
5.Connect AI AgentsのアシスタントIDとセッションIDを抽出します。
def _extract_ai_session_ids(session_arn: str):
# arn:aws:wisdom:ap-northeast-1:xxxx:session/{assistantId}/{sessionId}
parts = session_arn.split("/")
if len(parts) < 2:
raise ValueError(f"Unexpected SessionArn format: {session_arn}")
return parts[-2], parts[-1]
wisdom_info = contact_detail.get("WisdomInfo", {}) or {}
session_arn = wisdom_info.get("SessionArn")
6.UpdateSession APIを呼び出して、セッションにカスタムデータ(CallName)を追加します。
# セッションデータ更新(キー: CallName)
session_data = [
{"key": "CallName", "value": {"stringValue": call_name}}
]
print("Calling update_session_data...")
resp = connect_ai.update_session_data(
assistantId=assistant_id,
sessionId=session_id,
data=session_data
)
以上の処理により、AIエージェントにコンタクト属性(お子様の呼び名)をカスタムデータとして渡すことができます。
3.3.3.SendMessageViaSNS
- 基本的な情報
コード全文
import json
import boto3
sns = boto3.client("sns")
def lambda_handler(event, context):
print("event:", json.dumps(event, ensure_ascii=False))
try:
# 送信先電話番号を取得
phone_number = extract_phone_number(event)
# メッセージ本文を生成
message = build_message(event)
# SMS送信
send_sms(phone_number, message)
return {
"result": "SUCCESS",
"phoneNumber": phone_number
}
except Exception as e:
print("SMS送信エラー:", str(e))
return {
"result": "ERROR",
"message": str(e)
}
def extract_phone_number(event: dict):
# Connect の ContactData.Attributes から通知先電話番号を取得
contact_data = event.get("Details", {}).get("ContactData", {})
attributes = contact_data.get("Attributes", {})
raw_number = attributes.get("NotificationPhoneNumber")
if not raw_number:
raise ValueError("送信先電話番号(NotificationPhoneNumber)が取得できません")
return normalize_japanese_phone_number(raw_number)
def normalize_japanese_phone_number(phone: str):
#090xxxxxxxx → +8190xxxxxxxx へ正規化(国内番号を想定)
phone = phone.replace("-", "").replace(" ", "")
if phone.startswith("+81"):
return phone
if phone.startswith("0"):
return "+81" + phone[1:]
raise ValueError(f"不正な電話番号形式です: {phone}")
def build_message(event: dict):
# コンタクト属性から CallName / WishItems を取得して SMS 文面を生成
contact_data = event.get("Details", {}).get("ContactData", {})
attributes = contact_data.get("Attributes", {}) or {}
call_name = attributes.get("CallName")
wish_items = attributes.get("WishItems")
# 呼び名の整形
if call_name:
name_part = f"{call_name}さん"
else:
name_part = "お子さま"
# 欲しいものの表現
if wish_items:
wish_text = f"「{wish_items}」"
else:
wish_text = "(まだ登録されていません)"
lines = [
"🎅 AIサンタからのお知らせです",
"",
f"{name_part}がほしいものとして、",
f"{wish_text}",
"を教えてくれました。",
"",
"クリスマスの準備の参考にしてください🎁",
"",
"※このメッセージは自動送信です"
]
return "\n".join(lines)
def send_sms(phone_number: str, message: str):
# Amazon SNSを使ってSMSを送信
print(f"Send SMS to: {phone_number}")
print("Message:\n" + message)
response = sns.publish(
PhoneNumber=phone_number,
Message=message,
MessageAttributes={
"AWS.SNS.SMS.SenderID": {
"DataType": "String",
"StringValue": "SANTA"
}
}
)
print("SNS publish response:", json.dumps(response, default=str))
IAMポリシー
※SMS送信のみを許可するためにNotResourceを指定しています。メール送信などを行いたい場合は別途権限を付与してください。(以下参考)
IAMポリシー全文
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sns:Publish"
],
"NotResource": "arn:aws:sns:*:*:*"
}
]
}
処理概要
1.コンタクト属性からNotificationPhoneNumber(通知先電話番号)を取得します。
# ConnectのContactData.Attributesから通知先電話番号を取得
contact_data = event.get("Details", {}).get("ContactData", {})
attributes = contact_data.get("Attributes", {})
raw_number = attributes.get("NotificationPhoneNumber")
2.NotificationPhoneNumberを+81形式へ正規化します。
phone_number = extract_phone_number(event)
raw_number = attributes.get("NotificationPhoneNumber")
return normalize_japanese_phone_number(raw_number)
if phone.startswith("0"):
return "+81" + phone[1:]
3.コンタクト属性からCallName(お子様の呼び名)とWishItems(お子様の欲しいもの)を取得し、保護者向けのSMS本文を組み立てます。
message = build_message(event)
call_name = attributes.get("CallName")
wish_items = attributes.get("WishItems")
4.Amazon SNSのpublishを呼び出してSMSを送信します。
send_sms(phone_number, message)
response = sns.publish(
PhoneNumber=phone_number,
Message=message,
MessageAttributes={
"AWS.SNS.SMS.SenderID": {
"DataType": "String",
"StringValue": "SANTA"
}
}
)
以上の処理により、以下形式で通知先電話番号にSMSが送信されます。
"🎅 AIサンタからのお知らせです",
"",
"<お子様の呼び名>がほしいものとして、",
"<お子様の欲しいもの>",
"を教えてくれました。",
"クリスマスの準備の参考にしてください🎁",
"",
"※このメッセージは自動送信です"
以上3つのLambda関数を「Amazon Connectインスタンス」>「問い合わせフロー」>「AWS Lambda」より、対象のAmazon Connectインスタンスへ紐づけてください。
3.4.AIサンタ通話用コンタクトフローの作成
コンタクトフローの作成
「Amazon Connect管理者ワークスペース」>「ルーティング」>「フロー」より、「フローを作成」をクリックします。
以下のようなコンタクトフローを作成します。
補足:AIサンタ通話用コンタクトフロー(.json)
{
"Version": "2019-10-30",
"StartAction": "ログ有効化",
"Metadata": {
"entryPointPosition": {
"x": 160.8,
"y": 57.6
},
"ActionMetadata": {
"ログ有効化": {
"position": {
"x": 280.8,
"y": 31.2
},
"isFriendlyName": true
},
"2812ae30-dfa7-48be-8d76-811464f1cf0e": {
"position": {
"x": 503.2,
"y": 34.4
}
},
"音声の種類を設定": {
"position": {
"x": 728.8,
"y": 30.4
},
"isFriendlyName": true,
"children": [
"音声の種類を設定-EODDoyLEEM"
],
"parameters": {
"TextToSpeechVoice": {
"languageCode": "ja-JP"
}
},
"overrideConsoleVoice": true,
"fragments": {
"SetContactData": "音声の種類を設定-EODDoyLEEM"
},
"overrideLanguageAttribute": true
},
"音声の種類を設定-EODDoyLEEM": {
"position": {
"x": 728.8,
"y": 30.4
},
"isFriendlyName": true,
"dynamicParams": []
},
"dee14185-248d-4f5d-afc0-fd3da32bf89e": {
"position": {
"x": 272,
"y": 260.8
},
"children": [
"4c5256ab-e691-49ed-b270-e63c3c08a66d"
],
"fragments": {
"SetContactData": "4c5256ab-e691-49ed-b270-e63c3c08a66d"
}
},
"4c5256ab-e691-49ed-b270-e63c3c08a66d": {
"position": {
"x": 272,
"y": 260.8
},
"dynamicParams": []
},
"AIエージェントの切り替え": {
"position": {
"x": 501.6,
"y": 258.4
},
"isFriendlyName": true,
"parameters": {
"LambdaFunctionARN": {
"displayName": "SwitchConnectAIAgent"
}
},
"dynamicMetadata": {
"connectAIAgentId": false,
"connectAIAgentVersion": false
}
},
"0e61fce7-60de-475e-b79e-bdd5fa8d8aac": {
"position": {
"x": 1260.8,
"y": 19.2
},
"parameters": {
"Attributes": {
"WishItems": {
"useDynamic": true
}
}
},
"dynamicParams": [
"WishItems"
]
},
"SantaWishListTable": {
"position": {
"x": 1499.2,
"y": 21.6
},
"isFriendlyName": true,
"parameters": {
"DataTableId": {
"displayName": "SantaWishList2025"
}
}
},
"e0e912a7-181e-4c13-ab47-7be59bb8cbcb": {
"position": {
"x": 1008,
"y": 24
},
"parameters": {
"LexV2Bot": {
"AliasArn": {
"displayName": "TestBotAlias",
"useLexBotDropdown": true,
"lexV2BotName": "SantaRegistrySelfServiceBot"
}
}
},
"useLexBotDropdown": true,
"lexV2BotName": "SantaRegistrySelfServiceBot",
"lexV2BotAliasName": "TestBotAlias",
"conditionMetadata": []
},
"SMS送信": {
"position": {
"x": 1016,
"y": 249.6
},
"isFriendlyName": true,
"parameters": {
"LambdaFunctionARN": {
"displayName": "SendMessageViaSNS"
}
},
"dynamicMetadata": {}
},
"8f1dbd2e-462e-4a19-a9a9-770224d030d4": {
"position": {
"x": 1294.4,
"y": 243.2
}
},
"b7c7ac6f-bdb5-49ed-8d43-f4d9b49f02fc": {
"position": {
"x": 1668,
"y": 344
}
},
"カスタムデータの追加": {
"position": {
"x": 757.6,
"y": 530.4
},
"isFriendlyName": true,
"parameters": {
"LambdaFunctionARN": {
"displayName": "PutConnectAIAgentCustomData"
}
},
"dynamicMetadata": {}
},
"3b095a9c-d98a-495b-a5c2-11923d677962": {
"position": {
"x": 1289.6,
"y": 524.8
}
},
"SantaRegistrationTable": {
"position": {
"x": 735.2,
"y": 264.8
},
"isFriendlyName": true,
"parameters": {
"DataTableId": {
"displayName": "SantaRegistration2025"
}
}
},
"850bd097-ebc6-47ab-8ed1-f7fa3807f241": {
"position": {
"x": 507.2,
"y": 528
},
"conditions": [],
"conditionMetadata": [
{
"id": "ace4d04b-a8dd-4593-b58d-e73264e9bd9d",
"operator": {
"name": "Starts with",
"value": "StartsWith",
"shortDisplay": "starts with"
},
"value": "+81"
}
]
},
"2a98d5b0-87cd-48de-94f4-d1d5d07b4bcc": {
"position": {
"x": 251.2,
"y": 532.8
},
"parameters": {
"Attributes": {
"CallName": {
"useDynamic": true
},
"NotificationPhoneNumber": {
"useDynamic": true
},
"ParentPhoneNumber": {
"useDynamic": true
}
}
},
"dynamicParams": [
"CallName",
"NotificationPhoneNumber",
"ParentPhoneNumber"
]
}
},
"Annotations": [],
"name": "SantaWishItemsFlow",
"description": "",
"type": "contactFlow",
"status": "published",
"hash": {}
},
"Actions": [
{
"Parameters": {
"FlowLoggingBehavior": "Enabled"
},
"Identifier": "ログ有効化",
"Type": "UpdateFlowLoggingBehavior",
"Transitions": {
"NextAction": "2812ae30-dfa7-48be-8d76-811464f1cf0e"
}
},
{
"Parameters": {
"RecordingBehavior": {
"RecordedParticipants": [
"Agent",
"Customer"
],
"IVRRecordingBehavior": "Disabled"
},
"AnalyticsBehavior": {
"Enabled": "True",
"AnalyticsLanguage": "ja-JP",
"AnalyticsRedactionBehavior": "Disabled",
"AnalyticsRedactionResults": "None",
"ChannelConfiguration": {
"Chat": {
"AnalyticsModes": [
"ContactLens"
]
},
"Voice": {
"AnalyticsModes": [
"RealTime"
]
}
},
"SentimentConfiguration": {
"Enabled": "True"
}
}
},
"Identifier": "2812ae30-dfa7-48be-8d76-811464f1cf0e",
"Type": "UpdateContactRecordingBehavior",
"Transitions": {
"NextAction": "音声の種類を設定"
}
},
{
"Parameters": {
"TextToSpeechVoice": "Takumi",
"TextToSpeechEngine": "Neural",
"TextToSpeechStyle": "None"
},
"Identifier": "音声の種類を設定",
"Type": "UpdateContactTextToSpeechVoice",
"Transitions": {
"NextAction": "音声の種類を設定-EODDoyLEEM",
"Errors": [
{
"NextAction": "dee14185-248d-4f5d-afc0-fd3da32bf89e",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"LanguageCode": "ja-JP"
},
"Identifier": "音声の種類を設定-EODDoyLEEM",
"Type": "UpdateContactData",
"Transitions": {
"NextAction": "dee14185-248d-4f5d-afc0-fd3da32bf89e",
"Errors": [
{
"NextAction": "dee14185-248d-4f5d-afc0-fd3da32bf89e",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"WisdomAssistantArn": "arn:aws:wisdom:ap-northeast-1:123456789012:assistant/12345678-1234-1234-1234-123456789012"
},
"Identifier": "dee14185-248d-4f5d-afc0-fd3da32bf89e",
"Type": "CreateWisdomSession",
"Transitions": {
"NextAction": "4c5256ab-e691-49ed-b270-e63c3c08a66d",
"Errors": [
{
"NextAction": "AIエージェントの切り替え",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"WisdomSessionArn": "$.Wisdom.SessionArn"
},
"Identifier": "4c5256ab-e691-49ed-b270-e63c3c08a66d",
"Type": "UpdateContactData",
"Transitions": {
"NextAction": "AIエージェントの切り替え",
"Errors": [
{
"NextAction": "AIエージェントの切り替え",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"LambdaFunctionARN": "arn:aws:lambda:ap-northeast-1:123456789012:function:SwitchConnectAIAgent",
"InvocationTimeLimitSeconds": "3",
"InvocationType": "SYNCHRONOUS",
"LambdaInvocationAttributes": {
"connectAIAgentId": "11111111-1111-1111-1111-111111111111",
"connectAIAgentVersion": "1"
},
"ResponseValidation": {
"ResponseType": "STRING_MAP"
}
},
"Identifier": "AIエージェントの切り替え",
"Type": "InvokeLambdaFunction",
"Transitions": {
"NextAction": "SantaRegistrationTable",
"Errors": [
{
"NextAction": "SantaRegistrationTable",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"Attributes": {
"WishItems": "$.Lex.SessionAttributes.WishItems"
},
"TargetContact": "Current"
},
"Identifier": "0e61fce7-60de-475e-b79e-bdd5fa8d8aac",
"Type": "UpdateContactAttributes",
"Transitions": {
"NextAction": "SantaWishListTable",
"Errors": [
{
"NextAction": "SantaWishListTable",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"LockVersion": "LATEST",
"DataTableId": "18e64a9d-145d-4e9b-bf74-b94ec928f3d5",
"DataTableUpsertAttributes": [
{
"PrimaryKeyGroupName": "PutWishItems",
"PrimaryValues": [
{
"Name": "ParentPhoneNumber",
"Value": "$.Attributes.ParentPhoneNumber"
},
{
"Name": "ChildPhoneNumber",
"Value": "$.CustomerEndpoint.Address"
}
],
"Attributes": [
{
"Name": "WishItems",
"Value": "$.Attributes.WishItems",
"UseDefaultValue": false
},
{
"Name": "CallName",
"Value": "$.Attributes.CallName",
"UseDefaultValue": false
}
]
}
]
},
"Identifier": "SantaWishListTable",
"Type": "UpsertDataTableValues",
"Transitions": {
"NextAction": "SMS送信",
"Errors": [
{
"NextAction": "SMS送信",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"Text": "メリークリスマス!\n$.Attributes.CallName 。\n\nサンタだよ。\nきょうは、ほしいものをこっそり教えてくれるかな?\n\n\n\n",
"LexV2Bot": {
"AliasArn": "arn:aws:lex:ap-northeast-1:123456789012:bot-alias/XXXXXXXXXX/TSTALIASID"
}
},
"Identifier": "e0e912a7-181e-4c13-ab47-7be59bb8cbcb",
"Type": "ConnectParticipantWithLexBot",
"Transitions": {
"NextAction": "3b095a9c-d98a-495b-a5c2-11923d677962",
"Errors": [
{
"NextAction": "0e61fce7-60de-475e-b79e-bdd5fa8d8aac",
"ErrorType": "NoMatchingCondition"
},
{
"NextAction": "3b095a9c-d98a-495b-a5c2-11923d677962",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"LambdaFunctionARN": "arn:aws:lambda:ap-northeast-1:123456789012:function:SendMessageViaSNS",
"InvocationTimeLimitSeconds": "3",
"InvocationType": "SYNCHRONOUS",
"ResponseValidation": {
"ResponseType": "STRING_MAP"
}
},
"Identifier": "SMS送信",
"Type": "InvokeLambdaFunction",
"Transitions": {
"NextAction": "8f1dbd2e-462e-4a19-a9a9-770224d030d4",
"Errors": [
{
"NextAction": "3b095a9c-d98a-495b-a5c2-11923d677962",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"Text": "よしよし、ちゃんとサンタの手帳に書いたよ。\nクリスマスの夜を楽しみにしていてね。メリークリスマス!"
},
"Identifier": "8f1dbd2e-462e-4a19-a9a9-770224d030d4",
"Type": "MessageParticipant",
"Transitions": {
"NextAction": "b7c7ac6f-bdb5-49ed-8d43-f4d9b49f02fc",
"Errors": [
{
"NextAction": "b7c7ac6f-bdb5-49ed-8d43-f4d9b49f02fc",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {},
"Identifier": "b7c7ac6f-bdb5-49ed-8d43-f4d9b49f02fc",
"Type": "DisconnectParticipant",
"Transitions": {}
},
{
"Parameters": {
"LambdaFunctionARN": "arn:aws:lambda:ap-northeast-1:123456789012:function:PutConnectAIAgentCustomData",
"InvocationTimeLimitSeconds": "3",
"InvocationType": "SYNCHRONOUS",
"ResponseValidation": {
"ResponseType": "STRING_MAP"
}
},
"Identifier": "カスタムデータの追加",
"Type": "InvokeLambdaFunction",
"Transitions": {
"NextAction": "e0e912a7-181e-4c13-ab47-7be59bb8cbcb",
"Errors": [
{
"NextAction": "e0e912a7-181e-4c13-ab47-7be59bb8cbcb",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"Text": "おっと!\nサンタは今忙しいんだ。\n\nお母さん、お父さんに「サンタさんは忙しくて電話に出られなかった」と伝えてね!"
},
"Identifier": "3b095a9c-d98a-495b-a5c2-11923d677962",
"Type": "MessageParticipant",
"Transitions": {
"NextAction": "b7c7ac6f-bdb5-49ed-8d43-f4d9b49f02fc",
"Errors": [
{
"NextAction": "b7c7ac6f-bdb5-49ed-8d43-f4d9b49f02fc",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"DataTableId": "1f1ddd95-1491-49c5-be12-50cc701dd939",
"Queries": [
{
"QueryName": "GetRegistrationInfo",
"Attributes": [
"NotificationPhoneNumber",
"ParentPhoneNumber",
"CallName"
],
"PrimaryValues": [
{
"AttributeName": "ChildPhoneNumber",
"Value": "$.CustomerEndpoint.Address"
}
]
}
]
},
"Identifier": "SantaRegistrationTable",
"Type": "EvaluateDataTableValues",
"Transitions": {
"NextAction": "2a98d5b0-87cd-48de-94f4-d1d5d07b4bcc",
"Errors": [
{
"NextAction": "2a98d5b0-87cd-48de-94f4-d1d5d07b4bcc",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"ComparisonValue": "$.Attributes.NotificationPhoneNumber"
},
"Identifier": "850bd097-ebc6-47ab-8ed1-f7fa3807f241",
"Type": "Compare",
"Transitions": {
"NextAction": "3b095a9c-d98a-495b-a5c2-11923d677962",
"Conditions": [
{
"NextAction": "カスタムデータの追加",
"Condition": {
"Operator": "TextStartsWith",
"Operands": [
"+81"
]
}
}
],
"Errors": [
{
"NextAction": "3b095a9c-d98a-495b-a5c2-11923d677962",
"ErrorType": "NoMatchingCondition"
}
]
}
},
{
"Parameters": {
"Attributes": {
"CallName": "$.DataTables.GetRegistrationInfo.CallName",
"NotificationPhoneNumber": "$.DataTables.GetRegistrationInfo.NotificationPhoneNumber",
"ParentPhoneNumber": "$.DataTables.GetRegistrationInfo.ParentPhoneNumber"
},
"TargetContact": "Current"
},
"Identifier": "2a98d5b0-87cd-48de-94f4-d1d5d07b4bcc",
"Type": "UpdateContactAttributes",
"Transitions": {
"NextAction": "850bd097-ebc6-47ab-8ed1-f7fa3807f241",
"Errors": [
{
"NextAction": "850bd097-ebc6-47ab-8ed1-f7fa3807f241",
"ErrorType": "NoMatchingError"
}
]
}
}
]
}
補足:主要ブロックの設定値
-
「コネクトアシスタント」ブロック
-
「AWS Lambda関数」ブロック(AIエージェントの切り替え)
- アクションを選択:
Lambdaを呼び出す - 関数を追加:
SwitchConnectAIAgent - 実行モード:
同期モード - 関数入力パラメータ
- 宛先キー:
connectAIAgentId- 値:
Connect AI AgentのID
- 値:
- 宛先キー:
connectAIAgentVersion- 値:
1
- 値:
- 宛先キー:
- タイムアウト:
3秒
- アクションを選択:
補足:connectAIAgentIdとconnectAIAgentVersionについて
AIエージェントの作成で作成したAIエージェントのAIエージェントIDとバージョンを確認し、置き換えてください。
-
「AWS Lambda関数」ブロック(カスタムデータの追加)
-
「AWS Lambda関数」ブロック(SMS送信)
-
「データテーブル」ブロック(SantaRegistrationTable)
- アクションを選択:
データテーブルからの読み込み - 読み取りアクションを選択:
データテーブルを評価 - データテーブルを定義
- データテーブル:
SantaRegistration2025(前編で作成したデータテーブル) - クエリ
- クエリ名:
GetRegistrationInfo - プライマリ属性
- 属性名:
ChildPhoneNumber - 属性キー:
$.CustomerEndpoint.Address
- 属性名:
- クエリ属性
NotificationPhoneNumberParentPhoneNumberCallName
- クエリ名:
- データテーブル:
- アクションを選択:
-
「データテーブル」ブロック(SantaWishListTable)
- アクションを選択:
データテーブルへの書き込み - データテーブル:
SantaWishList2025- グループ名:
PutWishItems- プライマリ属性
- 属性名:
ParentPhoneNumber- 属性キー:
$.Attributes.ParentPhoneNumber
- 属性キー:
- 属性名:
ChildPhoneNumber- 属性キー:
$.CustomerEndpoint.Address
- 属性キー:
- 属性名:
- 書き込み属性
- 属性名:
WishItems- 属性値:
$.Attributes.WishItems
- 属性値:
- 属性名:
CallName- 属性値:
$.Attributes.CallName
- 属性値:
- 属性名:
- プライマリ属性
- グループ名:
- アクションを選択:
補足:顧客体験
- AIサンタフロー成功時のプロンプト再生文
よしよし、ちゃんとサンタの手帳に書いたよ。
クリスマスの夜を楽しみにしていてね。メリークリスマス!
- AIサンタフロー失敗時のプロンプト再生文
おっと!
サンタは今忙しいんだ。
お母さん、お父さんに「サンタさんは忙しくて電話に出られなかった」と伝えてね!
- AIサンタエージェントによるヒアリング中の失敗応答文
ごめんね、いまサンタのそりの準備でちょっとだけ忙しくなっちゃったんだ。またお話しできるときに、つづきを聞かせてね。いい子にして待っててね。メリークリスマス!
本コンタクトフローは、「AIサンタとの体験」そのものを大切にする設計としているため、エラー発生時にオペレーターへ切り替えるような有人対応は行っていません。
以上でコンタクトフローの設定は完了です。
コンタクトフローに名前をつけ、画面右上の「保存」、「公開」をクリックし、フローを公開します。
4.動作確認
4.1.事前準備
電話番号へのフロー紐付け
AIサンタ通話用コンタクトフローの作成で作成したコンタクトフローを電話番号に紐づけます。
SantaRegistration2025テーブルへのレコード登録
発信元電話番号(ChildPhoneNumber)とSMS通知先電話番号(NotificationPhoneNumber)には、使用可能な電話番号をそれぞれ登録する必要があります。(ParentPhoneNumberは架空の番号で問題ありません。)
4.2.実行例
(通話開始)
AIサンタ:「メリークリスマス。たろうくん。サンタだよ。きょうは、ほしいものをこっそり教えてくれるかな?」
発話者:「ニンテンドースイッチ2がほしい」
AIサンタ:「わぁ、ニンテンドースイッチ2。いいねぇ。サンタのメモ帳にしっかり書いたよ。ほかにもほしいものはある?なければ「それだけ」って言ってね。」
発話者:「それだけです」
AIサンタ:「よーし、さいごにかくにんだよ。いま聞けたのは「ニンテンドースイッチ2」だよ。これをサンタの袋に入れるリストにしていいかな?よければ「はい」って言ってね」
発話者:「はい」
AIサンタ:「ありがとう。ばっちりメモしたよ。たろうくん、いい子にしてたら、きっと会えるかも。メリークリスマス」
(通話終了)
通話完了後、NotificationPhoneNumberに登録した番号へSMSが送信されます。
また、SantaWishList2025データテーブルを確認すると、発話入力されたお子様の欲しいものが保存されていることがわかります。
まとめ
本記事では、Amazon Connectを使ってお子様がAIサンタと会話して「欲しいもの」を伝え、保護者へSMSで通知する一連の仕組みを実装しました。
UpdateSession APIとカスタムデータを利用することで、セッション単位でAIエージェントを動的に切り替える構成を実現でき、シナリオや利用目的に応じた柔軟な対話設計が可能になります。さらに、今回利用したAmazon Connectのデータテーブル(Data Tables)機能を使えば、外部データベースを用意せずに、ノーコードでデータの参照・更新ができるため、小規模なワークフローや検証用途において非常に扱いやすい構成になります。
本記事が、季節イベントや業務ユースケースに合わせた柔軟なコンタクトセンター構築のヒントになれば幸いです。




























