ダイアログモデルを使用すると、マルチターンの会話スキルを簡単に開発できます。 1つ以上のスロットで、このインテントを完了させるためにこのスロットは必須ですか? を選択すると、Alexa音声サービスはダイアログの状態と、 まだ収集されていない必須スロットを常に追跡し、この情報をバックエンドに送信します。各ターンで、スキルは dialog ディレクティブを返してAlexaの返事を決定できます。 Dialog.Delegate ディレクティブを返すと、Alexa音声サービスは自動的に次のスロットの入力を促します。 Dialog.ElicitSlot ディレクティブを返すと、Alexaが次に促すスロットを決定できます。 Dialog.ConfirmSlot ディレクティブを返すと、ユーザーにスロット値を確認できます。
この技術記事では、ダイアログモデルを使用して、前の会話に基づいて動的にスロットを引き出す方法というブログで紹介したコーヒーショップスキルを元に作成していきます。
コーヒーショップスキルでは、ユーザーがコーヒーまたはお茶を注文できます。 ユーザーが選択した飲み物(drink)に基づいて、スキルはコーヒーのローストの種類(coffeeRoast)またはお茶の種類(teaType)を尋ねます。 前回の記事では、Dialog.ElicitSlotディレクティブを使用して、drinkに基づいて動的にcoffeeRoastスロットとteaTypeスロットを引き出す方法を順を追って説明しました。
コーヒーにフレーバーを追加する機能を追加してみましょう。 コーヒーショップでは、フレーバー(flavor)を追加するには50円の追加料金がかかります。 そのため、Dialog.ConfirmSlotディレクティブを使用して、ユーザーに追加料金がかかってもいいかどうかを尋ねます。 まずは、drinkがコーヒーの場合にflavorを勧め、Dialog.ElicitSlotを使用して動的に新しいflavorを引き出します。
次に、Dialog.ConfirmSlotを使用してスロットを確認します。では、ユーザーがflavorを追加しない場合はどうでしょうか。 flavorスロット値の1つとして「いりません」を追加しておき、ユーザーがflavorsを選択した場合にのみDialog.ConfirmSlotディレクティブを返します。
次の2つを行うことになります。
最後に、ユーザーがflavorを追加するための追加料金を確認するか拒否するかによって、次に行うことを決めます。
手順を追って説明しましょう。
最初は対話モデルの更新です。この時点で、コーヒーショップスキルにはdrink、coffeeRoast、teaTypeという3つのカスタムスロットがあります。 値は次のとおりです。
drink | coffeeRoast | teaType |
---|---|---|
コーヒー | ライト | 紅 |
茶 | ミディアム | 緑 |
ミディアムダーク | 白 | |
ダーク | ウーロン |
スキルとのやり取りで、ユーザーは次のように注文すると考えられます。
注文をお願い
{drink} をください
{drink} を飲みたい
{coffeeRoast} {drink} をちょうだい
{drink} がいい
{teaType} {drink} をください
{teaType} {drink} にしようかな
{drink} をお願い
ユーザーが追加したいフレーバーを指定するには、発話を更新して新しいカスタムスロットを追加する必要があります。たとえば、次のように言うかもしれません。
ミディアムローストのコーヒーをバニラ風味で
ヘーゼルナッツ入りのコーヒーを飲みたい
コーヒーにキャラメルをワンプッシュ入れて
バニラ入りコーヒー
このような発話では、flavorという新しいスロットを使用します。drink、coffeeRoast、flavorの各スロットを使用して、入力したい値をスロットで置き換えます。
{coffeeRoast} {drink} を {flavor} 風味で
{flavor} 入りの {drink} を飲みたい
{drink} に {flavor} をワンプッシュ入れて
{flavor} 入り {drink}
次の表に、flavorスロットのカスタム値を示します。
フレーバー |
---|
いりません |
バニラ |
ヘーゼルナッツ |
キャラメル |
いりませんという値を使って、drinkがコーヒーの場合にDialog.ElicitSlotディレクティブで入力を促すflavorの追加を断ることができます。それ以外の値が指定された場合は、フレーバーの追加に50円の料金がかかることを了承するかどうかを尋ねます。
これで音声ユーザー対話モデルの変更方法を理解していただけたと思います。次はバックエンドコードを見てみましょう。
coffeeRoastスロットやteaTypeスロットと同様に、flavorスロットも動的です。drinkがcoffeeの場合にのみ、フレーバーを追加するかどうかをユーザーに尋ねます。Dialog.ElicitSlotを使用してflavorスロットを引き出し、Dialog.SlotConfirmationを使用して確認を引き出します。この場合、さらに2つのハンドラーを定義します。これらのハンドラーはスキルのさまざまな状況を表します。これらを使用して、flavorスロットを引き出し、確認します。ハンドラーは次のとおりです。
このハンドラーはDialog.ElicitSlotディレクティブを使用して、動的にflavorスロットを引き出します。canHandle関数は次の場合にtrueを返します。
たとえば、ユーザーが「ダークコーヒーをちょうだい」と言うと次のようになります。
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === "IntentRequest"
&& handlerInput.requestEnvelope.request.intent.name === "OrderIntent"
&& handlerInput.requestEnvelope.request.intent.slots.drink.value
&& handlerInput.requestEnvelope.request.intent.slots.drink.value === "コーヒー"
&& handlerInput.requestEnvelope.request.intent.slots.coffeeRoast.value
&& handlerInput.requestEnvelope.request.intent.slots.flavor.value
&& handlerInput.requestEnvelope.request.intent.slots.flavor.value !== "いりません"
&& handlerInput.requestEnvelope.request.intent.slots.flavor.confirmationStatus === "NONE";
},
その後ハンドラーはDialog.ElicitSlotディレクティブを使用して、flavorスロットを引き出します。
handle(handlerInput) {
return handlerInput.responseBuilder
.speak("コーヒーにフレーバーを追加できます。キャラメル、ヘーゼルナッツ、バニラから選べます。 不要の場合はいりませんと言ってください。")
.reprompt("コーヒーにどのフレーバーを追加しますか?キャラメル、ヘーゼルナッツ、バニラから選べます。 不要の場合はいりませんと言ってください。")
.addElicitSlotDirective("flavor")
.getResponse();
}
ハンドラー全体は次のようになります。
const CoffeeRoastGivenPromptFlavorOrderIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === "IntentRequest"
&& handlerInput.requestEnvelope.request.intent.name === "OrderIntent"
&& handlerInput.requestEnvelope.request.intent.slots.drink.value
&& handlerInput.requestEnvelope.request.intent.slots.drink.value === "コーヒー"
&& handlerInput.requestEnvelope.request.intent.slots.coffeeRoast.value
&& !handlerInput.requestEnvelope.request.intent.slots.flavor.value;
},
handle(handlerInput) {
return handlerInput.responseBuilder
.speak("コーヒーにフレーバーを追加できます。キャラメル、ヘーゼルナッツ、バニラから選べます。 不要の場合はいりませんと言ってください。")
.reprompt("コーヒーにどのフレーバーを追加しますか?キャラメル、ヘーゼルナッツ、バニラから選べます。 不要の場合はいりませんと言ってください。")
.addElicitSlotDirective("flavor")
.getResponse();
}
};
コーヒーが注文され、flavorスロットを動的に引き出すことができるようになったので、次のハンドラーに移りましょう。
FlavorGivenConfirmSlotOrderIntentHandlerではDialog.ConfirmSlotディレクティブを使用します。これのcanHandle関数は次の場合にtrueを返します。
この最後の条件、request.intent.slots.flavor.confirmationStatusがNONEに等しい、が非常に重要です。これがないと、スキルはflavorスロットの確認を永遠に続けるので、ユーザーは困ってしまいます。スロットが確認される前は、confirmationStatusはNONEです。ユーザーが答えるとDENIEDまたはCONFIRMEDのいずれかになります。したがって、flavorスロットを確認する前に、confirmationStatusをチェックする必要があります。
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === "IntentRequest"
&& handlerInput.requestEnvelope.request.intent.name === "OrderIntent"
&& handlerInput.requestEnvelope.request.intent.slots.drink.value
&& handlerInput.requestEnvelope.request.intent.slots.drink.value === "コーヒー"
&& handlerInput.requestEnvelope.request.intent.slots.coffeeRoast.value
&& handlerInput.requestEnvelope.request.intent.slots.flavor.value
&& handlerInput.requestEnvelope.request.intent.slots.flavor.value !== "いりません"
&& handlerInput.requestEnvelope.request.intent.slots.flavor.confirmationStatus === "NONE";
},
handle関数は確認プロンプトを作成し、Dialog.ConfirmSlotディレクティブを返します。
handle(handlerInput) {
const flavor = handlerInput.requestEnvelope.request.intent.slots.flavor.value;
const speechText = `${flavor}を追加すると50円の追加料金がかかります。追加してよろしいですか?`;
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.addConfirmSlotDirective("flavor")
.getResponse();
}
全体を見るとハンドラーのcanHandle関数とhandle関数がどのように連携しているかがわかります。
const FlavorGivenConfirmSlotOrderIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === "IntentRequest"
&& handlerInput.requestEnvelope.request.intent.name === "OrderIntent"
&& handlerInput.requestEnvelope.request.intent.slots.drink.value
&& handlerInput.requestEnvelope.request.intent.slots.drink.value === "コーヒー"
&& handlerInput.requestEnvelope.request.intent.slots.coffeeRoast.value
&& handlerInput.requestEnvelope.request.intent.slots.flavor.value
&& handlerInput.requestEnvelope.request.intent.slots.flavor.value !== "いりません"
&& handlerInput.requestEnvelope.request.intent.slots.flavor.confirmationStatus === "NONE";
},
handle(handlerInput) {
const flavor = handlerInput.requestEnvelope.request.intent.slots.flavor.value;
const speechText = `${flavor}を追加すると50円の追加料金がかかります。追加してよろしいですか?`;
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.addConfirmSlotDirective("flavor")
.getResponse();
}
};
スロットが確認または拒否された後に何をするかは自由に決めてください。ここではこれ以上スロットを収集する必要がなく、FlavorGivenConfirmSlotOrderIntentHandlerはコーヒー関連のスロットがすべて収集された後にのみ発生するため、CompletedOrderIntentHandlerで確認後の処理をしましょう。
このハンドラーは、ダイアログが完了した後に実行されます。canHandleは次の場合にtrueを返します。
canHandleは3つのシンプルなチェックに変換されます。
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === "IntentRequest"
&& handlerInput.requestEnvelope.request.intent.name === "OrderIntent"
&& handlerInput.requestEnvelope.request.dialogState === "COMPLETED";
},
handle関数には、confirmationStatusを処理するロジックが含まれています。drinkがコーヒーの場合、ロジックはconfirmationStatusをチェックします。DENIEDであれば、注文にフレーバーを付けないことをユーザーに知らせます。CONFIRMEDであれば、いいですね、と言います。この時点でサンプルスキルは終了しますが、本格的なコーヒーショップスキルを開発する場合は、このロジックに続けてショッピングカートにアイテムを追加し、「支払いに進みますか?別のアイテムをショッピングカートに追加しますか?」と尋ねることになります。
handle(handlerInput) {
const slots = handlerInput.requestEnvelope.request.intent.slots;
const drink = slots.drink.value;
let type = '';
let speechText = "いいですね。";
if (drink === 'コーヒー') {
type = slots.coffeeRoast.value;
if(slots.flavor.confirmationStatus === "DENIED") {
speechText = `わかりました。注文に${slots.flavor.value}は付けません。`;
}
else if (slots.flavor.confirmationStatus === "CONFIRMED") {
speechText = `おいしそうですね。${slots.flavor.value} は大好きです。`;
}
} else if (drink === '茶') {
type = handlerInput.requestEnvelope.request.intent.slots.teaType.value;
}
speechText += `${type}${drink}をショッピングカートに追加します。`;
return handlerInput.responseBuilder
.speak(speechText)
.getResponse();
}
ハンドラー全体は次のようになります。
const CompletedOrderIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === "IntentRequest"
&& handlerInput.requestEnvelope.request.intent.name === "OrderIntent"
&& handlerInput.requestEnvelope.request.dialogState === "COMPLETED";
},
handle(handlerInput) {
const slots = handlerInput.requestEnvelope.request.intent.slots;
const drink = slots.drink.value;
let type;
let speechText = "いいですね。";
if (drink === 'コーヒー') {
type = slots.coffeeRoast.value;
if(slots.flavor.confirmationStatus === "DENIED") {
speechText = `わかりました。注文に${slots.flavor.value}は付けません。`;
}
else if (slots.flavor.confirmationStatus === "CONFIRMED") {
speechText = `おいしそうですね。${slots.flavor.value} は大好きです。`;
}
} else if (drink === '茶') {
type = handlerInput.requestEnvelope.request.intent.slots.teaType.value;
} else {
type = '水';
}
speechText += `${type}${drink}をショッピングカートに追加します。`;
return handlerInput.responseBuilder
.speak(speechText)
.getResponse();
}
}
では、ユーザーがdrinkに対して有効な選択を指定しなかった場合、どうすればいいのでしょうか。水や野球と答えられたら、どうしますか? 次の記事では、ユーザーからの入力を検証する方法を説明します。それまでは、次のことについて考えてみてください。必要なハンドラーは何か? スロットを再度引き出す方法は? 必要なダイアログディレクティブは何か?
この記事を最後まで読んでいただき、ありがとうございました。このテクニックを応用して、ご自分のスキルで使ってみてください。オンラインのディスカッションも歓迎します。 Twitterのアカウントは@Tohimin8です。