【Alexaスキル開発】Dialogモデルを使って簡単スロット処理

この記事は公開されてから半年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

ブログ初投稿、開発部の辻です。
技術的なことをちょいちょい書いていければなぁと思っています。

最近Alexaスキルの開発を担当していますが
開発中はAmazon Echoと会話をすることが多く
テスト中には、隣の人から「またAlexaに怒ってる」という感想をいただきました。

目次

概要

スキルの中でユーザから情報を聞き出したいときはスロットを使用することになります。
正直、スロット値を集める処理は面倒大変です。
そこでDialogモデルを使用することで、スロット値が入力されるまでの処理をAlexaにおまかせすることができます。

従来の書き方

まず、Dialogモデルを使わないスキルを作成します。
今回は、サバ料理を注文するスキルを作っていきたいと思います。

「アレクサ、サバショップで味噌味のサバ料理を3つ」のように話しかける感じです。

スロットタイプの設定

サバ料理の味(Taste)をスロット値に入力します。
今回は

  • 味噌
  • 醤油

を用意しました。

数量を扱うためAMAZON.NUMBERもスロットタイプに追加しておきます

インテントの設定

サバ料理を注文するインテントを作成します。
名前はBuyMackerelFoodIntentにしました。

Lambdaのコード

const Alexa = require('ask-sdk-core');

const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
    },
    handle(handlerInput) {
        const speechOutput = 'いらっしゃいませ。何を注文しますか?';
        const reprompt = '味噌味、醤油味、塩味などがあります。何を注文しますか?';
        return handlerInput.responseBuilder
            .speak(speechOutput)
            .reprompt(reprompt)
            .getResponse();
    }
};

const BuyMackerelFoodIntentHandler = {
    canHandle(handlerInput) {
        return  handlerInput.requestEnvelope.request.type === 'IntentRequest'
            && handlerInput.requestEnvelope.request.intent.name === 'BuyMackerelFoodIntent';
    },
    handle(handlerInput) {
        let taste = handlerInput.requestEnvelope.request.intent.slots.taste.value;
        let amount = handlerInput.requestEnvelope.request.intent.slots.amount.value;
        let session = handlerInput.attributesManager.getSessionAttributes();

        /* 味を決める */
        if (taste === undefined && session.taste === undefined){
            return handlerInput.responseBuilder
                .speak('何味にしますか?')
                .reprompt('味噌、醤油、塩から選べます。何味にしますか?')
                .getResponse();
        } else {
            if (taste) {
                session.taste = taste;
                handlerInput.attributesManager.setSessionAttributes(session);
            } else {
                taste = session.taste;
            }
        }

        /* 個数を決める */
        if (amount === undefined && session.amount === undefined){
            return handlerInput.responseBuilder
                .speak('いくつにしますか?')
                .reprompt('何個必要ですか?')
                .getResponse();
        } else {
            if (amount) {
                session.amount = amount;
                handlerInput.attributesManager.setSessionAttributes(session);
            } else {
                amount = session.amount;
            }
        }

        const speechOutput = <code>${taste}味のサバ料理を${amount}つ承りました。</code>;
        return handlerInput.responseBuilder
            .speak(speechOutput)
            .getResponse();
    }
};

const ErrorHandler = {
    canHandle () {
        return true;
    },
    handle (handlerInput, error) {
        const message = &quot;すみません、うまく聞き取れませんでした。もう一度言ってください。&quot;;
        return handlerInput.responseBuilder
            .speak(message)
            .reprompt(message)
            .getResponse()
    }
};


const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
    .addRequestHandlers(
        LaunchRequestHandler,
        BuyMackerelFoodIntentHandler
    )
    .addErrorHandlers(ErrorHandler)
    .lambda();

スロット値が入力されているかを確認する処理なんかは、おなじような処理が並んでますね。

テスト

こんな感じでテストしてみます。

無事注文できましたね

しかし、このコードだとスロット値が増えるとその分if-elseが増え見づらいコードとなってしまいます。
次にDialogモデルを使用した場合です。

Dialogモデルを使用する

Dialogモデルを使用するには一部設定を有効にする必要がありますので、やっていきたいと思います。

インテントスロット

こちらの画面からダイアログの編集画面に行きます。

スロット入力から「このインテントを完了させるために、このスロットは必須ですか?」を有効にし
Alexaがどのように尋ねるかと、ユーザがどのように返事をするかを定義します。

数量も同様に設定します。

今までプログラムに書いていたことを、Alexa側に書く感じですね。

Lambdaのコード

const Alexa = require('ask-sdk-core');

const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
    },
    handle(handlerInput) {
        const speechOutput = 'いらっしゃいませ。どの味を注文しますか?';
        const reprompt = '味噌味、醤油味、塩味などがあります。何を注文しますか?';
        return handlerInput.responseBuilder
            .speak(speechOutput)
            .reprompt(reprompt)
            .getResponse();
    }
};

const BuyMackerelFoodIntentHandler = {
    canHandle(handlerInput) {
        return  handlerInput.requestEnvelope.request.type === 'IntentRequest'
            &amp;amp;&amp;amp; handlerInput.requestEnvelope.request.intent.name === 'BuyMackerelFoodIntent';
    },
    handle(handlerInput) {
        let taste = handlerInput.requestEnvelope.request.intent.slots.taste.value;
        let amount = handlerInput.requestEnvelope.request.intent.slots.amount.value;

        let {dialogState} = handlerInput.requestEnvelope.request;

        if (dialogState !== 'COMPLETED') {
            return handlerInput.responseBuilder
                .addDelegateDirective()
                .getResponse();
        } else {
            const speechOutput = <code>${taste}味のサバ料理を${amount}つ承りました。</code>;
            return handlerInput.responseBuilder
                .speak(speechOutput)
                .getResponse();
        }
    }
};

const ErrorHandler = {
    canHandle () {
        return true;
    },
    handle (handlerInput, error) {
        const message = &quot;すみません、うまく聞き取れませんでした。もう一度言ってください。&quot;;
        return handlerInput.responseBuilder
            .speak(message)
            .reprompt(message)
            .getResponse()
    }
};


const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
    .addRequestHandlers(
        LaunchRequestHandler,
        BuyMackerelFoodIntentHandler
    )
    .addErrorHandlers(ErrorHandler)
    .lambda();

テスト

前回と同じようにテストします。

変更前と同じことができました!

結論

Dialogモデルを使用すると、AlexaはdialogStateをリクエストに追加します。
この値がCOMPLETEDになるまでAlexaにお任せができる感じです。

スロットが入力されたかどうかの処理はAlexa側でやってくれるため、プログラムで処理する必要がありません。
今まで自分で判定していた処理を減らすことができ、不具合の原因を減らすことができます。

スロットの数が2つ程度なら従来の方法でも対処できそうですが、4つとかになるとDialogモデルが効果的になると思いますよ!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

Time limit is exhausted. Please reload CAPTCHA.