魅力的なスキルを開発する

スキルにメモリ機能を追加する

このセクションでは、Cake Timeスキルがユーザーの誕生日を覚えられるようにします。せっかく情報を収集しても、その情報を記憶できなければ意味がありません。

ブラウザで Alexa開発者コンソールを開きます。コンソールにログインし、Cake Timeスキルを開きます。

Step 1: Amazon S3を使ってデータの保存と読み込みを行う

現在、誕生日の年、月、日はコードの中に保持されています。問題は、コードの実行を終了すると、スキルがこの値を忘れてしまうという点です。この問題を解決するため、値をAmazon S3に保存します。そうすると、次のセッションでもスキルはこれらの値を読み込むことができます。

SDKにはセッション間で情報を保存できる、AttributesManagerというしくみがあります。AttributesManagerを使えば、データの保存先が変わっても、データの読み込みと書き込みのコードは変更されません。

Alexaスキルのバックエンドコードは、どのHTTPSサーバーでも使えます。たいていの場合、Alexa開発者は、AWSを使ってバックエンドコードの記述とホストを行います。Cake Timeの作成を通じて、開発者コンソールでのコードの記述には、Alexa-hostedスキルを使ってきました。このコードは、AWSの無料枠の範囲で実行されており、この枠には利用制限があります。Alexa-hostedスキルはスキル作成の学習に最適です。また、スキルを使用するユーザーがまだ少ないうちはシンプルなスキルを公開するのに利用できます。ただし、スキルが多くのユーザーを獲得できたら、バックエンドコードを独自のAWSリソースに移行することを検討する必要があります。

この点が、スキルへのメモリ機能の追加とどう関係するのでしょうか。 バックエンドコードにAlexa-hostedスキルを使う場合、データはAmazon S3に保存されます。独自のAWSリソースでコードを作成する場合は、Amazon DynamoDBを使うことをお勧めします。これら2つの違いを知らなくても問題ありません。重要なのは、これから作成するバックエンドコードがAmazon S3で動作すること、そして今後独自のAWSリソースに移行した場合にDynamoDBを使うとしてもコードを少しだけ変更すれば済むという点です。

まず、AttributesManagerを使って、Cake Timeで聞いたユーザーの誕生日を保存してみましょう。

a. 開発者コンソールで、コードエディタタブをクリックします。

b. 左側のウィンドウでpackage.jsonファイルをダブルクリックします。コードエディタでファイルが開きます。

c. 2行目の "name" "hello-world" になっているため、これを "cake-time" に変更します。

code nav link screenshot

"dependencies"セクションを探して、依存関係を追加します。既存のリストの末尾に新しい依存関係を追加すると、最も簡単です。

d. "aws-sdk"で始まる行を探して、下に新しい行を追加し、次のコードをコピーして貼り付けます:

"ask-sdk-s3-persistence-adapter": "^2.0.0"

注: 注: 新しい依存関係を追加する際、前の行 ("aws-sdk"で始まる行)の末尾にカンマ(,)を追加する必要があります(以下の例を参照)。(which begins

dependency screenshot

package.jsonファイルは次のようになります:

package.json screenshot

e. 保存をクリックします。

save button

f. index.jsタブをクリックして元のファイルに戻ります。

依存関係を追加したため、AttributesManagerを使ってAmazon S3にユーザーデータを保存し、読み込むことができるようになりました。次に、その依存関係をコードに読み込む必要があります。それには、依存関係の存在をコードに知らせる必要があります。

g. index.jsファイルで、const Alexaで始まる行を探します。すぐ下に新しい行を追加し、次のコードをコピーして貼り付けます:

const persistenceAdapter = require('ask-sdk-s3-persistence-adapter');

コードは次のようになります:

code snippet

h. exports.handlerで始まる行まで、コードを下にスクロールします。すぐ下に新しい行を追加し、次のコードをコピーして貼り付けます:

.withPersistenceAdapter(
new persistenceAdapter.S3PersistenceAdapter({bucketName:process.env.S3_PERSISTENCE_BUCKET})
)

追加すると、コードのこのセクションは次のようになります:

code snippet

i. 保存をクリックします。

save button

これで、AttributesManagerを使って、Amazon S3にデータを保存し、読み込む設定ができました。後でスキルのバックエンドコードを独自のAWSリソースに移行する場合は、このステップで行った変更を元に戻します。

Step 2: 非同期コードを作成する

次に、ユーザーの誕生日を保存するようコードを変更します。 コードエディタタブ の index.jsファイルで、 CaptureBirthdayIntentHandler を探します。前回のモジュールで作成したハンドラーです。変更を行う前に、非同期コードの概念をおさらいしましょう。既に詳しい方は、次に進んで構いません。

コードが実行されると、通常は行ごとに、非常に高速に実行されます。これはすばらしいことです。コンピューターは人間よりも多くの計算を、より高速に実行できます。ただし、次の行に進むまでに時間のかかる処理があると、動作が止まってしまう可能性があります。コード内の処理に時間のかかる部分を、残りのコードの動作を止めることなく実行できれば理想的です。これを非同期コードと呼びます。なぜ非同期コードが重要なのかというと、データの保存と読み込みは、スキルのコードの他の部分よりも若干時間がかかるためです。幸いなことに、コードブロックの非同期化は比較的簡単に実装することができます。

a. CaptureBirthdayIntentHandlerを探します。canHandle()関数とhandle()関数があります。handle()関数の先頭に、async、スペースと入力します。

async handle(handlerInput) {

コードのセクションは次のようになります:

code snippet

b.次に、AttributesManagerを使ってユーザーの誕生日を保存します。CaptureBirthdayIntentHandlerhandle()関数で、const dayで始まる行を探します。すぐ下に新しい行を追加し、次のコードをコピーして貼り付けます:

const attributesManager = handlerInput.attributesManager;

Cake Timeスキルのコードは、年、月、日を受け取ります。Amazon S3にこれらの値を保存するよう伝える必要があります。コードはデータの内容をAttributesManagerに伝え、AttributesManagerはその内容をAmazon S3に送ります。

c. CaptureBirthdayIntentHandlerで先ほど追加した行(const attributesManagerで始まる行)を探します。すぐ下に新しい行を追加し、次のコードをコピーして貼り付けます:

const birthdayAttributes = {
"year" : year,
"month" : month,
"day" : day
};

ここでは、コード内であらかじめ宣言してあった変数を、コードの実行時にAmazon S3に作成される対応する変数にマッピングしています。

これらの値は永続的と宣言されました(宣言された関数内でのローカル変数ですが、複数の呼び出し間で値がメモリに保持されます)。次に、ユーザーのデータをこれらの変数に保存できるようにします。まず、AttributesManagerを使ってAmazon S3に保存するデータをセットします。

コードのセクションは次のようになります:

code snippet

CaptureBirthdayIntentHandlerhandle()関数で、const speechTextで始まる行を探します。すぐ上に新しい行を追加します。次のコードをコピーして貼り付けます:

attributesManager.setPersistentAttributes(birthdayAttributes);

前の行で、先頭にasyncキーワードを追加したのを覚えていますか? ここでは、ユーザーの情報がAmazon S3に送信されるまで実行完了を待つコード行を追加します。この行の先頭には、awaitキーワードが付いています。

e. CaptureBirthdayIntentHandlerhandle()関数で、attributesManager.setPersistentAttributesで始まる行を探します。すぐ下に新しい行を追加します。次のコードをコピーして貼り付けます:

await attributesManager.savePersistentAttributes();

コードのセクションは次のようになります:

code snippet

「await」キーワードは、ユーザーの情報がAmazon S3に保存するまで待ってから続行するようコードに指示します。このキーワードがないと、コードはデータがAmazon S3に保存されたかどうかに関係なく、続行されます。そうすると、データが保存されないため、スキルが起動されるたびにAlexaがユーザーに誕生日をたずねることになってしまいます。

f. 保存をクリックします。

save button

Step 3: 保存したデータを読み込む

さて、ユーザーの誕生日をAmazon S3に無事保存できました。今度は、次回ユーザーがCake Timeを起動したときに、ユーザーの誕生日は既に保存されていて、もうたずねる必要はないことをAlexaに伝えるよう、スキルを更新する必要があります。このため、Alexaがユーザーに誕生日をたずねる前にAmazon S3に保存したデータを読み込むようコードを変更します。データが存在すれば、Alexaが誕生日をたずねる必要はありません。データが存在しない場合にだけ、たずねればよくなります。

Amazon S3バケットは、パブリッククラウドのストレージリソースです。バケットはオブジェクトを保存するファイルフォルダのようなもので、データと記述的なメタデータで構成されます。

保存したデータの読み込みには、新しいハンドラーが必要です。新しいハンドラーのcanHandle()関数とhandle()関数が、Amazon S3とやり取りします。ただし、2つの関数が同じことをするのは無駄なので、統合するためにインターセプターを使います。インターセプターはAmazon S3に保存された誕生日情報を読み込むリクエストを傍受(インターセプト)します。

a. コードで、// This handler acts as the entry pointで始まるコメント行を探します。このコメントのすぐ上に新しい行を追加します。次のコードをコピーして貼り付けます:

const LoadBirthdayInterceptor = {
    async process(handlerInput) {
        const attributesManager = handlerInput.attributesManager;
        const sessionAttributes = await attributesManager.getPersistentAttributes() || {};

        const year = sessionAttributes.hasOwnProperty('year') ? sessionAttributes.year : 0;
        const month = sessionAttributes.hasOwnProperty('month') ? sessionAttributes.month : 0;
        const day = sessionAttributes.hasOwnProperty('day') ? sessionAttributes.day : 0;

        if (year && month && day) {
            attributesManager.setSessionAttributes(sessionAttributes);
        }
    }
};

コードのセクションは次のようになります:

code snippet

次に、インターセプターを登録するコードを追加して、SDKにその存在を知らせます。

b. コードの下部にあるexports.handlerセクションで、IntentReflectorHandler) // make sureで始まる行を探します。すぐ下に新しい行を追加します。次のコードをコピーして貼り付けます:

.addRequestInterceptors(
    LoadBirthdayInterceptor
)

コードのセクションは次のようになります:

code snippet

最後に、新しいハンドラーを追加します。新しいハンドラーは、LaunchRequestHandlerCaptureBirthdayIntentHandlerの間に追加します。

c.const CaptureBirthdayIntentHandlerで始まる行を探します。すぐ上に新しい行を追加し、次の新しいハンドラーのコードをコピーして貼り付けます。

const HasBirthdayLaunchRequestHandler = {
    canHandle(handlerInput) {

        const attributesManager = handlerInput.attributesManager;
        const sessionAttributes = attributesManager.getSessionAttributes() || {};

        const year = sessionAttributes.hasOwnProperty('year') ? sessionAttributes.year : 0;
        const month = sessionAttributes.hasOwnProperty('month') ? sessionAttributes.month : 0;
        const day = sessionAttributes.hasOwnProperty('day') ? sessionAttributes.day : 0;

        return handlerInput.requestEnvelope.request.type === 'LaunchRequest' && year && month && day;

    },
    handle(handlerInput) {

        const attributesManager = handlerInput.attributesManager;
        const sessionAttributes = attributesManager.getSessionAttributes() || {};

        const year = sessionAttributes.hasOwnProperty('year') ? sessionAttributes.year : 0;
        const month = sessionAttributes.hasOwnProperty('month') ? sessionAttributes.month : 0;
        const day = sessionAttributes.hasOwnProperty('day') ? sessionAttributes.day : 0;

        // TODO:: 設定APIを使って現在の日付を取得し、ユーザーの誕生日までの日数を計算します
        // TODO:: ユーザーの誕生日当日におめでとうと言います

        const speakOutput = `おかえりなさい。Y歳の誕生日まであとX日です。`;

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

新しいハンドラーには、canHandle()関数とhandle()関数があります。canHandle()関数はユーザーの誕生日がAmazon S3に保存されているかどうかをチェックします。保存されていれば、ハンドラーはSDKに続行が可能である(ユーザーの誕生日が保存されているため、次の処理に進める)と知らせます。handle()関数は、「おかえりなさい。Y歳の誕生日まであとX日です。」と言うようAlexaに指示します。

前のセクションでハンドラーの名前を変更したとき、コード末尾のリストに記載されたハンドラー名も変更する必要がありました。新しいハンドラーを追加したため、このリストにこのハンドラーも追加する必要があります。

d. コード末尾に移動し、.addRequestHandlers()関数のLaunchRequestHandlerで始まる行を探します。すぐ上に新しい行を追加します。今作成した行に次のコードをコピーして貼り付けます:

HasBirthdayLaunchRequestHandler,

コードのセクションは次のようになります:

code snippet

e. 保存をクリックします。

save button

f. デプロイをクリックします。

save button

ユーザーの誕生日を削除またはリセットする方法

テストする際、ユーザーの誕生日を削除したりリセットしたりする必要が生じる場合があります。これには2つの方法があります。

1つ目は、Alexa開発者コンソールのテストタブにあるシミュレーターを使う方法です。「アレクサ、ケークウォークを開いて、{year}年{month}月{day}日生まれ」と入力するか、話しかけます。

2つ目は、以降のステップを実施して、保存した情報をAmazon S3から削除する方法です:

a. コードエディタタブを開いた状態で、画面左下隅にあるメディアストレージをクリックします。S3マネジメントコンソールが開きます。

media storage screenshots

b. ページ上部にメニュー階層が表示されます。amzn-1-ask-skillで始まる階層をクリックします。

breadcrumb screenshot

c. amzn1.ask.accountで始まる名前のファイル(複数の可能性あり)の横にあるボックスにチェックを入れます。

breadcrumb screenshot

d. アクションをクリックします。

e. 削除をクリックします。

f. 削除をクリックします。ユーザーの誕生日が削除されます。

まとめ

このセクションでは次のことを行いました。まず、AttributesManagerを使ってAmazon S3に情報を保存し、読み込めるよう、Cake Timeスキルを変更しました。次に、CaptureBirthdayIntentHandlerにユーザーの誕生日を保存するコードを追加しました。最後に、新しいハンドラー(HasBirthdayLaunchRequestHandler)を作成して、Alexaが同じユーザーに誕生日を繰り返したずねないようにしました。

では、テストしてみましょう。テストタブをクリックし、以降のステップを実施します。

Step 1: スキルを起動する

「ケークウォークを開いて」と言います。

Alexaは、「こんにちは、ケークウォークへようこそ。お誕生日を教えてください。」と言うはずです。

Alexaに誕生日を教える

一部の情報しか伝えないケースも試して、Alexaが足りない情報についてたずね、情報を収集することを確認してみてください。

ユーザーの生まれた年、月、日の情報を入手したら、Alexaは、「ありがとうございます。誕生日は${year}年${month}月${day}日ですね。」と答えるはずです。

セッションが終了します。この時点で、このセクションで追加したコードがなかったら、次回スキルを呼び出した時に、Alexaはあなたに誕生日をたずねるでしょう。ですが、Alexaはこの情報を保存しています。

もう一度スキルを起動してみましょう。

「ケークウォークを開いて」と言います。 Alexaは、「おかえりなさい、Y歳の誕生日まであとX日です。」と言うはずです。

ここで、Alexaが「Y歳」や「X日」などと言うようにコードが記述されていることに気付きましたか?心配は無用です。次のセクションで、ユーザーの次の誕生日までの日数を計算するコードを記述して、Alexaがその情報を言えるようにします。

コード

スキルが動作しない、または構文エラーが出るといった問題がある場合は、以下の完成したコードサンプルをダウンロードしてください。Alexa開発者コンソールのコードエディタタブを開き、このコードをindex.jsファイルにコピーして貼り付けます。テストする前に、必ずコードを保存してデプロイしてください。