AWS CloudFormationでAmazon Lex ボット作成時、バージョンのみ手動作成するバージョン管理手法
はじめに
Amazon Lex ボットの開発において、AWS CloudFormationと手動操作を組み合わせたバージョン管理手法を紹介します。この方法では、ボットの基本構造をCloudFormationで管理しつつ、バージョン作成のみを手動で行うことで、柔軟性とリソース作成の効率性を両立させます。
この手法を採用することで、テンプレートのコード量を抑えながら、細やかなバージョン管理が可能になります。
Amazon Lexのバージョンとエイリアスについての詳細は、以下のリンクをご参照ください。
前回の記事では、AWS CloudFormationを使用してAmazon Lexのバージョンを作成する際の挙動を確認し、以下のことが確認できました。
- 新しいバージョンを追加しつつ、元のバージョンを残す場合は、テンプレートにリソースとしてボットバージョンを追加する必要がある
- テンプレートのコード量がどんどん増えてしまうデメリットが存在する
- 元のバージョンが不要な場合は、テンプレートのバージョンの論理名を変更するだけでよい
- CloudFormationでバージョンを作成する場合、ボットの構築(Build)が自動的に行われる
そのなかで、新しいバージョンを追加しつつ、元のバージョンを残す場合は、テンプレートにリソースとしてボットバージョンを追加する必要があるため、バージョンを作成する度に、テンプレート内のコード量が肥大化する問題がありました。
そのため、今回は、新しいバージョンを追加しつつ、元のバージョンも残す案として、バージョンのみテンプレートからは作成しない方法を紹介します。
利用するCloudFormationテンプレート
今回は以下のLexボットを作成するテンプレートを利用します。
このボットは、Amazon Connectから呼び出され、特定の商材名を聞き取ります。音声をS3バケットに保存し、Lexでの文字起こし内容をログとしてCloudWatch Logsに保存します。
バージョンは作成しませんが、エイリアスは作成するテンプレートです。
AWSTemplateFormatVersion: 2010-09-09
Description: Amazon Lex Bot with Intent creation (Japanese)
Parameters:
LexBotName:
Type: String
Default: cm-hirai-product-name
Description: The name of the Lex bot
SlotName:
Type: String
Default: name
Description: The name of the slot
CustomSlotTypeName:
Type: String
Default: productname
Description: The name of the custom slot type
AliasName:
Type: String
AllowedValues:
- dev
- prd
Default: dev
Description: The alias name for the Lex bot
Resources:
MyLexBot:
Type: AWS::Lex::Bot
Properties:
Name: !Ref LexBotName
Description: !Ref LexBotName
DataPrivacy:
ChildDirected: false
IdleSessionTTLInSeconds: 300
RoleArn: !GetAtt LexBotRole.Arn
BotLocales:
- LocaleId: ja_JP
NluConfidenceThreshold: 0.40
VoiceSettings:
VoiceId: Kazuha
Intents:
- Name: FallbackIntent
ParentIntentSignature: AMAZON.FallbackIntent
- Name: ProductName
SampleUtterances:
- Utterance: '{name}'
- Utterance: 商材名は、{name}です。
- Utterance: '{name}です。'
- Utterance: 聞きたいのは、{name}についてです。
Slots:
- Name: !Ref SlotName
SlotTypeName: !Ref CustomSlotTypeName
ValueElicitationSetting:
SlotConstraint: Required
PromptSpecification:
MaxRetries: 3
MessageGroupsList:
- Message:
PlainTextMessage:
Value: 商材名をお伝え下さい。
IntentConfirmationSetting:
PromptSpecification:
MaxRetries: 3
MessageGroupsList:
- Message:
PlainTextMessage:
Value: 商材名は、{name}、ですね。よろしければ、はい、と、異なる場合、いいえ、とお伝え下さい。
## 確認プロンプトに NOと伝えた場合のボットのプロンプト
# DeclinationResponse:
# MessageGroupsList:
# - Message:
# PlainTextMessage:
# Value: 正しく商材名を聞き取れず、申し訳ございません。
## 応答を閉じる
# IntentClosingSetting:
# ClosingResponse:
# MessageGroupsList:
# - Message:
# PlainTextMessage:
# Value: 担当者にお繋ぎします
SlotPriorities:
- Priority: 1
SlotName: !Ref SlotName
SlotTypes:
- Name: !Ref CustomSlotTypeName
ValueSelectionSetting:
ResolutionStrategy: ORIGINAL_VALUE
SlotTypeValues:
- SampleValue:
Value: Wifi
- SampleValue:
Value: ウォーターサーバー
- SampleValue:
Value: スマートホームデバイス
- SampleValue:
Value: 電動自転車
TestBotAliasSettings:
BotAliasLocaleSettings:
- LocaleId: ja_JP
BotAliasLocaleSetting:
Enabled: true
## Lambdaを利用する場合
# CodeHookSpecification:
# LambdaCodeHook:
# LambdaArn: !GetAtt LambdaFunction.Arn
ConversationLogSettings:
TextLogSettings:
- Enabled: true
Destination:
CloudWatch:
CloudWatchLogGroupArn: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${CloudWatchLogGroup}"
LogPrefix: TestBotAlias/
AudioLogSettings:
- Enabled: true
Destination:
S3Bucket:
S3BucketArn: !GetAtt S3Bucket.Arn
LogPrefix: TestBotAlias/
# バージョンを利用する場合
# BotVersion1:
# Type: AWS::Lex::BotVersion
# Properties:
# BotId: !Ref MyLexBot
# BotVersionLocaleSpecification:
# - LocaleId: ja_JP
# BotVersionLocaleDetails:
# SourceBotVersion: DRAFT
MyLexBotAlias:
Type: AWS::Lex::BotAlias
Properties:
BotAliasName: dev
BotId: !Ref MyLexBot
# バージョンを利用する場合
# BotVersion: !GetAtt BotVersion1.BotVersion
ConversationLogSettings:
TextLogSettings:
- Enabled: true
Destination:
CloudWatch:
CloudWatchLogGroupArn: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${CloudWatchLogGroup}"
LogPrefix: dev/
AudioLogSettings:
- Enabled: true
Destination:
S3Bucket:
S3BucketArn: !GetAtt S3Bucket.Arn
LogPrefix: dev/
LexBotRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: lexv2.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: !Sub ${LexBotName}-policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- polly:SynthesizeSpeech
Resource: '*'
- Effect: Allow
Action:
- s3:PutObject
Resource:
- !Sub "arn:aws:s3:::${S3Bucket}/*"
- Effect: Allow
Action:
- logs:CreateLogStream
- logs:PutLogEvents
Resource:
- !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${CloudWatchLogGroup}:*"
CloudWatchLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/lex/${LexBotName}"
RetentionInDays: 365
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref LexBotName
パラメータは、以下の内容でスタックを作成します。
スタックを作成すると、バージョンはテンプレートに記載していませんが、ドラフトバージョンは自動作成されます。
バージョンは作成していないため、エイリアスdevは作成されますが、バージョンには関連付けられてはいません。
このテンプレートからバージョンのみを手動で作成します。
バージョンのみ手動で作成
バージョンのみを手動で作成する場合、AWS CLIのコマンドをスタックの出力に記載し、その出力内のコマンドを1行ずつ実行することで、バージョンの作成とエイリアスとの関連付けできるようにします。
最初に紹介したテンプレートに、以下のようにOutputsセクションを追加します。
CloudFormationテンプレート
AWSTemplateFormatVersion: 2010-09-09
Description: Amazon Lex Bot with Intent creation (Japanese)
Parameters:
LexBotName:
Type: String
Default: cm-hirai-product-name
Description: The name of the Lex bot
SlotName:
Type: String
Default: name
Description: The name of the slot
CustomSlotTypeName:
Type: String
Default: productname
Description: The name of the custom slot type
AliasName:
Type: String
AllowedValues:
- dev
- prd
Default: dev
Description: The alias name for the Lex bot
Resources:
MyLexBot:
Type: AWS::Lex::Bot
Properties:
Name: !Ref LexBotName
Description: !Ref LexBotName
DataPrivacy:
ChildDirected: false
IdleSessionTTLInSeconds: 300
RoleArn: !GetAtt LexBotRole.Arn
BotLocales:
- LocaleId: ja_JP
NluConfidenceThreshold: 0.40
VoiceSettings:
VoiceId: Kazuha
Intents:
- Name: FallbackIntent
ParentIntentSignature: AMAZON.FallbackIntent
- Name: ProductName
SampleUtterances:
- Utterance: '{name}'
- Utterance: 商材名は、{name}です。
- Utterance: '{name}です。'
- Utterance: 聞きたいのは、{name}についてです。
Slots:
- Name: !Ref SlotName
SlotTypeName: !Ref CustomSlotTypeName
ValueElicitationSetting:
SlotConstraint: Required
PromptSpecification:
MaxRetries: 3
MessageGroupsList:
- Message:
PlainTextMessage:
Value: 商材名をお伝え下さい。
IntentConfirmationSetting:
PromptSpecification:
MaxRetries: 3
MessageGroupsList:
- Message:
PlainTextMessage:
Value: 商材名は、{name}、ですね。よろしければ、はい、と、異なる場合、いいえ、とお伝え下さい。
## 確認プロンプトに NOと伝えた場合のボットのプロンプト
# DeclinationResponse:
# MessageGroupsList:
# - Message:
# PlainTextMessage:
# Value: 正しく商材名を聞き取れず、申し訳ございません。
## 応答を閉じる
# IntentClosingSetting:
# ClosingResponse:
# MessageGroupsList:
# - Message:
# PlainTextMessage:
# Value: 担当者にお繋ぎします
SlotPriorities:
- Priority: 1
SlotName: !Ref SlotName
SlotTypes:
- Name: !Ref CustomSlotTypeName
ValueSelectionSetting:
ResolutionStrategy: ORIGINAL_VALUE
SlotTypeValues:
- SampleValue:
Value: Wifi
- SampleValue:
Value: ウォーターサーバー
- SampleValue:
Value: スマートホームデバイス
- SampleValue:
Value: 電動自転車
TestBotAliasSettings:
BotAliasLocaleSettings:
- LocaleId: ja_JP
BotAliasLocaleSetting:
Enabled: true
## Lambdaを利用する場合
# CodeHookSpecification:
# LambdaCodeHook:
# LambdaArn: !GetAtt LambdaFunction.Arn
ConversationLogSettings:
TextLogSettings:
- Enabled: true
Destination:
CloudWatch:
CloudWatchLogGroupArn: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${CloudWatchLogGroup}"
LogPrefix: TestBotAlias/
AudioLogSettings:
- Enabled: true
Destination:
S3Bucket:
S3BucketArn: !GetAtt S3Bucket.Arn
LogPrefix: TestBotAlias/
# バージョンを利用する場合
# BotVersion1:
# Type: AWS::Lex::BotVersion
# Properties:
# BotId: !Ref MyLexBot
# BotVersionLocaleSpecification:
# - LocaleId: ja_JP
# BotVersionLocaleDetails:
# SourceBotVersion: DRAFT
MyLexBotAlias:
Type: AWS::Lex::BotAlias
Properties:
BotAliasName: dev
BotId: !Ref MyLexBot
# バージョンを利用する場合
# BotVersion: !GetAtt BotVersion1.BotVersion
ConversationLogSettings:
TextLogSettings:
- Enabled: true
Destination:
CloudWatch:
CloudWatchLogGroupArn: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${CloudWatchLogGroup}"
LogPrefix: dev/
AudioLogSettings:
- Enabled: true
Destination:
S3Bucket:
S3BucketArn: !GetAtt S3Bucket.Arn
LogPrefix: dev/
LexBotRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: lexv2.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: !Sub ${LexBotName}-policy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- polly:SynthesizeSpeech
Resource: '*'
- Effect: Allow
Action:
- s3:PutObject
Resource:
- !Sub "arn:aws:s3:::${S3Bucket}/*"
- Effect: Allow
Action:
- logs:CreateLogStream
- logs:PutLogEvents
Resource:
- !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${CloudWatchLogGroup}:*"
CloudWatchLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/lex/${LexBotName}"
RetentionInDays: 365
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref LexBotName
Outputs:
01BuildBot:
Value: !Sub |+
aws lexv2-models build-bot-locale
--bot-id "${MyLexBot}"
--bot-version "DRAFT"
--locale-id "ja_JP"
02CheckBuildStatus:
Value: !Sub |+
aws lexv2-models describe-bot-locale
--bot-id "${MyLexBot}"
--bot-version "DRAFT"
--locale-id "ja_JP"
--query 'botLocaleStatus'
03CreateBotVersion:
Value: !Sub |+
BOT_VERSION=$(aws lexv2-models create-bot-version
--bot-id "${MyLexBot}"
--bot-version-locale-specification '{
"ja_JP": {
"sourceBotVersion": "DRAFT"
}
}'
--description "Your bot version description"
--output json | jq -r '.botVersion')
03EchoBotVersion:
Value: |+
echo "The bot version you created: $BOT_VERSION"
04AssociateAlias:
Value: !Sub |+
aws lexv2-models update-bot-alias
--bot-alias-id "${MyLexBotAlias.BotAliasId}"
--bot-id "${MyLexBot}"
--bot-alias-name "${AliasName}"
--bot-version "$BOT_VERSION"
--conversation-log-settings '{
"textLogSettings": [
{
"enabled": true,
"destination": {
"cloudWatch": {
"cloudWatchLogGroupArn": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${CloudWatchLogGroup}",
"logPrefix": "${AliasName}/"
}
}
}
],
"audioLogSettings": [
{
"enabled": true,
"destination": {
"s3Bucket": {
"s3BucketArn": "${S3Bucket.Arn}",
"logPrefix": "${AliasName}/"
}
}
}
]
}'
追加した内容は以下の通りです。
Outputs:
01BuildBot:
Value: !Sub |+
aws lexv2-models build-bot-locale
--bot-id "${MyLexBot}"
--bot-version "DRAFT"
--locale-id "ja_JP"
02CheckBuildStatus:
Value: !Sub |+
aws lexv2-models describe-bot-locale
--bot-id "${MyLexBot}"
--bot-version "DRAFT"
--locale-id "ja_JP"
--query 'botLocaleStatus'
03CreateBotVersion:
Value: !Sub |+
BOT_VERSION=$(aws lexv2-models create-bot-version
--bot-id "${MyLexBot}"
--bot-version-locale-specification '{
"ja_JP": {
"sourceBotVersion": "DRAFT"
}
}'
--description "Your bot version description"
--output json | jq -r '.botVersion')
03EchoBotVersion:
Value: |+
echo "The bot version you created: $BOT_VERSION"
04AssociateAlias:
Value: !Sub |+
aws lexv2-models update-bot-alias
--bot-alias-id "${MyLexBotAlias.BotAliasId}"
--bot-id "${MyLexBot}"
--bot-alias-name "${AliasName}"
--bot-version "$BOT_VERSION"
--conversation-log-settings '{
"textLogSettings": [
{
"enabled": true,
"destination": {
"cloudWatch": {
"cloudWatchLogGroupArn": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${CloudWatchLogGroup}",
"logPrefix": "${AliasName}/"
}
}
}
],
"audioLogSettings": [
{
"enabled": true,
"destination": {
"s3Bucket": {
"s3BucketArn": "${S3Bucket.Arn}",
"logPrefix": "${AliasName}/"
}
}
}
]
}'
新しく上記のテンプレートでスタックを作成します。
スタックの出力は以下の通りです。
スタックの出力(Output)をそのままコピペして実行するだけで作成したバージョンをエイリアスと関連付けまで対応できます。
$ aws lexv2-models build-bot-locale --bot-id "${MyLexBot}" --bot-version "DRAFT" --locale-id "ja_JP"
$ aws lexv2-models describe-bot-locale --bot-id "${MyLexBot}" --bot-version "DRAFT" --locale-id "ja_JP" --query 'botLocaleStatus'
$ BOT_VERSION=$(aws lexv2-models create-bot-version --bot-id "${MyLexBot}" --bot-version-locale-specification '{ "ja_JP": { "sourceBotVersion": "DRAFT" } }' --description "Your bot version description" --output json | jq -r '.botVersion')
$ echo "The bot version you created: $BOT_VERSION"
$ aws lexv2-models update-bot-alias --bot-alias-id "${AliasName}" --bot-id "${MyLexBot}" --bot-alias-name "${AliasName}" --bot-version "$BOT_VERSION" --conversation-log-settings '{ "textLogSettings": [ { "enabled": true, "destination": { "cloudWatch": { "cloudWatchLogGroupArn": "arn:aws:logs:ap-northeast-1:xxxxxxxxxxxx:log-group:${CloudWatchLogGroup}", "logPrefix": "${AliasName}/" } } } ], "audioLogSettings": [ { "enabled": true, "destination": { "s3Bucket": { "s3BucketArn": "${S3Bucket.Arn}", "logPrefix": "${AliasName}/" } } } ] }'
${〇〇}
は変数であり、スタックの出力(Output)には実際の値が入ります。
5つのコマンドの流れとしては以下の通りです。
- ボットをビルドする
- ボットの構築ステータスを確認する
- バージョンを作成する
- ボットのバージョンを確認する
- 作成したバージョンをエイリアスと関連付ける
各コマンドを解説します。
ボットをビルドする
バージョンを作成する前に、ボットをビルドする必要があります。
手動でバージョンを作成する場合、以下のような状況では、ボットをビルドしてからバージョンを作成する必要があります
- ボット作成直後にバージョンを作成する場合
- ボットを作成しビルド後、設定変更をしたため、その変更内容を反映したバージョンを作成する場合
ドラフトバージョンをビルドせずにバージョンを作成すると、そのバージョンには設定変更内容が反映されません。また、ボット作成直後にビルドしない場合、ボット自体利用することができません。
余談ですが、CloudFormationでバージョンを作成する場合、スタックの更新中に自動的にビルドが行われます。
したがって、スタック作成後にドラフトバージョンをコマンドでビルドします。
$ aws lexv2-models build-bot-locale --bot-id "WNMQERKB1Z" --bot-version "DRAFT" --locale-id "ja_JP"
ビルドには時間がかかります。今回は、1分弱ほどかかりました。2つ目のコマンドでビルドステータスが確認できます。
$ aws lexv2-models describe-bot-locale --bot-id "WNMQERKB1Z" --bot-version "DRAFT" --locale-id "ja_JP" --query 'botLocaleStatus'
"Building"
$ aws lexv2-models describe-bot-locale --bot-id "WNMQERKB1Z" --bot-version "DRAFT" --locale-id "ja_JP" --query 'botLocaleStatus'
"ReadyExpressTesting"
$ aws lexv2-models describe-bot-locale --bot-id "WNMQERKB1Z" --bot-version "DRAFT" --locale-id "ja_JP" --query 'botLocaleStatus'
"Built"
今回は、ビルドが完了するまでに以下のステータスが確認できました。
Building
: ビルド中であり、テストできないReadyExpressTesting
:インテントとスロットタイプに定義された発話を使用してボットをテストできます。Built
:ボットは使用できる状態であり、任意の発話を使用してテストできます。
他にも、ビルドに失敗した場合のFailed
などのステータスが用意されています。詳細は以下のドキュメントをご参照ください。
バージョンを作成
以下のコマンドでバージョンを作成します。
$ BOT_VERSION=$(aws lexv2-models create-bot-version --bot-id "WNMQERKB1Z" --bot-version-locale-specification '{ "ja_JP": { "sourceBotVersion": "DRAFT" } }' --description "Your bot version description" --output json | jq -r '.botVersion')
バージョン1が作成されました。
以下のコマンドで作成されたバージョン数が確認できます。
$ echo "The bot version you created: $BOT_VERSION"
The bot version you created: 1
変数のBOT_VERSION
は、次のバージョンをエイリアスに関連付ける際に利用します。
バージョンをエイリアスに関連付け
最後に、作成したバージョンをエイリアスに関連付けます。
CloudFormationテンプレートで作成したエイリアスdev
に、バージョン1を関連付けます。スタックの出力(Output)に記載されているコマンドをそのまま実行するだけで実現できます。
$ aws lexv2-models update-bot-alias --bot-alias-id "LPMCY0CEEZ" --bot-id "WNMQERKB1Z" --bot-alias-name "dev" --bot-version "$BOT_VERSION" --conversation-log-settings '{ "textLogSettings": [ { "enabled": true, "destination": { "cloudWatch": { "cloudWatchLogGroupArn": "arn:aws:logs:ap-northeast-1:xxxxxxxxxxxx:log-group:/aws/lex/cm-hirai-product-name", "logPrefix": "dev/" } } } ], "audioLogSettings": [ { "enabled": true, "destination": { "s3Bucket": { "s3BucketArn": "arn:aws:s3:::cm-hirai-product-name", "logPrefix": "dev/" } } } ] }'
{
"botAliasId": "LPMCY0CEEZ",
"botAliasName": "dev",
"botVersion": "1",
"conversationLogSettings": {
"textLogSettings": [
{
"enabled": true,
"destination": {
"cloudWatch": {
"cloudWatchLogGroupArn": "arn:aws:logs:ap-northeast-1:xxxxxxxxxxxx:log-group:/aws/lex/cm-hirai-product-name",
"logPrefix": "dev/"
}
}
}
],
"audioLogSettings": [
{
"enabled": true,
"destination": {
"s3Bucket": {
"s3BucketArn": "arn:aws:s3:::cm-hirai-product-name",
"logPrefix": "dev/"
}
}
}
]
},
"botAliasStatus": "Available",
"botId": "WNMQERKB1Z",
"creationDateTime": "2024-07-22T04:45:07.382000+00:00",
"lastUpdatedDateTime": "2024-07-22T05:30:14.347000+00:00"
}
作成したバージョンのボットのログ出力設定もしてます。ログ出力の設定を記載しない場合、エイリアスの会話ログは無効化されます。
エイリアスがバージョン1に関連付けされました。
Lexの設定を変更するためにCloudFormationテンプレートを修正してスタックを更新した場合、テンプレート内ではエイリアスdevとバージョン1の関連付けを行っていませんが、スタック更新後もエイリアスdevはバージョン1に関連付けられたままとなります。
また、バージョン1も削除はされません。
CloudFormationテンプレートを変更し、スタック更新後もバージョン1は削除されない
CloudFormationテンプレートを変更し、スタック更新後もエイリアスdevとバージョン1は関連付けされたまま
スタック更新後、スタックの出力コマンドを実行すると、バージョン2が作成され、エイリアスdevと関連付けられます。
最後に
AWS CloudFormationを使用してAmazon Lex ボットを作成する際、バージョン作成のみを手動で行う選択肢があることを紹介しました。
この方法を利用することで、テンプレートのコード量を抑えつつ、柔軟にバージョン管理を行うことができます。
参考になれば幸いです。
参考