Alexaのスキルを実機で試そう

2018年8月7日(火)
畠中 幸司(はたなか・こうじ)

こんにちは。

前回までで、ユーザーによる任意の入力を含む、様々なリクエストを受け付けるスキルを開発できるようになりました。今回はいよいよ、開発したスキルをAmazon Echo、Echo Dotに代表されるAlexa搭載デバイスで動作させたいと思います。

もちろんAlexa Skills Kit開発コンソールから利用できるAlexaシミュレーターでも多くのテストが行えますが、Alexa搭載デバイスでなければ試すことのできないことがあります。実機が必要なテストと言えば、例えば以下のようなものが挙げられます。

  • デバイスの所在地情報に基づいた処理を行う(所在地APIのテストを行う)
  • 店舗や会議室での動作など、デバイスを含めた挙動について確認する

例えばAlexa標準機能の天気予報スキルに天気をたずねると、デバイスの所在地に基づいた天気情報を返答します。そのためユーザーは、場所の情報を毎回伝える必要がありません。このようにデバイスに機能を使ってユーザーの負担を減らすことで、スムーズな利用体験を実現しています。

この所在地情報の取得にはデバイス固有のID(deviceId)が必要となるため、これを使った処理のテストはシミュレーターでは行えません。

また、Alexaを業務で使う場面を想定してみましょう。自宅のリビングルームと違い、店舗やオフィスなどは想像以上に雑音が多い場所です。さらに複雑な形状の空間であることも多く、このような環境でもきちんと指示を聞き取れるかの確認が必要になります。

というわけで、今回のテーマはこちらです。

  • 実機で動作確認する(ベータテスト)
  • 所在地情報を取得する

実機での動作確認手順(ベータテスト)

それではさっそく、前回作成した「来場者カウント」スキルを実機で動かすための手続きを始めましょう。これまでと同様にAlexa Skills Kit開発者コンソールを開いて、スキルの編集画面を開きます。その後、上部のメニューから「公開」をクリックします。

公開情報の入力

Alexaのスキルは、最終的にはストアで公開することで一般の利用者が入手できるようになります。公開に必要な情報を設定するのがこの画面ですが、今回は手元でテストを行うだけですので、それほど深刻に考えずに情報を入力して構いません。

説明に従って「公開名」「説明」「詳細な説明」「サンプルフレーズ」などの項目を埋めていきましょう。

スキル公開情報の入力

スキル公開情報の入力

公開に際して108×108ピクセル、および512×512ピクセルのアイコン画像が必要になります。画像編集ソフト等を使って作成しますが、この連載内容のテストを行うだけであれば、以下のデータをご利用ください。

テスト用アイコンのダウンロード

カテゴリーには「Productivity」の「Organizers & Assistants」を選びましょう。ひとまず「*」のついた必須項目だけ入力したら「保存して続行」をクリックします。

必要項目の入力

必要項目の入力

プライバシーとコンプライアンス」画面で、必要な項目の選択、入力を行います。「来場者カウント」スキルであれば、図1.3の通りの内容でOKです。

プライバシーとコンプライアンス

プライバシーとコンプライアンス

テストの手順」には、このAlexaスキルの公開に当たってテストを行うAmazonの担当者が必要とする情報を記載します。ここでは特に必要な情報はありませんので、以下のように入力しておくことにします。

Alexa対応デバイス以外に必要なものはありません。フレーズ例にしたがって発話するだけでテストできます。

入力が終わったら「保存して実行」をクリックします。

公開範囲

公開範囲」のページでは「公開」を選択します。特定の企業内で利用する目的のスキルであれば「Alexa for Business」というサービスを利用しますが、今回は扱いません。

さらに、実デバイスでテストを行うために「ベータテスト」と書かれた場所をクリックし、「ベータテスト管理者用Eメールアドレス」を入力します。特に理由がなければ、開発者ポータルにログインする際に使用しているアドレスでよいでしょう。入力したら「追加」をクリックします。

公開範囲情報

公開範囲情報

次に、「ベータテスターを追加」と書かれた欄に、実際にテストを行うユーザーのメールアドレスを入力します。こちらは、Alexa対応デバイスに設定しているアカウントのものを入力して追加します。完了後は「ベータテストを有効化」をクリックしましょう。テスターとして追加されたアドレスに、ベータテストへ招待するメールが送信されます。「保存して実行」もクリックしておきましょう。

ベータテストに参加するユーザーのアドレスを記入する

ベータテストに参加するユーザーのアドレスを記入する

You're invited to beta test a new Alexa skill」という件名のメールが、招待者に送られているはずです。メール本文中の「JP Customers:」と書かれたセクションの「Enable Alexa skill "来場者カウント"」のリンクをクリックしましょう。「来場者カウント」の部分には実際につけたスキルの名称が入ります。開いたウェブページからこのメールアドレス(Alexaで使用しているAmazonのアカウント)でログインすると、ベータテストに参加する旨を問うメッセージが表示されますので、「ACCEPT」を選択します。

ベータテスターには招待メールが届く

ベータテスターには招待メールが届く

以下のような画面が表示されていればOKです。[ENABLE]をクリックしてスキルをデバイスで有効にしましょう。

スキルの有効化

スキルの有効化

2018年4月の時点で、ベータテストに際しては特にAmazon社のレビューを受けることなく即座に試せるようです。お手元のAlexa対応デバイスでも無事に動作を試すことができたでしょうか。

連載のここまでの内容で、下記のことを押さえました。以降ではもう少しスキルでできることに踏み込んだ内容に入っていきたいと思います。

  • 設計と開発の流れについて知る
  • Alexaスキル開発に必要な環境を準備する
  • サンプルを基にスキルを開発し、動作確認をする
  • 実デバイスで動かす(ベータテスト)

所在地情報を取得する

Alexaの所在地情報は、スマートフォン等のGPSを基にした位置情報サービスと異なり、ユーザーが自分で設定したものです。情報の設定・変更は、Alexaアプリの「デバイスの所在地」から行えます。この情報を基に、例えば天気予報スキルでは場所を伝えなくても自宅付近の天気を調べられるのです。

それでは、所在地情報を扱うスキルを作成してみましょう。開発者ポータルで以下のようなスペックの新しいスキルを作成します。

  • スキル名:今どこスキル
  • スキル作成時のデフォルト:日本語
  • スキルに追加するモデル:カスタム

インテントはJSONで定義したものを取り込んでみましょう。次のコード例の内容でJSON形式のファイル(UTF-8形式のテキストファイル)を作成し、左メニューの「JSONエディター」で取り込みます。

リスト1:コード例1: interface.json

    "interactionModel": {
        "languageModel": {
            "invocationName": "今どこスキル",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "WhereAreYouIntent",
                    "slots": [],
                    "samples": [
                        "いまどこにいるか教えて"
                    ]
                }
            ],
            "types": []
        }
    }
}

AWS Lambdaにもこれまでと同じように「alexa-skill-kit-sdk-factskill」の設計図を基に関数を作成します。index.jsファイルの内容は、コード例2のように書き換えます。トリガーの追加も忘れないようにしましょう。

リスト2:コード例2: index.js

/* eslint-disable  func-names */
/* eslint quote-props: ["error", "consistent"]*/

'use strict';
const Alexa = require('alexa-sdk');
const Https = require('https');

//=============================================================================
// 定数定義
//=============================================================================
const APP_ID = 'amzn1.ask.skill.b884ff75-6598-40b6-93c5-5b378e5a1733';
const SKILL_NAME = '今どこスキル';
const HELP_MESSAGE = '現在のデバイスの場所をお知らせします';
const HELP_REPROMPT = '何をお調べしますか';
const STOP_MESSAGE = '終了します';

//=============================================================================
// 所在地APIを呼び出す関数
//=============================================================================
function getAddress(deviceId, consentToken, apiEndpoint) {
    const hostname = apiEndpoint.replace(/^https?:\/\//i, "");
    // 国コードと郵便番号を取得する場合のエンドポイント。
    const path = `/v1/devices/${deviceId}/settings/address/countryAndPostalCode`;
    // 全ての住所情報を取得する場合のエンドポイント
    // const path = `/v1/devices/${deviceId}/settings/address`;
    const requestOptions = {
        'hostname': hostname,
        'path': path,
        'method': 'GET',
        'headers': {
            'Authorization': 'Bearer ' + consentToken
        }
    };

    // APIに非同期でHTTPS接続をする。完了時にresolve, 失敗時にrejectが呼び出される
    return new Promise((resolve, reject) => {
        Https.get(requestOptions, (response) => {
            console.log("StatusCode: " + response.statusCode);
            response.on('data', (data) => {
                console.log(data);
                let responseObject = JSON.parse(data);
                const result = {
                    statusCode: response.statusCode,
                    responseObject: responseObject
                };
                resolve(result);
            });
        }).on('error', (e) => {
            reject();
        });
    });

}

//=============================================================================
// ハンドラー
//=============================================================================

const handlers = {
    'LaunchRequest': function () {
        this.emit('WhereAreYouIntent');
    },
    'WhereAreYouIntent': function () {
        const deviceId = this.event.context.System.device.deviceId;
        const consentToken = this.event.context.System.user.permissions.consentToken;
        const apiEndpoint = this.event.context.System.apiEndpoint;

        console.log("deviceId: " + deviceId);
        console.log("consentToken: " + consentToken);
        console.log("apiEndpoint: " + apiEndpoint);

        getAddress(deviceId, consentToken, apiEndpoint).then((result) => {
            switch (result.statusCode) {
                case 200:
                    // リザルトコードが200の時だけ戻り値の解析を行う

                    // 郵便番号を取得する。
                    const location = result.responseObject['postalCode'];
                    const speechOutput = `郵便番号は${location}です`;
                    console.log("speechOutput: " + speechOutput);
                    this.response.speak(speechOutput);
                    break;
                case 204:
                    this.response.speak('所在地情報がセットされていません');
                    break;
                case 403:
                    this.response.speak('所在地情報の取得は許可されていません');
                    break;
                default:
                    this.response.speak('所在地情報の取得に失敗しました');
            }
            this.emit(':responseReady');
        }).catch(() => {
            this.response.speak('所在地APIとの接続に失敗しました');
            this.emit(':responseReady');
        });
    },
    'AMAZON.HelpIntent': function () {
        const speechOutput = HELP_MESSAGE;
        const reprompt = HELP_REPROMPT;

        this.response.speak(speechOutput).listen(reprompt);
        this.emit(':responseReady');
    },
    'AMAZON.CancelIntent': function () {
        this.response.speak(STOP_MESSAGE);
        this.emit(':responseReady');
    },
    'AMAZON.StopIntent': function () {
        this.response.speak(STOP_MESSAGE);
        this.emit(':responseReady');
    },
    'SessionEndedRequest': function () {
        this.emit(':responseReady');
    },
};

exports.handler = function (event, context, callback) {
    const alexa = Alexa.handler(event, context, callback);
    alexa.APP_ID = APP_ID;
    alexa.registerHandlers(handlers);
    alexa.execute();
};

所在地APIのうち、国コードと郵便番号を取得するエンドポイントにHTTPS接続し、結果のJSONコードから郵便番号情報を取り出して返答しています。

所在地APIへのリクエスト手順、応答内容の詳細については、こちらのページに情報があります。

所在地情報を使用してスキルを拡張する

開発者コンソールでエンドポイントの設定が済んだら、左メニューから「アクセス権限」を開きます。このスキルがユーザーのどのような情報にアクセスできるかを設定するページです。ここで所在地情報へのアクセス権限を持たせることができます。まずは「デバイスのアドレス」セクションの「国と郵便番号のみ」にチェックを入れておきます。「ビルド」ページに戻るには「カスタム」をクリックします。設定は自動的に保存されます。

アクセス権限の設定

アクセス権限の設定

それでは、先のベータテストの例と同様に、今作成したスキルでも実デバイスでテストできるように設定してみましょう。Alexaの設定を行うアプリ側でも、このスキルに所在地情報へのアクセスを許可する設定が必要です。iPhoneアプリではスキルの詳細を表示して、下記の画面で許可します。

Alexaアプリでのアクセス権の設定

Alexaアプリでのアクセス権の設定

スキルに対する権限の設定(1)

スキルに対する権限の設定(1)

スキルに対する権限の設定(2)

スキルに対する権限の設定(2)

設定が済んだら、

アレクサ、今どこスキルを開いて

と話しかけてみてください。

郵便番号は○○○-○○○○

と返答があれば成功です。

所在地APIへの接続については、こちらにあるサンプルコードも参考になるでしょう。接続APIからの情報取得手順を便利なオブジェクトにまとめて実装しています。

https://github.com/alexa/skill-sample-node-device-address-api

次回は、スキル・サービス側のコードにフォーカスし、フレームワークをより詳細に理解するための情報を提供する予定です。それではお楽しみに。

著者
畠中 幸司(はたなか・こうじ)

音楽と自然と猫を愛するソフトウェア&インフラエンジニア。日本ヒューレット・パッカード株式会社でクラウドネイティブなアプリケーションのためのインフラ提案、および構築業務に従事。2000年にウェブスタートアップでエンジニアとしてのキャリアをスタートして以来、メガソフト株式会社の3Dマイホームデザイナーシリーズの開発や、マイクロソフト日本法人にて Windows Phone、Microsoft Officeシリーズの開発など、数多くの国内およびグローバルな開発プロジェクトに携わる。建設業向けモバイルアプリSTUCCO(スタッコ)のスタートアップ起業経験、500 KOBE Pre-Acceleratorへの参加等を経て2017年より現職。

連載バックナンバー

開発ツール技術解説
第6回

Alexa:ステートフルなサービスと聞き取りやすい音声応答

2018/9/20
連載6回目は、ステートフルなサービスの構築と、Alexaの音声応答を聞き取りやすくする手法を紹介します。
開発ツール技術解説
第5回

Alexaのためのローカル開発環境を整備する

2018/9/4
連載5回目は、より高度なプログラム作成に先駆けて、Alexaのためのローカル開発環境を整備していきます
開発ツール技術解説
第4回

Alexa Skills Kit SDK for Node.jsについて知る

2018/8/21
連載4回目は、スキル・サービス側のコードに着目して、フレームワークの理解を深めていきます。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています