はじめに
こんばんは、mirukyです。
Amazon Lex シリーズ第3回です。
前回(#2)では、AWSコンソールからLexボットを構築し、インテント・スロット・サンプル発話の基本を学びました。
今回は、AWS Lambdaと連携して動的な応答を返すボットに進化させます。
#2で作成したボットは「注文番号を聞いて定型文を返す」だけでしたが、実際の業務では「注文番号をもとにデータベースを検索し、リアルタイムの注文状況を返す」ことが求められます。この動的処理を実現するのがLambda連携です。
出典:AWS Lambda関数を Amazon Lex V2 ボットに統合する - AWS
目次
- Lambda連携の仕組み
- Lambda関数の作成
- ダイアログコードフックの設定
- フルフィルメントコードフックの設定
- ボットへのLambda関数の紐付け
- テストと動作確認
- エラーハンドリング
- 料金について
- おわりに
1. Lambda連携の仕組み
1-1. コードフックとは
Amazon Lexでは、会話の途中やインテント完了時にLambda関数を呼び出す仕組みをコードフックと呼びます。コードフックには2種類あります。
| コードフック | 呼び出しタイミング | 主な用途 |
|---|---|---|
| ダイアログコードフック | スロット引き出し中(会話の途中) | 入力値のバリデーション、条件に応じた会話分岐 |
| フルフィルメントコードフック | 全スロットが埋まった後(インテント完了時) | DB検索、外部API呼び出し、最終応答の生成 |
1-2. 処理フロー
ユーザー:「注文の状況を確認したい」
↓
Amazon Lex:インテント認識(CheckOrderStatus)
↓
Lex:「注文番号をお教えください」(スロットプロンプト)
↓
ユーザー:「ORD-001」
↓
Lambda(ダイアログコードフック):注文番号の形式を検証
↓ 検証OK
Lex:「注文番号 ORD-001 で確認します。よろしいですか?」
↓
ユーザー:「はい」
↓
Lambda(フルフィルメント):DB検索 → 注文情報を取得
↓
Lex → ユーザー:「ORD-001は現在配送中です。3月7日到着予定です。」
2. Lambda関数の作成
2-1. Lambda関数の作成手順
- AWSマネジメントコンソール(東京リージョン)で Lambda を開く
- 「関数の作成」 → 「一から作成」
| 設定項目 | 値 |
|---|---|
| 関数名 | lex-order-lookup |
| ランタイム | Python 3.14 |
| アーキテクチャ | x86_64 |
2-2. Lambda関数のコード
コードソースに下記のコードを入力します。
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# サンプルの注文データ(本番ではDynamoDB等から取得)
ORDERS = {
"ORD-001": {
"customerName": "田中太郎",
"product": "ワイヤレスイヤホン",
"status": "配送中",
"estimatedDelivery": "2026年3月10日"
},
"ORD-002": {
"customerName": "佐藤花子",
"product": "モバイルバッテリー",
"status": "出荷準備中",
"estimatedDelivery": "2026年3月12日"
},
"ORD-003": {
"customerName": "鈴木一郎",
"product": "USBケーブル",
"status": "お届け済み",
"estimatedDelivery": "2026年3月5日"
}
}
def lambda_handler(event, context):
logger.info(f"Received event: {json.dumps(event, ensure_ascii=False)}")
intent_name = event["sessionState"]["intent"]["name"]
invocation_source = event["invocationSource"] # DialogCodeHook or FulfillmentCodeHook
if intent_name == "CheckOrderStatus":
if invocation_source == "DialogCodeHook":
return handle_dialog(event)
elif invocation_source == "FulfillmentCodeHook":
return handle_fulfillment(event)
# その他のインテントはそのまま通過
return delegate(event)
def handle_dialog(event):
"""ダイアログコードフック:スロット値のバリデーション"""
slots = event["sessionState"]["intent"]["slots"]
order_id_slot = slots.get("OrderId")
# スロットがまだ埋まっていない場合はLexに委任
if not order_id_slot or not order_id_slot.get("value"):
return delegate(event)
order_id = order_id_slot["value"]["interpretedValue"]
# 注文番号の形式チェック(ORD-で始まるか)
if not order_id.startswith("ORD-"):
return elicit_slot(
event,
"OrderId",
"注文番号はORD-で始まる形式です(例:ORD-001)。もう一度お教えください。"
)
return delegate(event)
def handle_fulfillment(event):
"""フルフィルメントコードフック:注文情報の検索と応答"""
slots = event["sessionState"]["intent"]["slots"]
order_id = slots["OrderId"]["value"]["interpretedValue"]
if order_id in ORDERS:
order = ORDERS[order_id]
message = (
f"注文番号 {order_id} の情報です。\n"
f"商品:{order['product']}\n"
f"ステータス:{order['status']}\n"
f"お届け予定日:{order['estimatedDelivery']}"
)
else:
message = f"注文番号 {order_id} は見つかりませんでした。番号をご確認の上、もう一度お試しください。"
return close(event, "Fulfilled", message)
def delegate(event):
"""Lexにダイアログ管理を委任する"""
return {
"sessionState": {
"dialogAction": {"type": "Delegate"},
"intent": event["sessionState"]["intent"]
}
}
def elicit_slot(event, slot_name, message):
"""特定のスロットを再度引き出す"""
return {
"sessionState": {
"dialogAction": {
"type": "ElicitSlot",
"slotToElicit": slot_name
},
"intent": event["sessionState"]["intent"]
},
"messages": [
{"contentType": "PlainText", "content": message}
]
}
def close(event, fulfillment_state, message):
"""会話を終了する"""
return {
"sessionState": {
"dialogAction": {"type": "Close"},
"intent": {
"name": event["sessionState"]["intent"]["name"],
"state": fulfillment_state
}
},
"messages": [
{"contentType": "PlainText", "content": message}
]
}
「Deploy」 をクリックして関数をデプロイします。
Lambda関数のレスポンス形式
Amazon Lex V2向けのLambdaレスポンスは、sessionStateのdialogActionで次のアクションを指定します。主なアクションタイプは以下の3つです。
| dialogAction.type | 動作 |
|---|---|
| Delegate | 会話の制御をLexに委任する |
| ElicitSlot | 指定したスロットの再入力を求める |
| Close | 会話を終了し応答を返す |
3. ダイアログコードフックの設定
3-1. ダイアログコードフックとは
会話の途中で呼び出されるコードフックです。ユーザーがスロット値を入力するたびに呼び出されるため、入力値のバリデーションや条件に応じた会話分岐に使用します。
3-2. 今回の実装内容
def handle_dialog(event):
"""ダイアログコードフック:スロット値のバリデーション"""
slots = event["sessionState"]["intent"]["slots"]
order_id_slot = slots.get("OrderId")
# スロットがまだ埋まっていない場合はLexに委任
if not order_id_slot or not order_id_slot.get("value"):
return delegate(event)
order_id = order_id_slot["value"]["interpretedValue"]
# 注文番号の形式チェック(ORD-で始まるか)
if not order_id.startswith("ORD-"):
return elicit_slot(
event,
"OrderId",
"注文番号はORD-で始まる形式です(例:ORD-001)。もう一度お教えください。"
)
return delegate(event)
上記のLambdaコードでは、handle_dialog()関数で以下の処理を行っています。
| 処理 | 内容 |
|---|---|
| スロット未入力チェック |
OrderIdがまだ入力されていなければLexに委任 |
| 形式チェック | 注文番号がORD-で始まっていなければ再入力を要求 |
| 検証OK | 検証に問題なければLexに委任して会話を続行 |
4. フルフィルメントコードフックの設定
4-1. フルフィルメントコードフックとは
すべての必須スロットが埋まり、ユーザーが確認を終えた後に呼び出されるコードフックです。ビジネスロジックの実行(データベース検索、API呼び出し、レスポンス生成など)を行います。
4-2. 今回の実装内容
def handle_fulfillment(event):
"""フルフィルメントコードフック:注文情報の検索と応答"""
slots = event["sessionState"]["intent"]["slots"]
order_id = slots["OrderId"]["value"]["interpretedValue"]
if order_id in ORDERS:
order = ORDERS[order_id]
message = (
f"注文番号 {order_id} の情報です。\n"
f"商品:{order['product']}\n"
f"ステータス:{order['status']}\n"
f"お届け予定日:{order['estimatedDelivery']}"
)
else:
message = f"注文番号 {order_id} は見つかりませんでした。番号をご確認の上、もう一度お試しください。"
return close(event, "Fulfilled", message)
handle_fulfillment()関数では、以下の処理を行っています。
-
OrderIdスロットから注文番号を取得 - 注文データ(サンプル)を検索
- 見つかれば注文情報を整形して応答、見つからなければエラーメッセージを返す
本番環境ではDynamoDBを使いましょう
今回はサンプルとしてLambdaコード内に注文データをハードコードしていますが、本番環境ではDynamoDBなどのデータベースから取得するのが一般的です。
5. ボットへのLambda関数の紐付け
5-1. エイリアスにLambda関数を設定
- Amazon Lexコンソールで
CustomerSupportBotを開く - 左メニューの 「エイリアス」 を選択
- 「TestBotAlias」 をクリック
- 「言語」 セクションの 「Japanese(Japan)」 をクリック
-
Lambda関数のプルダウンで
lex-order-lookupを選択 - Lambda関数のバージョンまたはエイリアスは
$LATESTを選択 - 「保存」 をクリック
5-2. インテントでコードフックを有効化
-
CheckOrderStatusインテントの編集画面を開く - 「フルフィルメント」 セクションをアクティブ にする
- 「コードフック」 セクションでチェックマーク をつける
- 「インテントを保存」 → 「ビルド」 をクリック
ビルドを忘れずに
Lambda関数の紐付けやコードフックの設定を変更した後は、必ずビルドを実行してください。ビルドしないと変更が反映されません。
6. テストと動作確認
6-1. テスト①:正常な注文確認
ユーザー:注文の状況を確認したい
ボット :注文番号をお教えください。(例:ORD-001)
ユーザー:ORD-001
ボット :注文番号 ORD-001 の情報です。
商品:ワイヤレスイヤホン
ステータス:配送中
お届け予定日:2026年3月10日

あらかじめLambdaで渡しているデータを出力してくれました。
6-2. テスト②:不正な注文番号形式
ユーザー:注文の確認をお願いします
ボット :注文番号をお教えください。(例:ORD-001)
ユーザー:12345
ボット :注文番号はORD-で始まる形式です(例:ORD-001)。もう一度お教えください。
ユーザー:ORD-002
ボット :注文番号 ORD-002 の情報です。
商品:モバイルバッテリー
ステータス:出荷準備中
お届け予定日:2026年3月12日
6-3. テスト③:存在しない注文番号
ユーザー:注文を確認したい
ボット :注文番号をお教えください。(例:ORD-001)
ユーザー:ORD-999
ボット :注文番号 ORD-999 は見つかりませんでした。番号をご確認の上、もう一度お試しください。
7. エラーハンドリング
7-1. Lambda関数のエラー時の動作
Amazon Lex V2はLambda関数を同期呼び出し(RequestResponse)で実行するため、Lambda関数がエラー(例外、タイムアウト等)で失敗した場合、自動リトライは行われません。エラーが発生すると、Lexはインテントに設定されたフルフィルメント失敗応答をユーザーに返します。フルフィルメント失敗応答が未設定の場合は、汎用的なエラーメッセージが表示されます。
7-2. エラーハンドリングのベストプラクティス
| プラクティス | 説明 |
|---|---|
| try-exceptで例外をキャッチ | Lambda関数内で例外を適切にハンドリングし、ユーザーフレンドリーなエラーメッセージを返す |
| CloudWatch Logsでモニタリング | Lambda関数のログを確認し、エラーの原因を特定する |
| タイムアウト値の設定 | Lambdaのデフォルトタイムアウトは3秒。DB接続やAPI呼び出しがある場合は適切に延長する |
| フォールバック応答の用意 | エラー時でも「担当者に繋ぎます」のような応答を返して会話を終了する |
def handle_fulfillment(event):
"""エラーハンドリング付きフルフィルメント"""
try:
slots = event["sessionState"]["intent"]["slots"]
order_id = slots["OrderId"]["value"]["interpretedValue"]
# DB検索処理(実際にはDynamoDBなどを使用)
order = lookup_order(order_id)
if order:
message = format_order_response(order)
else:
message = f"注文番号 {order_id} は見つかりませんでした。"
except Exception as e:
logger.error(f"Error: {str(e)}")
message = "申し訳ございません。システムエラーが発生しました。お手数ですが、しばらく時間をおいてから再度お試しください。"
return close(event, "Fulfilled", message)
8. 料金について
| サービス | 料金 | 備考 |
|---|---|---|
| Amazon Lex | テキスト: $0.00075/リクエスト、音声: $0.004/リクエスト | 各ユーザー入力ごとに課金 |
| AWS Lambda | リクエスト数 + 実行時間課金 | 無料利用枠あり(月100万リクエスト) |
コードフックが有効な場合のLambda呼び出し回数
ダイアログコードフックを有効にすると、ユーザーの入力ごとにLambdaが呼び出されます。例えば1回の会話で3回のやり取りがあれば、Lambda呼び出しも3回以上発生します。コスト見積もりの際はこの点を考慮してください。
最新の料金は公式ページをご確認ください。
出典:Amazon Lex の料金 - AWS
9. おわりに
ここまでお読みいただきありがとうございます。
今回は、Lambda関数と連携して、注文データを動的に検索・応答できるボットを構築しました。
コードフック(ダイアログ・フルフィルメント)の仕組みを理解すれば、入力値のバリデーションからデータベース検索、外部API連携まで、あらゆるビジネスロジックをボットに組み込めるようになります。
次回#4では、構築したLexボットをAmazon Connectに統合し、電話で操作できる音声ボットを構築します。実際の電話回線からLexボットと会話する仕組みを作っていきましょう。
ではまた、お会いしましょう。






