AWS Lambda で Alexa スキル開発 (Hello World 編)

blog.kondoumh.com

Echo Dot 買ったら Alexa スキルで Voice UI 開発ということで Hello World してみました。

Alexa スキルは AWS Lambda で実装しますが、以前 Web IDE の Cloud 9 を試すときに AWS アカウントを作っていたのでしばらく無料で試せます。

blog.kondoumh.com

Alexa Skills のエンドポイント作成とテストは AWS ではなく Amazon Developer のサイトで行います。AWS 側では Lambda 関数を作って Alexa とのやり取り部分を実装します。

developer.amazon.com

Amazon Developer では普段使ってる Amazon のアカウントでログインすべきで、新しくアカウントを作るとハマるそうです。

Alexa 開発者アカウント作成時のハマりどころ : Alexa Blogs

ブラウザのタブを2枚開いて Alexa Developer Console (Amazon アカウント) と Lambda Management Console (IAM ユーザ) を交互に操作して開発を進めます。

そもそもクラウド開発に不慣れなオンプレミスディベロッパーなので Hello World もすんなり行きませんでした。公式チュートリアルを見ても AWS の設定画面の方が更新されてますし。権限もよくわかっておらず・・・。

AWS では作業用アカウントはルートアカウントではなく、必要な権限のみ付与した IAM ユーザとして作成するのがベストプラクティスとされています。前回 Cloud 9 を使うために IAM ユーザのグループに AWSCloud9User を割り当てていて、今回新たに AWSLambdaFullAccess を追加しました。これで Lambda の機能はフルに使えるはずですが、Lambda Management Console で 関数を作ろうとしてると権限が足りないエラーが。

not authorized to perform: iam:CreateRole on resource: role/lambda_basic_execution

エラーが出る度にびルートで IAM Console にログインして権限を追加。権限設定ができるまでは IAM ユーザでログインしたメインのブラウザ(Chrome など)と IAM Console にログインしたサブのブラウザ(Safari など)を起動した方が楽です*1 。結局、グループに割り当てた AWSLambdaFullAccess に加えて、インラインポリシーとして iam:PutRolePolicyiam:CreateRole を追加する必要がありました。

f:id:kondoumh:20180406151602p:plain

これで Lambda 関数の追加ができるようになりました。今回は設計図(テンプレート)から作成を選びました。

f:id:kondoumh:20180406152359p:plain

設計図としては、alexa-skill-kit-sdk-factskill を選択。

Lambda の編集画面には Cloud 9 のエディタがプロジェクトごと埋め込まれていました (フルスクリーンにして利用可能です)。

f:id:kondoumh:20180406154409p:plain

ひとまず Lambda 関数を作成したら、Alexa Developer Console 側でスキルの対話モデルを定義します。まずスキルの呼び出し名をつけてインテントを追加します。テンプレートが太陽系の惑星についての豆知識を教えてくれるサンプルになっていたので、「惑星くん」と名付けました。インテントは適当に Sandbox としておきました。

f:id:kondoumh:20180406153313p:plain

サンプル発話の登録画面です。今回は適当に1個追加したのみです。

f:id:kondoumh:20180406153655p:plain

対話モデルを 「JSON エディター」で見ると以下のようになりました。

{
    "interactionModel": {
        "languageModel": {
            "invocationName": "惑星くん",
            "intents": [
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "Sandbox",
                    "slots": [],
                    "samples": [
                        "教えて"
                    ]
                }
            ],
            "types": []
        }
    }
}

次に、スキルのエンドポイントの設定です。Lambda Management Console で作成した関数の ARN を設定します。

f:id:kondoumh:20180406154156p:plain

Lambda Management Console の Cloud 9 エディタで関数を編集します。

'use strict';
const Alexa = require('alexa-sdk');
const APP_ID = 'amzn1.ask.skill.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
const SKILL_NAME = 'MyFactSkill';
const GREETING_MESSAGE = "オーケー: ";
const HELP_MESSAGE = 'You can say tell me a space fact, or, you can say exit... What can I help you with?';
const HELP_REPROMPT = 'What can I help you with?';
const STOP_MESSAGE = 'Goodbye!';

const data = [
    '水星の1年は88日だよ。',
    '太陽からの距離は水星より遠いにもかかわらず、金星は水星より気温が高いよ。',
    '金星は反時計回りに回転するよ。おそらく過去の小惑星との衝突のせいだね。',
    '火星では、太陽は地球上で見るのと半分ぐらいの大きさだよ。',
    '地球は神にちなんで命名されていない唯一の惑星だよ。',
    '木星はすべての惑星の中でもっとも1日が短いよ。',
    '天の川銀河は約50億年後にアンドロメダ銀河と衝突するよ。',
    '太陽だけで太陽系の質量の99.86%を占めてるんだよ。',
    '太陽はほぼ完璧な球体だよ。',
    '皆既日食は1〜2年に一度発生するよ。これはとても珍しい事象だよ。',
    '土星は、太陽から受けるより2.5倍のエネルギーを宇宙空間に放射してるんだよ。',
    '太陽の内部温度は摂氏1500万度に達することがあるよ。',
    '月は我々の惑星から毎年約3.8cm離れていくよ。',
];

const handlers = {
    'LaunchRequest': function () {
        this.emit('GetNewFactIntent');
    },
    'GetNewFactIntent': function () {
        const factArr = data;
        const factIndex = Math.floor(Math.random() * factArr.length);
        const randomFact = factArr[factIndex];
        const speechOutput = GREETING_MESSAGE + randomFact;

        this.response.cardRenderer(SKILL_NAME, randomFact);
        this.response.speak(speechOutput);
        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');
    },
};

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

ほとんどテンプレートのまま。豆知識を翻訳しただけです。handlers の LaunchRequest プロパティが Amazon Echo からのエントリーポイントになります。GetNewFactIntent プロパティの function を呼び出して、応答を生成し Alexa に返しています。

Alexa Developer Console のテスト画面で Alexa にスキルを呼び出してもらうことができます。PC のマイクを有効にすれば、自分の声で話しかけることもできますし、チャットの UI にテキストを打ち込んで送信することも可能です。

「アレクサ 惑星くんを開いて」と送信したところ無事に惑星くんのスキルを開いて応答を返してくれました。

f:id:kondoumh:20180406155413p:plain

スキルI/O (Alexa へのリクエストとレスポンス) の JSON も確認できます。

ここまでくれば、開発者アカウントに紐付いた Amazon Echo の実機で作成したスキルを試せます。あっさり Echo Dot から惑星くんが起動できたのでちょっと驚きました*2

最初は権限周りでちょっと戸惑いましたが、これでスキル開発に取りかかれそうです。それにしてもサーバーレスな AWS Lambda は Alexa のスキル実行基盤として最適ですね。

*1:IAM ユーザとルートアカウントは同時ログインできないため

*2:他の人に使ってもらうには ベータテストの設定が必要のようです。