お試し Alexa スキル開発

2019年3月14日

はじめに

お試しで、猫と犬の鳴き声を返す Alexa スキル「なきごえなあに」を作ってみる。

環境

  • macOS Mojave (10.14.3)
  • Safari 12.0.3
  • Amazon Echo Dot 第3世代

Amazon 開発者アカウントは作成済み。

台本

まずは台本を作る。

基本

ユーザー: 「なきごえなあに」を開いて。
Alexa: 動物の名前を言ってください。
ユーザー: 猫。
Alexa: にゃーん。
ユーザー: 犬。
Alexa: わん、わん。
ユーザー: もういいです。
Alexa: 了解です。(終了)

ヘルプ

ユーザー: 「なきごえなあに」を開いて。
Alexa: 動物の名前を言ってください。
ユーザー: ヘルプ。
Alexa: 鳴き声を知りたい動物の名前を言ってください。

エラー

ユーザー: 「なきごえなあに」を開いて。
Alexa: 動物の名前を言ってください。
ユーザー: サメ。
Alexa: サメー!、サメー! ...ごめんなさい、わかりません。

インテント

基本的なインテントは以下の通りである。

  • LaunchRequest
  • AnimalIntent
  • HelpIntent

入力発話

ユーザーが入力する発話は以下の通り。

インテント発話引数あり
AnimalIntent{animal}
AnimalIntent猫の鳴き声{animal} の鳴き声
AnimalIntent猫の鳴き声を教えて{animal} の鳴き声を教えて
AnimalIntent猫の鳴き声が知りたい{animal} の鳴き声が知りたい

出力発話

Alexa が出力する発話は以下の通り。

タイミング発話引数
起動時動物の名前を言ってください。
ヘルプ鳴き声を知りたい動物の名前を言ってください。
エラー%sー!、%sー!...ごめんなさい、わかりません。入力

作成手順

基本

  • Alexa 開発者コンソールで「なきごえなあに」スキルを新規作成 ("Alexa がホスト" を選択)。
  • "HelloWorldIntent" を削除し、"AnimalIntent" を作成。
  • "AnimalIntent" のサンプル発話に入力発話を追加。その際、animal スロットを作成。
  • スロットタイプ "ANIMAL" を追加。
  • スロットタイプ ANIMAL のスロット値に "猫"、"犬" を追加。
  • AnimalIntent のインテントスロット "animal" のスロットタイプを "ANIMAL" に設定。
  • [モデルをビルド] をクリック。
  • [コードエディタ] に移動。
  • LaunchRequestHandler の handle() を以下のように修正。
        handle(handlerInput) {
            const speechText = '動物の名前を言ってください。';
            return handlerInput.responseBuilder
                .speak(speechText)
                .reprompt(speechText)
                .getResponse();
        }
    
  • "HelloWorldIntent" を "AnimalIntent" に置換。(macOS の場合 Command+F の検索で置換できる)
  • AnimalIntentHandler の handle() を以下のように修正。
        handle(handlerInput) {
            let speechText = 'わかりません。'
            if(handlerInput.requestEnvelope.request.intent.slots.animal.value === '猫') {
                speechText = 'にゃーん。';
            }
            else if(handlerInput.requestEnvelope.request.intent.slots.animal.value === '犬') {
                speechText = 'わん、わん。';
            }
            return handlerInput.responseBuilder
                .speak(speechText)
                .reprompt(speechText)
                .getResponse();
        }
    
  • CancelAndStopIntentHandler の handle() を以下のように修正。
        handle(handlerInput) {
            const speechText = '了解です。';
            return handlerInput.responseBuilder
                .speak(speechText)
                .getResponse();
        }
    
  • [デプロイ] をクリック。
  • [テスト] に移動。
  • "非公開" となっているのを "開発中" に変更。
  • 基本の会話を再現できるか確認。

ヘルプ

  • [コードエディタ] に移動。
  • HelpIntentHandler の handle() を以下のように修正。
        handle(handlerInput) {
            const speechText = '鳴き声を知りたい動物の名前を言ってください。';
    
            return handlerInput.responseBuilder
                .speak(speechText)
                .reprompt(speechText)
                .getResponse();
        }
    
  • [テスト] でヘルプの会話を再現できるか確認。

エラー

  • [コードエディタ] に移動。
  • AnimalIntentHandler の handle() を以下のように修正。
        handle(handlerInput) {
            let speechText = '';
            if(handlerInput.requestEnvelope.request.intent.slots.animal.value === '猫') {
                speechText = 'にゃーん。';
            }
            else if(handlerInput.requestEnvelope.request.intent.slots.animal.value === '犬') {
                speechText = 'わん、わん。';
            }
            else {
                const name = handlerInput.requestEnvelope.request.intent.slots.animal.value;
                speechText = name + 'ー!、' + name + 'ー! <break time="1s"/>ごめんなさい、わかりません。';
            }
            return handlerInput.responseBuilder
                .speak(speechText)
                .reprompt(speechText)
                .getResponse();
        }
    

同義語

  • [ビルド] に移動。
  • スロットタイプ ANIMAL で、"猫" の同義語に "にゃんこ" を追加。
  • [モデルをビルド] をクリック。
  • [コードエディタ] に移動。
  • AnimalIntentHandler の handle() を以下のように修正。
        handle(handlerInput) {
            let speechText = '';
    
            const animal = handlerInput.requestEnvelope.request.intent.slots.animal;
            const resolutionsPerAuthority = animal.resolutions.resolutionsPerAuthority;
    
            if(resolutionsPerAuthority[0].status.code === 'ER_SUCCESS_MATCH') {
                const name = resolutionsPerAuthority[0].values[0].value.name;
                if(name === '猫') {
                    speechText = 'にゃーん。';
                }
                else if(name === '犬') {
                    speechText = 'わん、わん。';
                }
            }
            else {
                const name = animal.value;
                speechText = name + 'ー!、' + name + 'ー! <break time="1s"/>ごめんなさい、わかりません。';
            }
    
            return handlerInput.responseBuilder
                .speak(speechText)
                .reprompt(speechText)
                .getResponse();
        }
    

未定義

どのインテントにも引っかからない言葉はカスタムインテントに送るようになっているようで、値が未定義のものが来ることがある。

  • [コードエディタ] に移動。
  • AnimalIntentHandler の handle() を、たとえば以下のように修正。
        handle(handlerInput) {
            let speechText = '';
    
            const animal = handlerInput.requestEnvelope.request.intent.slots.animal;
    
            if(animal.value === undefined) {
                speechText = 'なんですか?';
            }
            else {
                const resolutionsPerAuthority = animal.resolutions.resolutionsPerAuthority;
    
                if(resolutionsPerAuthority[0].status.code === 'ER_SUCCESS_MATCH') {
                    const name = resolutionsPerAuthority[0].values[0].value.name;
                    if(name === '猫') {
                        speechText = 'にゃーん。';
                    }
                    else if(name === '犬') {
                        speechText = 'わん、わん。';
                    }
                }
                else {
                    const name = animal.value;
                    speechText = name + 'ー!、' + name + 'ー! <break time="1s"/>ごめんなさい、わかりません。';
                }
            }
    
            return handlerInput.responseBuilder
                .speak(speechText)
                .reprompt(speechText)
                .getResponse();
        }
    
  • テスト。
    ユーザー: 「なきごえなあに」を開いて。
    Alexa: 動物の名前を言ってください。
    ユーザー: 猫。
    Alexa: にゃーん。
    ユーザー: ありがとう。
    Alexa: なんですか?
    

「ありがとう」でスキルを終了する

「ありがとう」でスキルを終了したい。

  • 「ビルド」に移動。
  • スロットタイプ THANKS を追加し、スロット値に「ありがとう」を追加する。
  • AnimalIntent にサンプル発話「{thanks}」を追加する。thenks のスロットタイプを THANKS とする。
  • 「コードエディタ」に移動。
  • AnimalIntent の handle() に以下のように追記。
        handle(handlerInput) {
            let speechText = '';
    
            const thanks = handlerInput.requestEnvelope.request.intent.slots.thanks;
    
            if(thanks.value !== undefined) {
                const resolutionsPerAuthority = thanks.resolutions.resolutionsPerAuthority;
    
                if(resolutionsPerAuthority[0].status.code === 'ER_SUCCESS_MATCH') {
                    speechText = 'どういたしまして。';
                    return handlerInput.responseBuilder
                        .speak(speechText)
                        .getResponse();
                }
            }
    
            ...
    
  • テスト
    ユーザー: 「なきごえなあに」を開いて。
    Alexa: 動物の名前を言ってください。
    ユーザー: トマト。
    Alexa: なんですか?
    ユーザー: トマトの鳴き声。
    Alexa: トマト―!、トマト―!...ごめんなさい、わかりません。
    ユーザー: ありがとう。
    Alexa: どういたしまして。
    

※インテントを分けたほうがよいかと思ったが、たとえば ThanksIntent といったものを作成した場合、AnimalIntent のほうで処理したい不明な入力が ThanksIntent に流れてくることがあり、うまくいかなかった。

追加機能

繰り返し

ユーザー: 「なきごえなあに」を開いて。
Alexa: 動物の名前を言ってください。
ユーザー: 猫。
Alexa: にゃーん。
ユーザー: もう一回。
Alexa: にゃーん。

もう一度同じ内容を発話させるには、組み込みの AMAZON.RepeatIntent を用いる。

  • [ビルド] のインテントの追加で "Alexa のビルトインライブラリから既存のインテントを使用" を選んで "AMAZON.ReleatIntent" を選ぶ。
  • [コードエディタ] で、AnimalIntentHandler の handle() に追記。
                if(resolutionsPerAuthority[0].status.code === 'ER_SUCCESS_MATCH') {
                    const name = resolutionsPerAuthority[0].values[0].value.name;
                    if(name === '猫') {
                        speechText = 'にゃーん。';
                    }
                    else if(name === '犬') {
                        speechText = 'わん、わん。';
                    }
    
                    // 追加
                    let attributes = handlerInput.attributesManager.getSessionAttributes();
                    attributes.speechText = speechText;
                    handlerInput.attributesManager.setSessionAttributes(attributes);
                }
    
  • RepeatIntentHandler を追加。
    const RepeatIntentHandler = {
        canHandle(handlerInput) {
            return handlerInput.requestEnvelope.request.type === 'IntentRequest'
                && handlerInput.requestEnvelope.request.intent.name === 'AMAZON.RepeatIntent';
        },
        handle(handlerInput) {
            let attributes = handlerInput.attributesManager.getSessionAttributes();
            const speechText = attributes.speechText;
            return handlerInput.responseBuilder
                .speak(speechText)
                .reprompt(speechText)
                .getResponse();
        }
    };
    
  • RepeatIntentHander を登録。
    exports.handler = Alexa.SkillBuilders.custom()
        .addRequestHandlers(
            LaunchRequestHandler,
            AnimalIntentHandler,
            RepeatIntentHandler, // 追加
            HelpIntentHandler,
            CancelAndStopIntentHandler,
            SessionEndedRequestHandler,
            IntentReflectorHandler)
        .addErrorHandlers(
            ErrorHandler)
        .lambda();
    

セッションアトリビュートに発話を保存しておいて、RepeatIntent が来たらそれを発話するようにしている。

一時停止/再開

ユーザー: 「なきごえなあに」を開いて。
Alexa: 動物の名前を言ってください。
ユーザー: 猫。
Alexa: 食肉目ネコ科の哺乳類。体長50センチメートル内外。...
ユーザー: 一時停止。
Alexa: ...
ユーザー: 続けて。
Alexa: 毛色は多様。指先には...

参考: 猫 (Weblio 辞書)

今回の鳴き声では必要ないが、発話が少々長い場合、発話の一時停止および再開をしたいことがあるかもしれない。実はこれはテキストではできないらしい (音楽ならできるらしい)。代わりに、はじめから言い直すようにする。

  • [ビルド] のインテントの追加で "AMAZON.PauseIntent"、"AMAZON.ResumeIntent" を追加。
  • [コードエディタ] で PauseIntentHandler と ResumeIntentHandler を追加。
    const PauseIntentHandler = {
        canHandle(handlerInput) {
            return handlerInput.requestEnvelope.request.type === 'IntentRequest'
                && handlerInput.requestEnvelope.request.intent.name === 'AMAZON.PauseIntent';
        },
        handle(handlerInput) {
            const speechText = 'はい。';
            return handlerInput.responseBuilder
                .speak(speechText)
                .reprompt(speechText)
                .getResponse();
        }
    };
    
    const ResumeIntentHandler = {
        canHandle(handlerInput) {
            return handlerInput.requestEnvelope.request.type === 'IntentRequest'
                && handlerInput.requestEnvelope.request.intent.name === 'AMAZON.ResumeIntent';
        },
        handle(handlerInput) {
            let attributes = handlerInput.attributesManager.getSessionAttributes();
            const speechText = attributes.speechText;
            return handlerInput.responseBuilder
                .speak(speechText)
                .reprompt(speechText)
                .getResponse();
        }
    };
    
  • PauseIntentHander、ResumeIntentHander を登録。
    exports.handler = Alexa.SkillBuilders.custom()
        .addRequestHandlers(
            LaunchRequestHandler,
            AnimalIntentHandler,
            RepeatIntentHandler,
            PauseIntentHandler, // 追加
            ResumeIntentHandler, // 追加
            HelpIntentHandler,
            CancelAndStopIntentHandler,
            SessionEndedRequestHandler,
            IntentReflectorHandler)
        .addErrorHandlers(
            ErrorHandler)
        .lambda();
    

参考