Alexa Live 2020で紹介のあったAlexa Conversations、2回に分けてセッションまとめを書きました。
なんとなく概念的なものと開発フローの流れはつかめた気がするので、現在のスキル開発とどう違うのか?の観点で、公式のチュートリアルをやってみましたので、そのメモ。
きちんと理解できているか、はちょっと怪しいところもあるかなと思いますので、あくまでも個人的な理解、単なるやってみたメモだという点について予めご認識ください。
目次
- 目次
- 注意
- サンプルのスキル
- スキルの新規作成
- Alexa Conversationsの有効化とダイアログマネージャの選択
- Alexa Conversationsの5つのコンポーネント
- バックエンド
- テスト
- まとめ
注意
- まだベータです。将来的に変わる可能性があります。
- 手元で確認した限り、USアカウントかつen-USロケールでしか動かないようです。JPアカウントではen-USロケールでスキル作成しても動きませんでした。
- JPアカウントでも、スキル新規作成時にデフォルトの言語を「en-US」にすれば、スキルの「作成」はできます。
- が、テストシミュレータでスキルが起動できません・・・
サンプルのスキル
チュートリアルと同じ"Pet Match"スキルを作ります。こんな感じのやり取りになります。
Alexa, open pet match.
(アレクサ、ペットマッチをひらいて)
Welcome to pet match. I can find the best dog for you. What are two things you’re looking for in a dog?
(ペットマッチにようこそ。あなたにピッタリの犬を教えます。あなたが求める犬の条件を2つ教えてください。)
I want a large family dog.
(大きくて、家族になれるのがいいな)
Do you want a high-energy or low-energy dog?
(活発なのとおとなしいのではどちらがいいですか?)
Low.
(おとなしいやつ)
So you want a large low-energy family dog. I recommend a chihuahua.
(大きくて、おとなしい、家族になれる犬がいいんですね。それならチワワがおすすめです。)
犬の3つの特徴(大きさ、気質、元気さ)の好みを言うと、おすすめの犬の種類を答えてくれるというデモです。3つの好みがすべて揃わなければユーザに聞き返すという、いかにもダイアログモデル的なやり取りですね。
スキルの新規作成
早速やっていきましょう。USアカウントでスキルを作成します。
- スキル名は"pet match test"にします。
- デフォルトの言語は「英語(米国)」=en-USを選択してください。
- スキルの種類は「カスタム」を選択します。
- スキルのバックエンドは「Alexa-Hosted(Node.js)」を選択します。
- 「スキルの作成」をクリックします。
テンプレートを選択します。すでにAlexa Conversations用のテンプレートが用意されていますが、普通に"HelloWorldスキル"のテンプレートで勧めましょう。選択したら「テンプレートで続ける」をクリックします。
スキルの作成完了まで待ちます。
スキルが作成されました。
Alexa Conversationsの有効化とダイアログマネージャの選択
まずAlexa Conversationsを有効にしましょう。左のメニューから「インタフェース」をクリックします。
インタフェースの中に"Alexa Conversations"がありますので有効化します。"Use Alexa Conversations as default dialog manager" というチェックボックスは何でしょう?
ダイアログマネージャとは
Alexa Conversationsは2つの実装方法があります。それがダイアログマネージャです。セッションまとめにも書いたのでその部分を抜粋します。
Alexa Conversationsにすべてをおまかせするか、それとも一部だけをおまかせするか、っていう感じみたいです。これから新しくスキルを新しく作ってAlexa Conversationsを全面的に採用するのであれば"default dialog manager"、既存のスキルの一部のやり取りだけをAlexa Conversationsに任せるのであれば"custom skill dialog manager"にするのが良さそうです(ここは未確認)。
今回はAlexa Conversationsがどういうものかを知るのが目的なので、"default dialog manager"にしましょう。"Use Alexa Conversations as default dialog manager"にチェックを入れます。
「インタフェースを保存」をクリックします。この時点では「モデルをビルド」する必要はありません(というかモデルがまだないのでエラーになります)
すると左のメニューにAlexa Conversationsが表示されて、利用できるようになりました。
Alexa Conversationsの5つのコンポーネント
ではいよいよAlexa Conversationsの設定を行っていきます。Alexa Conversationsのメニューをクリックすると以下のようなメニューが表示されます。
Alexa Liveで紹介されていた「5つのコンポーネント」と同じものですね。
Alexa Conversationsではこの5つのコンポーネントを使って、これまでの「インテント(+サンプル発話)」ベースとは異なるやり方で対話モデルを作成します。スロットだけは変わりませんね。
では順に設定していきましょう。
レスポンステンプレート(その1)
まず最初にレスポンステンプレートを少しだけ触ります。ちょっと順番が違うと思うかもしれませんが、こういうものだと割り切って考えればいいのかなと思っています。
Alexa Conversationsの目的は、ダイアログモデル的なマルチターンの会話を柔軟に構築することです。サンプルの会話でいうとこの部分です。
I want a large family dog
Do you want a high-energy or low-energy dog?
Low.
So you want a large low-energy family dog. I recommend a chihuahua.
これらを上記で紹介した5つのコンポーネントを使って実装していくわけですが、以下の部分はどうなるでしょうか?
Alexa, open pet match.
Welcome to pet match. I can find the best dog for you. What are two things you’re looking for in a dog?
ここはマルチターンではなくて、単にLaunchRequestに対する応答ですね。最初にAlexa Conversationsを有効化した際、default dialog managerを有効にすると、すべての会話がAlexa Conversationsにコントロールされる、と書きました。つまり、マルチターンではない部分もAlexa Conversationsによってコントロールされます。でここだけやり方が異なります。実際に見てみましょう。
"Rensposes"をクリックします。
すでに5つのレスポンステンプレートが登録されています。とりあえず、"welcome"を開いてみましょう。"編集"をクリックします。
"welcome"のレスポンステンプレートが開きました。"Audio Response"の下のドロップダウンを開いてみてください。
ここにいくつか並んでいますね。"welcome"は"AlexaConversationsWelcome"というものに紐付いているようですね。次にドロップダウンの横にある"Edit in APL Audio"をクリックしてみてください。
別タブでAPL-Aのオーサリングツールが開きました。中身を見ると "Welcome" と発話させるようになっています。"Play" をクリックすると確認することができます。
同様に "provide_help" のレスポンステンプレートを見てみてください。"AlexaConversationsProvideHelp" というものに紐付いていて、ヘルプ的なメッセージが流れるようになっています。
つまり、これまで実装が必須となっていた、LaunchRequestやHelp/Stopなどのビルトインインテントに等しいものは、Alexa Conversationsでも必須として組み込まれており、その場合は単純にレスポンスを設定するだけということのようです。予め設定されているものはこんな感じかなと思います。
レスポンステンプレート名 | プロンプト名 | 相当する過去のビルトインインテント |
---|---|---|
welcome | AlexaConversationsWelcome | LaunchRequest(ただしちょっとこれは特殊っぽい) |
bye | AlexaConversationsBye | StopIntent |
provide_help | AlexaConversationsHelp | HelpIntent |
reqmore | AlexaConversationsRequestMore | (相当するものがなさげ・・・「どうしますか?」みたいなメッセージが設定されている。reprompt?) |
out_of_domain | AlexaConversationsOutOfDomain | FallbackIntent(かな?) |
では実際に設定してみましょう。
welcomeのレスポンステンプレートを開いて、"Edit in APL Audio"をクリック、APL-Aのオーサリングツールが開いたら以下のJSONで上書きしてください。保存も忘れず。
{ "type": "APLA", "version": "0.8", "mainTemplate": { "parameters": [ "payload" ], "item": { "type": "Selector", "strategy": "randomItem", "description": "Change 'type' above to try different Selector Component Types like Sequencer", "items": [ { "type": "Speech", "contentType": "text", "content": "Welcome to pet match. I can find the best dog for you. What are the two things you're looking for in a dog?", "description": "Expand on 'items' array to add multiple prompts, use response template arguments by adding it to 'content' like this ${payload.input_argument_name} and add SSML by changing 'contentType' to 'SSML' and adding SSML to 'content' like that" }, { "type": "Speech", "contentType": "text", "content": "Welcome to pet match. I can find the best dog for you. What size of dog are you looking for?", "description": "Expand on 'items' array to add multiple prompts, use response template arguments by adding it to 'content' like this ${payload.input_argument_name} and add SSML by changing 'contentType' to 'SSML' and adding SSML to 'content' like that" }, { "type": "Speech", "contentType": "text", "content": "Welcome to pet match. I can find the best dog for you. What temperament are you looking for in a dog?", "description": "Expand on 'items' array to add multiple prompts, use response template arguments by adding it to 'content' like this ${payload.input_argument_name} and add SSML by changing 'contentType' to 'SSML' and adding SSML to 'content' like that" } ] } } }
同様にprovide_helpも編集します。こちらは直接APL-AテンプレートのmainTemplate.item.items[0].contentを以下のように編集します。こちらも保存を忘れずに。
"content": "This is pet match. I can help you find the perfect pet for you. You can say, I want a large dog.",
はい、これでスキル起動時と「ヘルプ」と発話した場合のレスポンスが設定できました。ここについてはそういうものだと思ってもいい気がします。
実際にはwelcomeだけはちょっと特殊っぽいです。
ダイアログ
ここからがAlexa Conversationsっぽいところになります。まずはダイアログから。
ダイアログはいわゆる「台本」です。ここでユーザとAlexaのやりとりを記載して、その後以下の情報と紐付けしていくようなやり方になります。
- スロット
- 発話セット
- API定義
- レスポンステンプレート
これらを踏まえて、Alexa Conversationsは会話のパターンを学習し、異なる会話パターンや言い方のバリエーションなどを自動的に生成する、ということのようです。
では実際にやってみましょう。
左のメニューから"Dialogs"をクリックして、"Add dialog"をクリックします。
ダイアログの新規作成画面になりますので、まずダイアログ名を設定します。ここでは"dialog0"とします。
チュートリアルのようなものが開きますが、一旦”X”で閉じます。
では、ユーザとAlexaのやり取りを入力していきましょう。予めユーザとAlexaのやりとりを入力する欄が用意されていますので、以下のように入力します。
USER: I want a large family dog.
ALEXA: Do you want a high-energy or low-energy dog?
右側にメニューのようなものが出てくると思いますが、一旦無視しましょう。
続けて会話を追加します。"User says"をクリックするとユーザの発話入力欄が、"Alexa says"をクリックするとAlexaの発話欄が追加されますので、これを繰り返して会話を続けて設定していきます。
以下を追加します。
USER: Low.
ALEXA: So you want a large low-energy family dog. I recommend a chihuahua.
最終的にこんな感じになります。最後に「Save」をクリックして保存します。
これで1つ目のダイアログ作成が完了しました。サイズと気質が指定された場合のやり取りですね。続けて、あと4つほど異なるパターンのダイアログを作成していきます。
- dialog1
サイズと元気さの2つが指定された場合のやりとりです。
U: I want a large dog with high energy.
A: Do you want a dog that is good with family or better at guarding?
U: Family.
A: Okay, in that case I recommend a chihuahua.
- dialog2
気質と元気さの2つが指定された場合のやりとりです。
U: I want a family dog with high energy.
A: Do you prefer tiny, small, medium, or large dogs?
U: Large.
A: Okay, in that case I recommend a chihuahua.
- dialog3
サイズ、気質、元気さのすべてが指定された場合のやりとりです。
U: I want a large high energy guard dog.
A: Okay, in that case I recommend a chihuahua.
- dialog4
サイズだけが指定された場合のやりとりです。
U: I want a large dog.
A: Do you prefer low, medium, or high energy dogs?
U: Low.
A: Do you prefer a family dog or a guard dog?
U: Guard.
A: Okay, in that case I recommend a chihuahua.
ではこれらにいろいろな情報を紐付けしていきましょう。
スロット
カスタムスロットタイプの作成(その1)
スキル開発になれた方なら特に説明は不要ですね。さきほどダイアログで設定したユーザの発話で、カスタムスロットタイプで指定すべきものがありましたよね。
U: I want a large family dog.
U: I want a large dog with high energy.
U: I want a family dog with high energy.
U: I want a large high energy guard dog.
U: I want a large dog.
サンプル発話をスロットに置き換えると以下のような感じになります。
U: I want a {size} {energy} energy {temperament} dog.
設定すべきそれぞれのスロット名と値・シノニムは以下の通りです。
- size
- 大きさ
- small(tiny)
- medium(regular)
- large(gigantic/huge/big)
- 大きさ
- energy
- 元気さ
- low(lazy/calm/chill)
- medium(regular)
- high(hi/hyper/excited)
- 元気さ
- temperament
- 気質
- family(sweet/nice/friendly/loving)
- guard(protector/jerk/angry/mean/mad/aggressive)
- 気質
ではこれらのカスタムスロットタイプを作成します。カスタムスロットタイプはAlexa Conversationsのメニュー配下から直接設定はできないようなので、「アセット」→「スロットタイプ」のメニューから設定します(ここも以前とはメニューの場所が少し変わっていますね。あと、チュートリアルでは対話モデルのJSONを直接インポートしていましたが、ちょっと違うやり方でやってみます。)
スロットタイプの画面で「+スロットタイプ」をクリックします。
スロットタイプの設定画面です。以前はカスタムスロットタイプとビルトインの2つだったと思いますが、3つに増えていますね。"Create a custom slot type with properties"というのが新しく追加されていますが、これは後ほど。これまでのカスタムスロットタイプにあたるのは"Create a custom slot type with values”になります。こちらを選択して、まずは"size"から作成しましょう。
以下のようにスロット値とシノニムを設定して、保存してください。
同様にして、energyとtemperamentも追加していきますが、CSVを使ってバルクインポートしちゃいましょう。カスタムスロットタイプ"energy"を"Create a custom slot type with values”で作成して、スロット値の追加画面で「一括編集」をクリックします。
以下のCSVを貼り付けて「送信」をクリックします。
low,,lazy,calm,chill medium,,regular high,,hi,hyper,excited
できました。保存を忘れずに。
同様にして以下のCSVからtemperamentも作成してください。
family,,sweet,nice,friendly,loving guard,,protector,jerk,angry,mean,mad,aggressive
カスタムスロットタイプの作成(その2)
Alexa Conversationsでは、もう一つ、カスタムスロットタイプを作る必要があります。Alexa Liveのまとめから抜粋します。
- 2種類のスロット
- singular slot
- 単一の値を表す
- 「水曜日」や「台所」は個々の値
- compound slot
- 複合的な値セットを表す
- APIから返される
さきほど設定したsize、energy、temperamentは、3つともsingular slotでこれまでのスロットと使い方は変わらず、ユーザの発話に含まれるスロットをマッピングするのに使いますが、APIからのレスポンスやレスポンステンプレートへの割当はcompound slot、つまり複数のスロットを構造化データとしてまとめたとして一つのスロットとして扱うようになるようです。現時点ではちょっとピンときていませんが、とりあえず進めてみましょう。
まず、nameというスロットタイプを作成します。これはユーザの発話からスロットを抽出するわけではなく、APIからのレスポンスとして犬種名が入ります。なので、singular slotとして作成します。"Create a custom slot type with values" を選択して、スロット値は"dummy"というのを入れておけばよいです。
次に"getRecommendationResult"というスロットタイプを、今度はcompound slotで作成しますので、"Create a custom slot type with properties"で作成します。
"Add properties"をクリックします。
プロパティとして、name、size、energy、temperamentを追加します。スロット名とスロットタイプは同じで良いです。最後に保存。
ダイアログとの紐付けは後でまとめて行います。
API定義
ユーザから受け取ったスロット値は引数としてバックエンド(コードエディタで設定するLambdaのコード)に渡して、その結果をレスポンスとして受け取ってAlexaの発話でユーザに返すわけですが、API定義ではそのための「定義」を行います。この定義を見て、Alexa Conversationsは、どういう情報をユーザから引き出す必要があるのか、そして結果をどういうレスポンスとして返す必要があるのか、を判断するようなイメージだと思います。
ではやっていきましょう。左のAlexa Conversationsメニューから"API Definitions"をクリックして、"Add API"をクリックします。
API名に"getRecommendation"を設定して、"Add new arguments"をクリックします。
argumentsはAPIが結果を返すために必要なパラメータのことです。今回のサンプルだと、おすすめの犬の犬種を出すために必要なsize、energy、temperamentの3つのスロットで渡される値がそれに当たりますので、それぞれの引数名とスロットタイプを設定します。
あくまでもここで指定するのは、APIから見たパラメータになるので、スロットそのものではありません。なのでパラメータ名とスロットタイプをここでも指定するということです。
以下のように設定してください。
さらにAPI定義では以下の設定が必要です。
まずリターンタイプです。ここはAPIから返される値の「型」をセットします。「型」というのは、要はスロットタイプです。つまり、ここでさきほどcompound slotとして作成した「getRecommendationResult」を使えばよいということですね。getRecommendationResultを選択して、一旦ここで保存します。
最後にAPI結果用のレスポンステンプレートを設定する必要があるのですが、この画面からはそのまま作成ができない(事前に作ったレスポンステンプレートを選択するしかできない)ので、レスポンステンプレートを設定してから再度この画面に戻ることとします。
レスポンステンプレート(その2)
最初にLaunchRequestやHelpに対応するために少し触りましたが、レスポンステンプレートはユーザの発話に対するAlexaの応答を設定するためのテンプレートです。ここにさきほど作成したAPI定義へのレスポンスも含めて、追加していきます。
改めてダイアログを見てみましょう。dialog0で説明します。
- dialog0
USER: I want a large family dog.
ALEXA: Do you want a high-energy or low-energy dog? USER: Low.
ALEXA: So you want a large low-energy family dog. I recommend a chihuahua.
この中にあるAlexaのレスポンスは、energyが指定されていない場合に追加確認を行う
ALEXA: Do you want a high-energy or low-energy dog?
と、3つのスロットから結果を返す
ALEXA: So you want a large low-energy family dog. I recommend a chihuahua.
ですよね。
同様に他のダイアログもみてみると、
の、合計4つのAlexaのレスポンスパターンが有ることがわかります。つまり、これらをレスポンステンプレートとして登録する必要があるということになります。
では、まず引数が足りない場合のレスポンステンプレートを作成しましょう。まずsizeから。
Alexa Conversationsのメニューから"Responses"を表示して、"Add Response"をクリックします。
レスポンステンプレート名に"request_size"(sizeが足りないのでsizeをリクエストするレスポンスということですね)を入力します。
Audio Responseの下のドロップダウンから"Create new prompt"を選択します。
Alexaが返すプロンプトを設定する画面が出てきます。Prompt nameはテンプレート名と同じ"request_size"でよいでしょう。Alexa Promptsに実際にAlexaが返すレスポンスを入力してます。入力したら、そのままENTERをクリックするか、"+"をクリックします。
Do you prefer tiny, small, medium, or large dogs?
登録されました。続けて以下も登録してください。登録後に下の「保存」をクリックしたあと、上の"Save"をクリックして、保存します。レスポンス登録時の"+"も含めて、ここ忘れがちなので忘れないように。
Would you like a tiny, small, medium, or large dog? Are you interested in tiny, small, medium, or large dogs?
すると、"Edit in APL Audio"のリンクがクリックできるようになってるのがわかるでしょうか?クリックしてみてください。
APL-Aのオーサリングツールが開いて、先程入力したレスポンスがすべてAPL-Aテンプレートになっていますね。このようにAlexa Conversationsのレスポンスは基本的にAPL-Aで提供されているようです。
ちなみに、レスポンスを1つだけ登録しておいて、あとはAPL-Aのテンプレートを直接編集する、ということもできます。temperamentでやってみましょう。"request_temperament"を追加して、以下のレスポンスを登録してください。
Would you like a family dog or a guard dog?
"Edit in APL Audio"をクリックして、オーサリングツールを開きます。
先程登録したレスポンスが1つだけAPL−Aテンプレート内にあると思います。
以下のJSONでまるっと上書きします。最後に保存。
{ "type": "APLA", "version": "0.8", "mainTemplate": { "parameters": [ "payload" ], "item": { "type": "Selector", "strategy": "randomItem", "description": "Change 'type' above to try different Selector Component Types like Sequencer", "items": [ { "type": "Speech", "contentType": "text", "content": "Would you like a family dog or a guard dog?", "description": "Expand on 'items' array to add multiple prompts, use response template arguments by adding it to 'content' like this ${payload.input_argument_name} and add SSML by changing 'contentType' to 'SSML' and adding SSML to 'content' like that" }, { "type": "Speech", "contentType": "text", "content": "I can filter for a family dog or a guard dog. Which do you prefer?", "description": "Expand on 'items' array to add multiple prompts, use response template arguments by adding it to 'content' like this ${payload.input_argument_name} and add SSML by changing 'contentType' to 'SSML' and adding SSML to 'content' like that" } ] } } }
Responsesメニューの方に戻って一度リロードしてみてください。先程登録したレスポンスが見えなくなっていますよね。
このようにAPL-Aテンプレートを直接編集した場合は、responsesのメニュー画面からは編集できなくなり、オーサリングツールでAPL-Aテンプレートを編集するしかなくなるようです。お好み次第ですが、ここは少し注意したほうがいいかと思います。
ちなみに予め用意してあるビルトインのレスポンステンプレートも、デフォルトだとオーサリングツールでしか設定できないようになっています。一度削除して作り直せばresponsesの画面でレスポンスを登録できますが、welcomeだけは特殊でオーサリングツールのみとなっているようです。(
続けてenergyのレスポンステンプレートも登録しましょう。"request_energy"というレスポンステンプレート名で、以下のレスポンスを登録してください。
Would you prefer a high energy dog or a low energy dog? Do your friends describe you as high energy or low energy?
これで、引数が足りない場合の追加レスポンスのテンプレートができました。最後に、引数が全部揃った後にAPIからの結果を返すレスポンステンプレートを作成します。
"Add response" で新しいレスポンステンプレートを追加しましょう。テンプレート名は"getRecommendation" APIが成功した結果を通知するので"notifySuccess_getRecommendation" という名前にします。
で、実際にレスポンスはちょっと置いといて、一番下のArgumentsのところにAPIからの応答をレスポンステンプレートに組み込むための引数をセットします。ここがAPI定義の最後で作成した”getRecommendationResult”スロットタイプになります。スロット名も同じ"getRecommendationResult"にしておきましょう。
これを発話時のスロットと同じ要領でレスポンスに組み込みます。"Create new prompt"を選択、プロンプト名は"notifySuccess_getRecommendation"、レスポンスは以下を登録します。"Save"と"保存"をお忘れなく。
I recommend a ${getRecommendationResult.name}.
少し思い出してみましょう。"getRecommendation" APIからのレスポンスは以下のような”getRecommendationResult”という名前のcompound slotとして定義しました。
実際にAPIが返すのはこういう形です。
...snip... returnEntity: { "name": "Chihuahua", "size": "Small", "energy": "High", "temperament": "Guard" }
このreturnEntityがgetRecommendationResultに入り、getRecommendationResult.nameはreturnEntity.nameと同じなので、上記のレスポンステンプレートに"Chihuahua"が差し込まれて、"I recommend a Chihuahua."となるわけです。なぜAPIのレスポンスに「スロット」という表現を使うのか、そしてcompound slotのような構造化データの定義が必要なのか、というところがわかった気がしますよね。
これでAPI成功時のレスポンスも設定できたので、API定義"getRecommendation"を開いて、一つだけ設定していなかったレスポンステンプレートの部分にこの"notifySuccess_getRecommendation"を設定すればOKです。ここは選択したあとに”+”をクリックすることをお忘れなく。
"+"をクリックして以下のように下に表示されていればOKです。
そして最後に"Save"をお忘れなく。
これで、レスポンステンプレートもAPI定義も終わりました。
発話セット
ダイアログに付与するために登録しないといけない最後の情報は「発話セット」です。レスポンステンプレートがAlexaのレスポンスなら、発話セットはユーザのサンプル発話になります。ここはインテントベースのスキル開発でも普通にやっていたので親しみがあるかと思います。
で、ユーザのサンプル発話って「ダイアログ」作るときに登録しなかったっけ?と思いますよね。Alexa Conversationでは、ダイアログに登録した情報からユーザのサンプル発話のバリエーションを予測・生成するのはもちろんですが、より正確に予測するための材料として発話セットを登録するということになり、ここで登録したものはより直接的なサンプル発話として機能するようです。
こちらも改めてダイアログを見てみましょう。dialog0で説明します。
- dialog0
USER: I want a large family dog.
ALEXA: Do you want a high-energy or low-energy dog? USER: Low.
ALEXA: So you want a large low-energy family dog. I recommend a chihuahua.
この中にあるユーザの発話は、マルチターンに入るきっかけとなる最初の問いかけで、sizeとtemperamentを伝える、
USER: I want a large family dog.
と、足りない引数energyを追加で問い合わせた結果、energyを伝える、
USER: Low.
になります。
同様に他のダイアログもみてみると、こちらも
- マルチターンの会話を始めるトリガー(sizeだけ/energyだけ/temperamentだけ/sizeとenergy/sizeとtemperament/energyとtemperament/sizeとenergyとtemperamentの全部)
- トリガーの発話に足りない引数の追加要求に答える(sizeの追加/energyの追加/temperamentの追加)
という10個のパターンが有ることがわかります。ちなみに、最初に1つだけしか言わなかった場合に残り2つを追加要求するというパターンも考えられますが、今回は1回の追加確認で1つの追加スロット要求とするということで、割愛します。
で、発話セットにはもう一つ大事な概念があります。それが"ダイアログアクトです
ダイアログアクト
さきほど説明した10個の発話パターンですが、大きく分けると2つになります。
- マルチターンに入るきっかけとなる最初の問いかけ
USER: I want a large family dog.
- 足りない引数を追加で問い合わせて、結果を受け取る
USER: Low.
共に、APIを実行するというのがゴールです。その点を踏まえて、会話のフローをなぞらえるとこういう感じになります。
USER: I want a large family dog.
→ APIを要求しているということをAlexaが認識し、必要な引数などを確認、発話から取得する。引数が一つ足りない。
ALEXA: Do you want a high-energy or low-energy dog?
→ 足りない引数を取得するために、ユーザに発話を促すようAlexaが発話する。
USER: Low.
→ 足りない引数をユーザが発話する。
ALEXA: So you want a large low-energy family dog. I recommend a chihuahua.
→ API実行に必要な引数が揃ったので、APIに投げて、その結果を踏まえて応答する。
つまり、ユーザの最初の発話と、ユーザの2つ目の発話には違いがあるのですね。この違いをダイアログアクトで指定することになります。今回のチュートリアルで扱われているユーザの発話の振る舞いを示すダイアログアクトは以下の2種類です。
これを踏まえて発話セットを作成してみます。
まず最初に、マルチターンに入るきっかけとなる"Invoke APIs"の発話セットを作成します。スロットの組み合わせにより以下の7通りを作成します。
- サイズ、気質、元気さのすべてが含まれた発話
- サイズのみが含まれた発話
- 気質のみが含まれた発話
- 元気さのみが含まれた発話
- サイズと気質が含まれた発話
- サイズと元気さが含まれた発話
- 気質と元気さが含まれた発話
サンプルとして、「1. サイズ、気質、元気さのすべてが含まれた発話」をやってみましょう。
Alexa Conversationsの"Utterance Sets"メニューをクリックして、"Add Utternace Set"をクリックします。
発話セット名は"invoke_getRecommendation_size_temperament_energy"とします。登録する数が多いのでこのあたりの命名規則は揃えておいたほうがいいですね。ダイアログアクトは"Invoke APIs"を選択します。
この発話におけるゴールとなるAPIを指定します。"getRecommendation"を選択します。
画面下部の"Add Slot"から、ユーザのサンプル発話に含まれるスロットを追加していきます。
発話セットが「1. サイズ、気質、元気さのすべてが含まれた発話」なので、size、temperament、energyの3つを登録します。
ではサンプル発話を登録しましょう。このあたりはこれまでと変わらないですね。
I want a {size} {temperament} {energy} dog. I want a {size} {energy} {temperament} dog. I want a {energy} {temperament} {size} dog. I want a {energy} {size} {temperament} dog. I want a {temperament} {size} {energy} dog. I want a {energy} {size} {temperament} dog.
最後に"Save"で1つ目の発話セットの登録は完了です。
同様にして、"Invoke APIs"タイプの発話セットの残りも登録していきます。
2 | 発話セット名 | invoke_getRecommendation_size |
---|---|---|
ダイアログアクト | Invoke APIs | |
API名 | getRecommendation | |
スロット名(スロットタイプ) | size (size) | |
サンプル発話 | I want a {size} dog. |
3 | 発話セット名 | invoke_getRecommendation_temperament |
---|---|---|
ダイアログアクト | Invoke APIs | |
API名 | getRecommendation | |
スロット名(スロットタイプ) | temperament (temperament) | |
サンプル発話 | I want a {temperament} dog. |
4 | 発話セット名 | invoke_getRecommendation_energy |
---|---|---|
ダイアログアクト | Invoke APIs | |
API名 | getRecommendation | |
スロット名(スロットタイプ) | energy (energy) | |
サンプル発話 | I want a {energy} dog. |
5 | 発話セット名 | invoke_getRecommendation_size_temperament |
---|---|---|
ダイアログアクト | Invoke APIs | |
API名 | getRecommendation | |
スロット名(スロットタイプ) | size (size) temperament (temperament) |
|
サンプル発話 | I want a {size} {temperament} dog. I want a {temperament} {size} dog. |
5 | 発話セット名 | invoke_getRecommendation_size_energy |
---|---|---|
ダイアログアクト | Invoke APIs | |
API名 | getRecommendation | |
スロット名(スロットタイプ) | size (size) energy (energy) |
|
サンプル発話 | I want a {size} {energy} dog. I want a {energy} {size} dog. |
5 | 発話セット名 | invoke_getRecommendation_temperament_energy |
---|---|---|
ダイアログアクト | Invoke APIs | |
API名 | getRecommendation | |
スロット名(スロットタイプ) | temperament (temperament) energy (energy) |
|
サンプル発話 | I want a {temperament} {energy} dog. I want a {energy} {temperament} dog. |
次に、スロットが揃ってない場合に追加で確認をした場合のユーザの発話を受け取る発話セットです。こちらのダイアログアクトは "Inform Arguments"となります。
こちらも以下の3パターンの登録が必要です。
- サイズが含まれた発話
- 気質が含まれた発話
- 元気さのみが含まれた発話 "" 一応違いを確かめるために「1. サイズが含まれた発話」でやってみましょう。"Utterance Sets"メニューから"Add Utterance Set"で発話セットを追加します。
こちらは"Inform Arguments"タイプなので、発話セット名は"Inform_getRecommendation_size"にしましょう。ダイアログアクトは"Inform Arguments"で。
"Inform Arguments"の場合はAPIの指定は不要です。"Invoke APIs"を受けての"Inform Arguments"になるので、もうAPIがわかっているからですね。
スロットを登録します。「1. サイズが含まれた発話」なのでもちろん"size"で。
サンプル発話を登録します。ここはシンプルにサイズだけを答える感じにしましょう。最後に"Save"を忘れないように。
{size}.
続けて、残り2つの発話セットも登録してください。
2 | 発話セット名 | inform_getRecommendation_temperament |
---|---|---|
ダイアログアクト | Inform Arguments | |
スロット名(スロットタイプ) | temperament (temperament) | |
サンプル発話 | {temperament}. |
3 | 発話セット名 | inform_getRecommendation_energy |
---|---|---|
ダイアログアクト | Inform Arguments | |
スロット名(スロットタイプ) | energy (energy) | |
サンプル発話 | {energy}. |
はい、これで発話セットの登録も完了しましたので、次はいよいよダイアログとの紐付けを行っていきます。
ダイアログと各コンポーネントの紐付け(アノテーション)
これでAlexa Conversationsの5つのコンポーネントが揃いました。ではこれをダイアログに紐づけ、アノテーションを行っていきます。
Dialogsメニューを開いて、dialog0の「編集」をクリックします。
ダイアログを1行づつ見ていきましょう。1行目はマルチターンの会話のきっかけとなるユーザの発話ですね。まずはスロットの設定です。"large"を選択してください。
"large"はスロットタイプ"size"なので、Entity Typeに"size"を選択します。変数名は勝手に振られるので気にしなくて良いです。"Add"でスロットとして設定します。
できました。同じように"family"もスロットタイプ"temperament"として設定します。
次に右にメニューが出てきてると思います。この発話をどのダイアログアクトとして設定するか?を設定します。発話セットのところでやりましたよね。APIを実行するためのマルチターンのトリガーとなる最初の発話は"Invoke APIs"になりますので、これを選択します。
次にこの発話例と同じサンプル発話の発話セットを選択します。"Invoke APIs"で"size"と"temperament"を受けるための発話セットになるので"invoke_getRecommendation_size_temperament"を選択します。
次にスロットを変数にマッピングします。ここで変数にマッピングしておくことで、このマルチターンのセッションの中で参照できるようになるわけですね。変数名はスロット名から自動で生成されますので、同じ名前のものをマッピングします。
ダイアログを見ると、"Invoke"と表示され、「!」マークになっていたのが「✓」になりましたよね。これで1行目のユーザの発話のアノテーション設定は終わりです。
2行目はAlexaの発話です。ダイアログアクトはユーザからの発話だけでなくAlexaからの応答にも設定する必要がありますが、この場合選べるダイアログアクトが変わります。ここでは、ユーザに足りない引数を要求するための発話なので"Request Arguments"を選択します。
次に引数が揃ったらリクエストを送るAPIを指定します。"getRecommendation"を選択します。
APIを指定すると自動的に必要な引数が全部入力されますが、sizeとtemperamentはすでに最初の発話で取得できています。ここでは足りないenergyだけを指定するようにすればよいようです。sizeとtemperamentの"x"をクリックして削除します。
次にレスポンステンプレートの設定です。ダイアログにすでにAlexaが返す発話内容は記載されていますが、これだけではなくレスポンステンプレートからも発話されるようになっています。ここではenergyを追加確認する"request_energy"を選択します。
はい、2行目もチェックマークに変わりましたのでこれでOKです。
3行目です。ユーザが足りない引数energyを返してきましたので、スロットとして受け取ります。"Low" を選択して、スロットタイプ"energy"で設定します。
ダイアログアクトの設定です。発話セットでやりましたよね。ユーザから追加で必要な引数が伝えられる場合のダイアログアクトは"Inform Arguments"になります。
そして発話セットは"inform_getRecommendation_energy"ですね。
1行目と同じようにスロットenergyを変数にマッピングします。これで3行目の設定も終わりです。
そして最後の4行目。APIの結果をAlexaがユーザに返します。API成功時のダイアログアクトは"Nofity Success"を設定します。
APIs to callにAPI定義をセットします。もちろん"getRecommendation"ですね。
APIに渡す引数を設定します。ここまでのダイアログでスロットを設定したときに変数名が自動的に生成(スロット名0とかですね)されていたのを覚えているでしょうか。ここで使うんですね。それぞれの引数にマッチする変数をセットします。
その下にある"ReturnType- > Variable"はAPIの結果をどう受け取るかですね。"getRecommendationResult"スロットタイプで受け取った結果を変数"getRecommendationResult0"に入れるという感じです。ここは自動でセットされるみたいです。
最後にレスポンステンプレートでAlexaの発話をセットします。"notifySuccess_getRecommendation"をセットしてください。
で、今度はどの変数をレスポンステンプレートに埋め込むか、になります。ここは"ReturnType- > Variable"で指定されていた変数"getRecommendationResult0"に結果が入っているのでそれを"getRecommendationResult"スロットタイプとしてレスポンステンプレートに渡す、という感じですね。"getRecommendationResult0"を選択してください。
すべてチェックが付きましたね。これでこのダイアログのアノテーション設定は終わりです。
同様にして他のダイアログも設定を行っていきます。
ここはちょっと項目がおおいのでキャプチャのみでご了承ください。
はい、これでAlexa Conversationsを使用した対話モデルの作成は完了しました。"モデルをビルド"をクリックしてください。
AlexaConversationsは設定項目が非常に多い上、ちょっと保存を忘れがちなところもあるかなーと思います。モデルの設定内容に不備があると、こんな感じでエラーが出ます。適宜修正して再度ビルドしましょう。
そして、今回このビルドが非常に長いです・・・サンプル発話の自動生成などが行われているようなのでしょうがないのかもしれませんが、最低でも数分かかります。コーヒーでも飲んで休憩してください。
バックエンド
ではバックエンドです。APIで定義していたgetRecommendation APIを実装します。
今回はAlexa Hostedなので、コードエディタを開いてください。
まず、犬種のおすすめデータをまとめたJSONファイルを作成します。左上の「ファイルを作成」ボタンをクリックして、"PetMatch.json"というファイル名で作成します。
PetMatch.jsonファイルが開いたら、以下のURL先の内容をすべてコピー&ペーストして、「保存」します。
少しだけデータを見てみましょう。
{ "high-large-family": { "size": "Large", "energy": "High", "SSET": "high-large-family", "temperament": "Family", "description": "The Weimaraner is a large dog that was originally bred for hunting game such as deer, bear, and boar in 19th century Germany. They are high-energy dogs that need a lot of exercise and play time. Weimaraners come in shades of charcoal blue to silver grey and can weigh from 55 to 88 lbs.", "breed": "Weimaraner" },
ユーザの発話をもとにAPIに投げられた3つの引数(energy/size/temperament)からオブジェクトのキー"{energy}-{size}-{temperament}"を生成して、該当のオブジェクトのデータを引っ張って来るという感じですね。breedが犬種になります。"description"は今回は使いませんが、別の回で紹介します。
次に、index.jsです。
これも以下のURL先の内容をすべてコピペして「保存」「デプロイ」します。
これで終わりなのですが、少しだけコードも見てみましょう。まず先程のPetMatch.jsonを読み込んでます。
const Alexa = require('ask-sdk-core'); const data = require('./PetMatch.json');
必要なリクエストハンドラーは実質的にGetRecommendationAPIHandler
だけです。最初にレスポンステンプレートのところでお伝えしたとおり、LaunchRequestとかヘルプ/キャンセル/ストップインテントのハンドラは、Alexa Conversationsでdefault dialog managerを選択した場合は不要になるようです。
const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
.addRequestInterceptors(RequestInterceptor)
.addRequestHandlers(
GetRecommendationAPIHandler,
IntentReflectorHandler,
SessionEndedRequestHandler
)
.addResponseInterceptors(ResponseInterceptor)
.addErrorHandlers(ErrorHandler)
.lambda();
ではメインとなるGetRecommendationAPIHandlerです。
まずcanHandleのところ。AlexaConversationsではリクエストタイプが"Dialog.API.Invoked"という、これまでとは違うリクエストタイプとなり、apiRequest.nameでAPI名が渡ってきます。
const GetRecommendationAPIHandler = { canHandle(handlerInput) { return Alexa.getRequestType(handlerInput.requestEnvelope) === 'Dialog.API.Invoked' && handlerInput.requestEnvelope.request.apiRequest.name === 'getRecommendation'; },
handleのところでは、
- apiRequestにスロット値が入っているのでresolveEntityでこれを取り出す
- スロットからキーを作成してPetMatch.jsonのデータを参照
- 取り出したデータをrecommendationResultというオブジェクトに入れる
- buildSuccessApiResponseにこのオブジェクトをレスポンスとして返す
という感じです。
handle(handlerInput) { const apiRequest = handlerInput.requestEnvelope.request.apiRequest; let energy = resolveEntity(apiRequest.slots, "energy"); let size = resolveEntity(apiRequest.slots, "size"); let temperament = resolveEntity(apiRequest.slots, "temperament"); const recommendationResult = {}; if (energy !== null && size !== null && temperament !== null) { const key = `${energy}-${size}-${temperament}`; const databaseResponse = data[key]; console.log("Response from mock database ", databaseResponse); recommendationResult.name = databaseResponse.breed; recommendationResult.size = apiRequest.arguments.size recommendationResult.energy = apiRequest.arguments.energy recommendationResult.temperament = apiRequest.arguments.temperament; } const response = buildSuccessApiResponse(recommendationResult); console.log('GetRecommendationAPIHandler', JSON.stringify(response)); return response; } };
resolveEntityとbuildSuccessApiResponseはask-sdkの関数ではなく下の方で定義してあります。
esolveEntityはスロットを取り出す場合にシノニムではなく標準的なスロット値を取り出すものですね。 buildSuccessApiResponseは、recommendationResultオブジェクトをapiResponseの中に放り込んだだけです。
const resolveEntity = function(resolvedEntity, slotName) { let erAuthorityResolution = resolvedEntity[slotName].resolutions .resolutionsPerAuthority[0]; let value = null; if (erAuthorityResolution.status.code === 'ER_SUCCESS_MATCH') { value = erAuthorityResolution.values[0].value.name; } return value; }; const buildSuccessApiResponse = (returnEntity) => { return { apiResponse: returnEntity }; };
なのでほんと至ってシンプルなバックエンドコードになっています。speech textを返す必要もありません。たしかにこれなら既存スキルへの組み込みも楽かもしれません。
テスト
対話モデルのビルド、バックエンドコードのデプロイが終わりましたので、いよいよテストです。テストシミュレータで試してみましょう。
1問1答形式でちゃんと動きましたね。次に3つの要素が一度に渡された場合です。
はい、こちらも動きました。最後にちょっと意地悪なやつを・・・
マルチターンの最中にヘルプ呼び出しても、きちんとその前の状態覚えていて会話が続けられます。セッションアトリビュート、特にバックエンドで実装してなくてもできてますね。あと、発話セットに入れていないものでもうまく解釈してくれてたりします(ただもちろん100%ではないです)。
まとめ
いかがでしょうか、Alexa Liveのときは細かいところまでわからなかったのですが、実際にチュートリアルをやってみると理解が進みやすい気がしますね。まだベータなので今後どうなるかはわかりませんが、将来的にこれが主流になる可能性もありますので、その一端は見れたかなと思いました。
その他、いくつか感じたことをまとめておきます。
- バックエンドコードはほんとうに少なくて済みますし、APIとして書くだけなので処理としてはとてもスッキリします。
- その分、対話モデルのところは結構設定項目多いですし、UI的に保存忘れたり設定漏れたりみたいなのは起きやすいので、ちょっと注意が必要です。
- 軽く試してみた限りでは認識率はまあ良さそうな感じはしてます。もちろん銀の弾丸というわけではないと思いますが。
- 新しいものなので、最初にコンセプトを理解してそれを落とし込むところに時間がかかるのは致し方ないかなと思いますが、慣れの問題かなという気もします。既存スキルの一部機能から実装していくみたいなのも考慮されてるみたいなので、比較的入りやすいんじゃないかと思います。
- ただ、これまでのインテントベースの対話モデルでやってきた対話のノウハウのようなものをAlexa Conversationsでどう落とし込んでいくのかはまだちょっとわからないですね。元々の設計とかスキルの方向性によっては大きく見直さないといけない、とかそもそも要らない、というケースもあるんじゃないかな。
- ビルドに時間がかかる、UIちょっとまだこなれてない感がある、GUIでしか設定できない様子、対話モデルをエクスポートするような機能が見当たらない、など、まだ今後改善されるべきところはあります。
さて、チュートリアルはまだもう少し続きます。次回はAlexa Conversationsの中でも気になっていた"User correction"をやってみたいと思います。