実は、ダイアログ管理は私のお気に入りのAlexa Skills Kit機能の1つです。長い時間をかけていろいろな実験をしてきました。スキルの質を高めるコンテキスト切り替え(英語)や柔軟な条件付け(英語)といった便利なテクニックは、こうして開発できたのです。中には、Alexaイベントや@SleepyDeveloperを通して開発者から頂いた質問にインスパイアされたものもあります。Alexa Skills Kitチームでは日々、ダイアログ管理に新しい機能を追加し、更新を行っています。どれもユーザーとのやり取りを向上させるすばらしい機能ばかりです。今日はその中から、インテントチェーンをご紹介します。
インテントチェーンとは、スキルコードを編集することで、LaunchRequestなどの任意のインテントからダイアログ管理に移動できる機能のことです。対話モデルにダイアログモデルを含むカスタムインテントであれば、どのインテントでもチェーンを作成できます。ダイアログモデルがあるかどうかは、 必須スロットを定義したインテントが1つ以上あること、オートデリゲートを有効にしていることのどちらかで確認できます。どのインテントもチェーン元にはなれますが、チェーン先にはなれないインテントもあります。たとえば、LaunchRequestやすべてのビルトインインテント(Amazon.HelpIntentなど)はチェーン先にはできません。
今までは、ユーザー発話からダイアログ管理がトリガーされたら、スキルコードはDialogディレクティブだけを返すことができました。インテントチェーンがすばらしいのは、プログラムからスキルのダイアログ管理を有効にできる点です。
では、インテントチェーンを使わずにダイアログ管理を行う対話を考えてみましょう。ユーザーがコーヒーショップスキルに話しかけて、お茶かコーヒーを注文します。すべての会話をとり上げることはしませんが、LaunchRequestから、チェーン先のインテントであるOrderIntentに移るところを見ていきましょう。LaunchRequestでユーザーにあいさつをし、OrderIntentでダイアログ管理を使って注文を取ります。読みながら、どうすればこの会話をもっとシンプルにできるかを考えてみてください。
ユーザー
アレクサ、コーヒーショップを開いて。
ALEXA
コーヒーショップへようこそ。注文を始めるには「注文をお願い」と言ってください。
ユーザー
注文をお願い。
ALEXA
コーヒーとお茶のどちらにしますか?
ユーザー
コーヒー。
どうすればシンプルにできるかわかりましたか? たとえば、ユーザーが「注文をお願い」と言ってからスキルが注文を取り始めるのではなく、何も言われなくても注文を取り始めるというのはどうでしょうか? その場合、会話はこのようになります。
ユーザー
アレクサ、コーヒーショップを開いて。
ALEXA
コーヒーショップへようこそ。お茶とコーヒーのどちらにしますか?
ユーザー
コーヒー。
こちらのほうがずっとシンプルですね。 すぐに注文を取り始めることができます。さて、インテントチェーンの方法を紹介する前に、インテントチェーンのメリットを説明しましょう。
インテントチェーンを使わずに、LaunchRequestを更新して「コーヒーショップへようこそ。お茶とコーヒーのどちらにしますか?」と言わせることはできます。 しかしそれでは、ユーザーが答えたときにダイアログ管理が有効になっておらず、OrderIntentをトリガーするためには、ユーザーが何か言う必要があります。ダイアログ管理は会話をトラッキングし、発話があればすぐに処理を開始します。ダイアログ管理を使う大きなメリットは、精度が向上することです。Alexaサービスが、ユーザーが次に言うことや処理すべきインテントをもっと正確に予測できるようになります。この場合、ユーザーは「お茶」か「コーヒー」のどちらかを答える可能性が高く、OrderIntentでその答えを処理する必要があります。
また、ダイアログ管理を行うインテントから、さらにインテントチェーンを使って別のインテントに移ることもできます。たとえば、いくつかの情報を収集するインテントのセットを作ります。収集済みかそうでないかに応じて、セットに含まれるインテント間でチェーンを作成することもできます。さらに、オートデリゲートを使うカスタムインテントからのチェーンも可能です。では、インテントチェーンのメリットを理解できたところで、そのしくみについて見ていきましょう。
コーヒーショップスキルの例では、ユーザーが「アレクサ、コーヒーショップを開いて」と言うと、LaunchRequestからOrderIntentに移ります。LaunchRequestからダイアログをデリゲートしてチェーン先のインテント名を返すだけで、このインテントチェーンを作成できます。まずは、addDelegateDirective関数を使う方法を紹介します。
.addDelegateDirective({
name: 'OrderIntent',
confirmationStatus: 'NONE',
slots: {}
})
とても簡単ですね。 ここでは、addDelegateDirectiveを使って応答にDialog.Delegateディレクティブを追加し、チェーン先インテントであるOrderIntentのnameプロパティをセットしました。confirmationStatusは、インテントが確認されたか(CONFIRMED)、拒否されたか(DENIED)を表します。NONEの場合は、ユーザーにまだ確認を行っていないことを表します。slotsオブジェクトを使うと、プログラムからスロット値を設定できます。これは、コンテキスト切り替えから戻ったときに、収集したスロットを保存する場合に便利です。詳細については、Build for Context Switching: Don’t Forget Important Information When Switching Between Intents(英語)を参照してください。
上の方法で物足りない場合は、代わりにaddDirective()関数を使うこともできます。この方法では、ディレクティブtypeへのDialog.Delegateの設定、updatedIntentの定義、nameへのOrderIntentの設定をすべて手動で行う必要があります。
.addDirective({
type: 'Dialog.Delegate',
updatedIntent: {
name: 'OrderIntent',
confirmationStatus: 'NONE',
slots: {}
}
})
どちらを選んでもかまいませんが、記述するコードの量が少ないので、addDelegateDirectiveをお勧めします。ディレクティブの作成方法を理解できたところで、responseBuilderを使って応答全体を作成する方法を見ていきましょう。
return handlerInput.responseBuilder
.addDelegateDirective({
name: 'OrderIntent',
confirmationStatus: 'NONE',
slots: {}
})
.speak("コーヒーショップへようこそ。")
.getResponse();
addDelegateDirective関数はresponseBuilderに含まれています。responseBuilderは、スキルがAlexaに返す応答を作成します。speak関数はAlexaが何を言うかを定義します。speak関数に渡した引数を見てみましょう。
.speak("コーヒーショップへようこそ。")
何か足りないと思いませんか? そう、「お茶とコーヒーのどちらにしますか?」という質問がありません。 この質問を含めなかった理由はなぜでしょうか。 OrderIntentを定義したとき、coffeeスロットを必須にしましたからです。この場合、ダイアログモデルでプロンプトを定義する必要があります。Alexaはこのプロンプトを使ってスロットに入る情報をユーザーにたずねます。 LaunchRequestからDialog.Delegateに戻ると、Alexaはすぐにダイアログモデルを使って必須スロットのプロンプトを出します。
インテントチェーンで使えるDialogディレクティブはDialog.Delegateだけではありません。ElicitSlotディレクティブを使うと、Alexa音声サービスの次のプロンプトを指定できます。また、プログラムを使って任意のスロットを必須スロットに変換することもできます。
インテントチェーンはElicitSlotディレクティブに対応しています。このため、別のインテントに移る際に特定のスロットの情報を引き出すことができます。addElicitSlotDirectiveを使ったインテントチェーンの方法を見ていきましょう。
.addElicitSlotDirective('drink',
{
name: 'OrderIntent',
confirmationStatus: 'NONE',
slots: {}
})
addElicitSlotDirectiveに渡す最初のパラメーターは、情報を引き出すスロットの名前です。ここでは、drinkスロットの情報を引き出すことにします。addDelegateDirectiveの場合と同様に、チェーン先のインテント(OrderIntent)にnameを設定し、confirmationStatusをNONEに設定します。次に、同じことをaddDirectiveを使って行う方法を見ていきます。
.addDirective({
type: 'Dialog.ElicitSlot',
slotToElicit: 'drink',
updatedIntent: {
name: 'OrderIntent',
confirmationStatus: 'NONE'
}
})
この場合、ディレクティブを手動で定義しています。typeにDialog.ElicitSlotを、slotToElicitにdrinkを設定してから、nameとconfirmationStatusを定義してupdatedIntentにラップする必要があります。リクエストを手動で作成できる柔軟性は魅力的ですが、Dialog.Delegateの場合と同様に、ヘルパー関数を使うことをお勧めします。では、responseBuilder全体を見てみましょう。この場合、speak関数に何を渡しているかに注意して見てください。
return handlerInput.responseBuilder
.addElicitSlotDirective('drink', {
name: 'OrderIntent',
confirmationStatus: 'NONE',
slots: {}
})
.speak("コーヒーショップへようこそ。コーヒーとお茶のどちらにしますか?")
.reprompt("コーヒーとお茶のどちらにしますか?")
.getResponse();
Dialog.ElicitSlotディレクティブを使ってスキルチェーンを作成するときに、「お茶とコーヒーのどちらにしますか?」という質問を含めていることに気付きましたか? Dialog.Delegateとは異なり、Alexaは自動で質問をするのにダイアログモデルで定義したプロンプトを使いません。Dialog.ElicitSlotを使ってAlexaが次のプロンプトでたずねるスロットを指定する場合は、プロンプトも併せて定義する必要があります。質問を指定しない場合、Alexaは「コーヒーショップへようこそ」と言った後、マイクを一時的にオンにします。つまり、ユーザーに何もたずねないまま、ユーザーからの応答を待つことになります。数秒間気まずい沈黙が続いた後、スキルは再プロンプトとしてユーザーが設定したテキストを読み上げます。再プロンプトにも質問を定義していない場合は、またこの気まずい沈黙が発生し、その数秒後、セッションは終了します。これではユーザーは困ってしまいます。このため、インテントチェーンにDialog.ElicitSlotを使う場合は必ず質問を含めるようにしてください。一方、Dialog.Delegateを使う場合に質問を含めてしまうと、Alexaは最初の応答でも、ダイアログモデルで定義したプロンプトでも同じ質問を繰り返してしまいます。
ここまで紹介したように、インテントチェーンではユーザーとスキルとの会話を簡単にすることができます。さらに、精度が高まるというメリットもあります。コンテキストを設定することで、ユーザーが次に言うことや処理すべきインテントをAlexaが予測できるからです。
この記事が、スキルにどうインテントチェーンを組み込むかのヒントになれば幸いです。うまく行ったら、ぜひ教えてください。 私のTwitterアカウントは@SleepyDeveloperですので、連絡をお待ちしています。
リソース