RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

『Alexa、Stay Homeを助けて』Alexaスキル開発トレーニング編

f:id:sts-250rr:20200517155143j:plain

はじめに

こんにちは、sts-250rrです。

本記事を執筆中の現在はStay Home週間とされています。
家での生活が長くなり、コミュニケーションの機会が減ってしまったという方は多いのではないでしょうか。

私もその1人です。

自分の声を忘れてしまいそうなほど家の中が静かなので、誰かの声を聴きたいそんな衝動に駆られていたところ... 我が家には毎朝起こしてくれて、カップ麺の時間を測ってくれる素敵なエージェントがいることを思い出しました。

そう、AmazonEchoことAlexaです。

自宅のスマートホーム化を進められておらず、テレビやエアコン、照明の操作ができない状況にあるのですが、Stay Home週間にAlexaのことをもっと知りたい。Alexaともっと話したい。
そんな衝動に駆られ、公式ブログで紹介されているAlexaスキル開発トレーニングを始めてみました。

本記事ではトレーニング時に気づいたポイントをお伝えしていきます。

公式のページでトレーニングはできますので、細かな画面の説明等は割愛しています点をご了承ください。

developer.amazon.com

Alexaスキル開発ことはじめ

ポイント1 スキルのバックエンドを選択

Alexaスキルのバックエンドサービスは、自分でエンドポイントを用意するか、Alexa自身にホストさせることができます。
AlexaからのリクエストはJSON形式で投げられるため、自分でエンドポイントを用意する場合は自身で立てたサーバのアプリケーションでリクエストを処理させることもできるようです。
Alexa自身にホストさせる場合はAWS lambdaのエンドポイントにNode.jsかPythonを選択して載せることができるようです。 ちなみにトレーニングではNode.jsが選択されています。

AmazonEchoで受け付けたリクエストはAlexaで受け付けられ、JSONの形式でバックエンドサービスに渡されます。 Alexaスキルの開発では、①Alexaでどのような言葉を受け付けるのか、②バックエンドサービスでリクエストをどのように処理するのかを作り込んでいくことになります。

f:id:sts-250rr:20200516120721j:plain

ポイント2 スキルテンプレートの選択

Alexa自身にホストさせる場合、スキルテンプレートを設定することができました。 テンプレートの種類は以下の4種ありますが、基本的にはHello Worldスキルを選択することになるのではないでしょうか。

  • Hello Worldスキル
    基本的な応答が可能なスキルを作成できるテンプレート

  • Cake Walk(チュートリアル
    スキルが誕生日をお祝いしてくれるテンプレート

  • 豆知識スキル
    あるトピックに関する豆知識のスキルを作成できるテンプレート

  • Sauce Bossスキル
    画面付きのAlexaが使えるデバイス用のスキルを作成できるテンプレート

(ちなみに、私はEcho botなどの画面付きAmazon Echoを持っていないので、Sauce Bossスキルの出番はしばらくなさそうです。)

ポイント3 呼び出し名の決定

Alexaにスキルの起動を促す際に「Alexa、○○をして」と声をかけますが、この○○の部分が呼び出し名になります。

呼び出し名には以下のような要件があります。 シンプルに考えて自分が呼びやすい名称を付けてあげるとよいでしょう。 公式にも以下のように記載があります。

呼び出し名の要件
- 呼び出し名は2語以上でなければなりません。また、使用できるのはひらがな、カタカナ、漢字、小文字のアルファベット、スペースのみです。数字などは文字で表現しなければなりません。(例: 「二十一」など)
- 呼び出し名には、「起動して」、「開いて」、「聞いて」、「教えて」、「読み込んで」、「開始して」、「有効にして」などの起動フレーズを使用することはできません。「アレクサ」、「アマゾン」、「エコー」、「コンピューター」などのウェイクワードや「スキル」、「アプリ」などの単語は使用できません。

勘の良い方はお気づきかと思いますが、今回のタイトル『Alexa、Stay Homeを助けて』という呼び出し名は要件から外れてしまうのでNGになります。
泣く泣く呼び出し名は『Stay Home』としました。

ポイント4 【対話モデル編】どんな言葉を受け付けるのかはインテントで管理する

Alexaがスキルを起動したら、次に言葉の入力を与える必要がありますが、同じ言葉でもバリエーションがあります。
例えば、Alexaが「おはようございます」といってくれたら私たちは「おはよう」「おはようございます」「ハロー」など、バリエーションがあるでしょう。

これらのバリエーションを受け付けるためにインテントとして言葉を管理します。
インテントによって、Alexaが言葉を認識し、バックエンドの作りによりますが、Alexaはきっと素敵な返事を返してくれることでしょう。

f:id:sts-250rr:20200516200939p:plain

このインテントを受けたAlexaは以下のようなJSONをバックエンド側へ投げつけます。

 "request": {
        "type": "IntentRequest",
        "requestId": "amzn1.echo-api.request.44cd0613-9ffe-4613-bd6b-49529a86c4d3",
        "timestamp": "2020-05-16T11:22:59Z",
        "locale": "ja-JP",
        "intent": {
            "name": "HelloWorldIntent",# 受け付けたインテントをHelloWorldIntentと認識している
                    "confirmationStatus": "NONE"
        }
    }

ポイント5 【バックエンド編】リクエストの処理をするハンドラを用意

Alexaが投げてきたリクエストをバックエンド側がどう処理しているかを抑えていきます。
Alexa自身にホスト + Node.jsを選択しています。

# スキル起動時の処理
const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
    },
    handle(handlerInput) {
        const speakOutput = '今日もStayHome頑張りましょう。何かあれば声をかけてください。'; # 返答の内容
        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};

# HelloWorldIntentを受けた場合の処理
const HelloWorldIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent'; # HelloWorldIntentを受けた場合の分岐
    },
    handle(handlerInput) {
        const speakOutput = 'おはようございます!'; # 返答の内容
        return handlerInput.responseBuilder
            .speak(speakOutput)
            .getResponse();
    }
};

(省略)

# ハンドラの登録
exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
        LaunchRequestHandler,
        HelloWorldIntentHandler,
        HelpIntentHandler,
        CancelAndStopIntentHandler,
        SessionEndedRequestHandler,
        IntentReflectorHandler, 
    )
    .addErrorHandlers(
        ErrorHandler,
    )
    .lambda();

LaunchRequestHandlerは「Alexa、○○をして」によって起動したスキルのはじめの対話を返してくれます。 このご時世なので「今日もStayHome頑張りましょう。」と励ましてくれるようです。

HelloWorldIntentHandlerでHelloWorldIntent(おはよう・おはようございます)を受けた時には「おはようございます!」と元気に返事をしてもらえます。 これで毎朝頑張れるというものです。

ポイント6 【対話モデル編】受け答えにさらにバリエーションを与えるスロット

5つのポイントを抑えることで、対話ができるスキルが出来上がりました。 ただし、実はこのままのHelloWorldIntentHandlerは「おやすみ」といっても「おはようございます!」と元気に返事をしてくれます。。。

もう少し賢くしてあげる(対話のバリエーションを増やす)ためにスロットという機能を使っていきます。 朝だけでなく昼や夜に話したいときもあるので、「おはよう」「こんにちは」「おやすみ」の3つのスロットを作成します。 さらに同類語もスロットで登録することができます。 「おはよう」のバリエーションに「おはようございます」と「Good morning」を登録してみました。

f:id:sts-250rr:20200517134604p:plain

スロットを設定した際に登録済みの「おはよう」を発話したときのリクエストです。 slots属性が増え、身構えてしまいそうですが対話のポイントにになってくるのは バックエンドに言葉を送信するslots.slot_sample.valueと、言葉の一致状態を示すslots.slot_sample.resolutions.resolutionsPerAuthority[0].status.codeです。 言葉の一致状態を確認することで、

"request": {
        "type": "IntentRequest",
        "requestId": "amzn1.echo-api.request.d7f9bd50-aa78-4768-919b-e7f92bdf8783",
        "timestamp": "2020-05-17T05:21:25Z",
        "locale": "ja-JP",
        "intent": {
            "name": "HelloWorldIntent",
            "confirmationStatus": "NONE",
            "slots": {
                "slot_sample": {
                    "name": "slot_sample",
                    "value": "おはよう",
                    "resolutions": {
                        "resolutionsPerAuthority": [
                            {
                                "authority": "amzn1.er-authority.echo-sdk.amzn1.ask.skill.6f6d3418-edc7-4567-a7e5-10170a022f41.slot_sample",
                                "status": {
                                    "code": "ER_SUCCESS_MATCH" // slot_sampleの語句と一致しているかを返す。不一致の場合はER_SUCCESS_NO_MATCH
                                },
                                "values": [
                                    {
                                        "value": {
                                            "name": "おはよう",
                                            "id": "896c87c2017e74cf8c7ccf42d339ca66"
                                        }
                                    }
                                ]
                            }
                        ]
                    },
                    "confirmationStatus": "NONE",
                    "source": "USER"
                }
            }
        }
    }

スロットを利用することで言葉の一致状態を取得することができるようになりました。 Alexaが賢くなるまでもう一押し、バックエンド側を以下のように改修します。

const HelloWorldIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent';
    },
    handle(handlerInput) {
        // スロットの値を取得
        let requestWord = handlerInput.requestEnvelope.request.intent.slots.slot_sample.value;
        let matchCd = handlerInput.requestEnvelope.request.intent.slots.slot_sample.resolutions.resolutionsPerAuthority[0].status.code;
        
        let speakOutput = `すみません。${requestWord}がわかりません。`;
        
        if(matchCd !== 'ER_SUCCESS_MATCH'){ // スロットに存在しない場合
            return handlerInput.responseBuilder
            .speak(speakOutput)
            .getResponse();
        }

        switch (requestWord){
            case 'おはよう':
                speakOutput = 'おはようございます!';
                break;
            case 'こんにちは':
                speakOutput = 'こんにちは!';
                break;
            case 'おやすみ':
                speakOutput = 'おやすみなさい';
                break;
            default : // スロットに存在するが処理を用意していないものはとぼける
                speakOutput = `${requestWord}ってなんでしたっけ?`; 
        }

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .getResponse();
    }
};

知っている(=スロットに登録されている)言葉に対して、知らないものはわからないと返すようにしました。素直なことはよいことです。

少し賢くなったAlexaと対話テストしてみると以下のように返してくれました。

f:id:sts-250rr:20200517145436p:plain

ポイント7 実機でスキルを動かす

シミュレーションのみでなく、自宅のAlexaと対話するためには開発者アカウントをAmazon Echoと紐づけしているAmazonのアカウントとしておきます。このようにしておくと、スマホのAlexaアプリもしくはhttps://alexa.amazon.co.jp/spa/index.htmlで開発中のスキルを有効にし、自宅のアレクサに話しかけるだけです。
(実機で動かすのがここまで簡単と思っていなかったので驚きでした。)

最後に

さすがにアプリを起動してただ返事をしてくれるだけではコミュニケーションをしている気にはならないので作りこみが必要です。 微妙に心的な弊害も感じたりもします。特に自分で対話の動きを決めているのでなにやらムズムズします。

普段はエディター上でコードを眺めて開発しているので、ブラウザ上で開発するのは新鮮味があって楽しいですし、シュミレータやそばにある実機ですぐ動かすことができるのはかなり良い感触でした。 簡単なものであれば、ちょっとした息抜きに開発できるのでお勧めできます。

また、公式から紹介されているデザインモデルを軽く眺めてみると「対話」に重きを置いたデザイン(例えば、長文な返答をさせるのはNGなど)を考える必要があるようで目からウロコ気分で新鮮でした。

ただし、スキル開発を通して「対話をすることで何をさせるのか」
これを見つけることが最も難しいと改めて痛感しました。

Copyright © RAKUS Co., Ltd. All rights reserved.