0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Alexa】新機能 hostedスキルのコードを理解する

Posted at

#はじめに
これまでβ版だったAlexa-hostedスキルが正式版?となったのでそれを使ってアレクサスキルを作って学習します。この時点で筆者が超初心者のため難しい説明はしません、というか出来ません。

#準備
こちらに準備方法を用意しています。
ここからテストが出来る段階まで進めておいてください。

参考

#コード全体を理解する
テンプレートのHelloWorldのプログラム部分を省略してみました。

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

const LaunchRequestHandler = {
    // 省略
};
const HelloWorldIntentHandler = {
    // 省略
};
const HelpIntentHandler = {
};
const CancelAndStopIntentHandler = {
    // 省略
};
const SessionEndedRequestHandler = {
    // 省略
};
const IntentReflectorHandler = {
    // 省略
};
const ErrorHandler = {
    // 省略
};
exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
        LaunchRequestHandler,
        HelloWorldIntentHandler,
        HelpIntentHandler,
        CancelAndStopIntentHandler,
        SessionEndedRequestHandler,
        IntentReflectorHandler, 
    )
    .addErrorHandlers(
        ErrorHandler,
    )
    .lambda();

よく見るとプログラムの実態は下の方に定義してあるだけで、それ以外の部分はconstで定義されていることがわかります。
constで定義されたハンドラプログラム?がaddRequestHandlersまたはaddErrorHandlersで登録されているようです。
つまり新たなハンドラプログラムを書いたときは、ここにも追加する必要があるわけです。addRequestHandlersはその手前を見れば** Alexa.SkillBuilders.custom()**なのでアレクサのスキルビルダーのカスタムスキルからリクエストされるプログラムで、addErrorHandlersはエラーの時に呼ばれるようですね。

まずはここを理解しましょう。

#インテントハンドラ
アレクサに対して発音する言葉の単語(インテント)の内容に応じて呼び出される命令セット(ハンドラ)が異なるというイメージです。※イメージの意味は後で説明します。

ハンドラごとの処理の大まかな内訳は

ハンドラハンドラ名 処理の内容
LaunchRequestHandler アレクサが最初に呼ばれたときに実行
HelpIntentHandler 説明を求められたときに実行
HelloWorldIntentHandler 自分で作ったスキルを実行
CancelAndStopIntentHandler 中止または停止を求められた場合に実行
SessionEndedRequestHandler セッションを終了する時に実行
IntentReflectorHandler デバッグ時に実行
ErrorHandler エラーが発生したときに実行

のようになっています。
でもこれ単なるイメージです。

Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent'

Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent'
という比較がありますが、ソース中のこの2カ所を入れ替えてみましょう。

入れ替え前

index.js
const HelloWorldIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'こんにちは、アレクサスキルの世界へようこそ!';
        return handlerInput.responseBuilder
            .speak(speakOutput)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};
const HelpIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'これはあなたが作ったアレクサのスキルです。。';

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

入れ替え後

index.js
const HelloWorldIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'こんにちは、アレクサスキルの世界へようこそ!';
        return handlerInput.responseBuilder
            .speak(speakOutput)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};
const HelpIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'これはあなたが作ったアレクサのスキルです。。';

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

どうなりましたか?
説明、とこんにちはで本来返す言葉が入れ変わりました。
つまり発音した単語ごとにインテントハンドラが決まっているわけではなく、発音した場合はaddRequestHandlersで追加したインテントハンドラにすべて処理が行き渡っているのです。

では比較文を両方ともHelloWorldIntentにしてみるとどうなるでしょうか?
試してみると最初の比較文は真になって実行されますが、次の比較は行われません。
これは次の章で説明しますが、比較文が真の時にはreturnで返値とともに処理もそこで終わるためです。

ということでインテントハンドラの中身を見てみましょう。

#インテントハンドラの中身
「こんにちは」と発音した場合に処理されるインテントハンドラの中身を見てみましょう

index.js
const HelloWorldIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'こんにちは、アレクサスキルの世界へようこそ!';
        return handlerInput.responseBuilder
            .speak(speakOutput)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};

実はここにアレクサスキルの基本が詰め込まれています。
まずはcanHandlehandleを見てみましょう。

index.js
const HelpIntentHandler = {
    canHandle(handlerInput) {
        // 処理すべきなら true を返す
    },
    handle(handlerInput) {
        // 上記の判断で true になったときに実行される
    }
};

インテントハンドラには必ずcanHandlehandleがセットで登場します。
※なんかもっと良いやり方が将来出るでしょうけど

canHandle内では自分のところで処理すべきか判断し、return で true が返値になれば handle が実行されるようになっています。

なので判断さえできるなら複数の発音単語を処理しても構わないわけです。
サンプルをよく見ると

index.js
const CancelAndStopIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && (Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.CancelIntent'
                || Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent');
    },
    handle(handlerInput) {
        const speakOutput = 'さようなら';
        return handlerInput.responseBuilder
            .speak(speakOutput)
            .getResponse();
    }
};

のように終了と中止を同じように処理が行われています。

#canHandleの中身

canHandle内で行われている判定文を見てみます。

index.js
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent';
    },

Alexa.getRequestTypegetIntentNameをそれぞれ比較して双方が一致しているときにのみ true になっていることがわかります。

##getRequestType
getRequestTypeはなぜ呼ばれたのかの種類が入ります。

比較先の文字列 内容
LaunchRequest インテントの単語を呼ばれなかったとき
IntentRequest インテントに対応させた単語を呼ばれたとき

LaunchRequestは説明によっては「最初に呼ばれたとき」となっているものもありますが

呼び出し内容 Alexa.getRequestTypeに入る中身
ノードテストを開いて LaunchRequest
ノードテストを開いて こんにちは IntentRequest

となることから「最初に呼ばれたとき」という表現からは外しました。最初に呼ばれたときの処理は別途考えなくてはなりませんね。

##getIntentName
getIntentNameにはインテントとして設定した値が入ります。
「こんにちは」などの発音に設定したインテントはHelloWorldIntentなので、これと比較することでオリジナルのスキルを作ることが出来ます。

#スキルの継続と終了
デバッグではわかりづらいですが、スキルを読んだときは継続する場合と終了する場合があります。

「ノードテストを開いて」と発音した後に「こんにちは」と発音したことで独自のスキルが実行されました。しかし「こんにちは」の後に再度「こんにちは」と発音しても独自のスキルは実行されずにアレクサが反応してしまいます。

これは呼び出しスキル名を発音してインテントを発音しないときの処理が継続状態になっているためです。
先ほどのサンプルをもう一度見てみましょう。

index.js
const HelloWorldIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'こんにちは、アレクサスキルの世界へようこそ!';
        return handlerInput.responseBuilder
            .speak(speakOutput)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};

これは「こんにちは」と発音したときに実行される処理ですが、実行処理部分を見るとなにやらコメントになっている部分があって、さらにその後ろに何か書かれています。

つまり 「値を返すときに**.reprompt**をつけておくと処理は継続されますよ」ということです。

#最後に
これでアレクサスキルの実行処理コードが理解できるようになったのではないでしょうか?

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?