Amazon LexがCloudFormation対応したので、みんなが触るであろうお花を注文するやつを作ってみた

2022.01.01

いわさです。

先日、CloudFormationでAmazon Lexがサポートされました。
Amazon Lexのチュートリアルでははじめにお花を注文するサンプルテンプレート"OrderFlowers"を使うかと思います。
本日はCloudFormationでOrderFlowersテンプレートを作成することでLexの各コンポーネントを理解してみたいと思います。

対応リソース

まず、作成出来るリソースはV2となります。
そして、対応しているリソースは以下4つです。

  • AWS::Lex::Bot
  • AWS::Lex::BotAlias
  • AWS::Lex::BotVersion
  • AWS::Lex::ResourcePolicy

リソースポリシーはLambdaと変わらないので、それ以外の3つを試してみたいと思います。

最小構成

まず、リソース作成をするためだけの最小テンプレートは以下となります。
※ロールは別途用意が必要です。

AWSTemplateFormatVersion: 2010-09-09
Description: Lex V2 Bot Template
Parameters: 
  BotName:
    Description: Bot Name.
    Type: String
Resources: 
  Bot:
    Type: AWS::Lex::Bot
    Properties: 
      DataPrivacy:
        ChildDirected: false
      IdleSessionTTLInSeconds: 300
      Name: !Ref BotName
      RoleArn: !GetAtt BotRuntimeRole.Arn

こちらを実行すると以下が作成されます。

Lexボットリソースのみを作成しますが、デフォルトバージョンのドラフトとデフォルトエイリアスのTestBotAliasは自動作成されます。 バージョンとエイリアスについては後述します。

一方で、ロケールやインテントは何も定義されていないので、ビルドも実行も出来ない状態です。
ボットの大枠が存在しているだけの状態です。

ロケール・言語、デフォルトインテントの作成

ボットでは、1つ以上の言語とロケールを作成しどの言語でユーザーと対話するかをまず選択する必要があります。
そして、対話を通して行うアクションであるインテントを定義します。

ここがボット定義のメイン部分になります。

インテントを作成した後にビルドすることで実行出来るようになりますが、ビルドするためには、デフォルトのフォールバックインテントとSample utterancesを持つカスタムインテントが1つ必要です。
Sample utterancesとは、ユーザーの発話例となり、この情報を使ってインテントが開始されます。

ここまでをまとめると、"ビルド・実行が可能"な最小テンプレートは以下となります。

Bot:
  Type: AWS::Lex::Bot
  Properties: 
    DataPrivacy:
      ChildDirected: false
    IdleSessionTTLInSeconds: 300
    Name: !Ref BotName
    RoleArn: !GetAtt BotRuntimeRole.Arn
    AutoBuildBotLocales: false
    BotLocales:
      - LocaleId: ja_JP
        NluConfidenceThreshold: 0.5
        Intents:
          - Name: FallbackIntent
            ParentIntentSignature: "AMAZON.FallbackIntent"
          - Name: HogeIntent
            SampleUtterances:
              - Utterance: Fuga

ビルドしてテストしてみましょう。

最低限ユーザーと対話出来るリソースを用意することが出来ました。
この状態では、マネジメントコンソールで「空のボットを作成します。」を選択し、言語と発話を追加直後と同等の状態です。

インテントを実装する

ここからサンプルテンプレートのようにしっかりした対話を実装するためにはインテントを作り込む必要があります。
サンプルテンプレートのインテントを先に見てみましょう。

まず、発話例としてユーザーから花を注文したいという発話が定義されています。

そして、インテントにはスロットという概念が仕組みがあります。
スロットはインテントの実行を行うためのパラメータを指しており、ボットがユーザーから対話形式で収集します。

また、確認プロンプトを使うことで、ボットがインテントを履行するかユーザーに確認することが出来ます。
ユーザーはそのタイミングでキャンセルすることも可能です。

サンプルではスロットに以下を定義しています。

  • どの花か
  • いつ受け取るか(何日、何時)

スロットには型があり、組み込みスロットタイプとカスタムスロットタイプの概念があります。
組み込みスロットタイプは事前にLexで定義されているものですぐに使用することが出来ます。
組み込みスロットタイプ - Amazon Lex

今回のサンプルでは受け取り日時は組み込みスロットタイプを使っており、花の種類はカスタムスロットタイプを使っています。

ここまでのサンプルボット相当をCloudFormationテンプレートで表現すると、以下のようになります。

Bot:
  Type: AWS::Lex::Bot
  Properties: 
    DataPrivacy:
      ChildDirected: false
    IdleSessionTTLInSeconds: 300
    Name: !Ref BotName
    RoleArn: !GetAtt BotRuntimeRole.Arn
    AutoBuildBotLocales: false
    BotLocales:
      - LocaleId: ja_JP
        NluConfidenceThreshold: 0.5
        # カスタムスロットタイプ
        SlotTypes:
          - Name: FlowerTypes
            ValueSelectionSetting:
              ResolutionStrategy: ORIGINAL_VALUE
            SlotTypeValues:
              - SampleValue: 
                  Value: ユリ
              - SampleValue: 
                  Value: バラ
              - SampleValue: 
                  Value: チューリップ
        # インテント
        Intents:
          - Name: FallbackIntent
            Description: 花のブーケを注文するインテント
            ParentIntentSignature: AMAZON.FallbackIntent
          - Name: HogeIntent
            # サンプル発話
            SampleUtterances:
              - Utterance: 花を注文
              - Utterance: 花を注文します
            # スロット
            Slots:
              - Name: FlowerType
                SlotTypeName: FlowerTypes
                ValueElicitationSetting:
                  SlotConstraint: Required
                  PromptSpecification:
                    MaxRetries: 3
                    MessageGroupsList:
                      - Message: 
                          PlainTextMessage: 
                            Value: どのような花を注文しますか?
              - Name: PickupDate
                SlotTypeName: AMAZON.Date
                ValueElicitationSetting:
                  SlotConstraint: Required
                  PromptSpecification:
                    MaxRetries: 3
                    MessageGroupsList:
                      - Message: 
                          PlainTextMessage: 
                            Value: 何日に {FlowerType} を受け取りますか?
              - Name: PickupTime
                SlotTypeName: AMAZON.Time
                ValueElicitationSetting:
                  SlotConstraint: Required
                  PromptSpecification:
                    MaxRetries: 3
                    MessageGroupsList:
                      - Message: 
                          PlainTextMessage: 
                            Value: 何時に {FlowerType} を受け取りますか?
            # スロットの優先順位
            SlotPriorities:
              - Priority: 1
                SlotName: FlowerType
              - Priority: 2
                SlotName: PickupDate
              - Priority: 3
                SlotName: PickupTime
            # 確認プロンプトと応答拒否
            IntentConfirmationSetting: 
              PromptSpecification:
                MaxRetries: 3
                MessageGroupsList:
                  - Message:
                      PlainTextMessage:
                        Value: わかりました。{FlowerType} は {PickupDate} の {PickupTime} に受け取ることができます。これでよろしいですか?
              DeclinationResponse:
                MessageGroupsList:
                  - Message:
                      PlainTextMessage:
                        Value: それでは、注文は行いません。

実行してみましょう。

良いですね。

バージョン

サンプルボットに関してはここまでとなります。
CloudFormationではバージョンの作成も行うことが出来ます。

Lexにはバージョンの概念があります。
ボットにはデフォルトの「ドラフト」バージョンが必ず存在しており、設定変更や開発はドラフトバージョンで行います。
そしてボットをデプロイする任意のタイミングでバージョンを作成します。
バージョンは読み取り専用のスナップショットです。

Creating versions - Amazon Lex

以下のテンプレートで作成することが出来ます。

BotVersion1:
  Type: AWS::Lex::BotVersion
  Properties:
    BotId: !Ref Bot
    BotVersionLocaleSpecification:
      - LocaleId: ja_JP
        BotVersionLocaleDetails:
          SourceBotVersion: DRAFT

バージョン番号はLexで管理されています。 よってCloudFormation上で上記リソースを削除してスタックを更新後に、再度同じリソースを作成した場合は新たなバージョンが作成されます。

エイリアス

エイリアスは特定バージョンを指すポインタです。
バージョンを直接参照する場合、ボットを利用する外部からはバージョンアップごとに参照コードや設定のアップデートが必要になってしまいます。
エイリアス参照させておくことで、エイリアスの更新のみで参照する実体を切り替えることが出来ます。DNSを使ったアップデートに似ていますね。

エイリアスは以下のような形でバージョン番号を指定して定義します。

BotVersion1:
  Type: AWS::Lex::BotVersion
  Properties:
    BotId: !Ref Bot
    BotVersionLocaleSpecification:
      - LocaleId: ja_JP
        BotVersionLocaleDetails:
          SourceBotVersion: DRAFT
BotVersion2:
  Type: AWS::Lex::BotVersion
  Properties:
    BotId: !Ref Bot
    BotVersionLocaleSpecification:
      - LocaleId: ja_JP
        BotVersionLocaleDetails:
          SourceBotVersion: DRAFT

BotProd:
  Type: AWS::Lex::BotAlias
  Properties:
    BotId: !Ref Bot
    BotAliasName: "Prod"
    BotVersion: !GetAtt BotVersion1.BotVersion
BotBeta:
  Type: AWS::Lex::BotAlias
  Properties:
    BotId: !Ref Bot
    BotAliasName: "Beta"
    BotVersion: !GetAtt BotVersion2.BotVersion

この状態で新規バージョンを作成していっても、参照する外部システムは何の影響も受けません。
上記では安定版とベータ版という形でエイリアスを用意しましたが、エイリアスを以下のように更新することで、ベータ版のみバージョンアップさせることが出来ます。

BotVersion1:
  Type: AWS::Lex::BotVersion
  Properties:
    BotId: !Ref Bot
    BotVersionLocaleSpecification:
      - LocaleId: ja_JP
        BotVersionLocaleDetails:
          SourceBotVersion: DRAFT
BotVersion2:
  Type: AWS::Lex::BotVersion
  Properties:
    BotId: !Ref Bot
    BotVersionLocaleSpecification:
      - LocaleId: ja_JP
        BotVersionLocaleDetails:
          SourceBotVersion: DRAFT
BotVersion3:
  Type: AWS::Lex::BotVersion
  Properties:
    BotId: !Ref Bot
    BotVersionLocaleSpecification:
      - LocaleId: ja_JP
        BotVersionLocaleDetails:
          SourceBotVersion: DRAFT

BotProd:
  Type: AWS::Lex::BotAlias
  Properties:
    BotId: !Ref Bot
    BotAliasName: "Prod"
    BotVersion: !GetAtt BotVersion1.BotVersion
BotBeta:
  Type: AWS::Lex::BotAlias
  Properties:
    BotId: !Ref Bot
    BotAliasName: "Beta"
    BotVersion: !GetAtt BotVersion3.BotVersion

さいごに

本日は新たにサポートされたCloudFormationを使って構築することで、Lex構築の基本となるインテント、運用の基本となるバージョンとエイリアスについて確認しました。

他サービスのインフラストラクチャと同じレベルで管理出来るようになったのはありがたいですね。
試してみる前は、ボットをCloudFormationで管理するなんて結構複雑なテンプレートになるのではなんて思ってましたが、想定したよりだいぶシンプルな実装をすることが出来ました。
ボット作成だけでなくバージョンとエイリアスでリリースの運用管理まで操作出来るようになっているのが嬉しいですね。

この検証では1つのスタックで全てのリソースをまとめてしまいましたが、最低限バージョンだけはバージョンアップ毎にテンプレートが肥大化していくのでインテントやエイリアスとはスタックを分けたほうが良さそうですね。