LoginSignup
12
11

More than 3 years have passed since last update.

AlexaとAWS Lambdaで遊ぶ

Posted at

はじめに

何かしらAWSのサービスを使ってみたいと考えていたところ、身内がビンゴ大会でゲットしたAmazon Echoがあることを思い出しました。
Amazon Echoと言えばAlexa。というわけで、Alexaで遊びつつAWSを使ってみることにしました。

Alexaのチュートリアル

Amazonの公式ドキュメントに沿って進めると、チュートリアルができます。
アカウントの作成方法から実機のセットアップ方法まで書いてあり、とても親切でした。
https://developer.amazon.com/ja/blogs/alexa/post/31c9fd71-f34f-49fc-901f-d74f4f20e28d/alexatraining-firstskill

AWSを使う

チュートリアルに従って、早速スキルが作れました。
しかし、バックエンドはAlexaにホストしてもらったため、自分ではAWSにログインすらしていません。
このままではAWSのサービスの使い方を分からずに終わってしまう……というわけで、次は自分のAWS Lambdaを使ってスキルを開発してみます。

スキルの作成

alexa developer console の右上にある「スキルの作成」をクリックします。
01.png

スキル名を入力し、スキルに追加するモデルで「カスタム」を、バックエンドリソースをホスティングする方法で「ユーザ定義のプロビジョニング」を選択します。
せっかくなら楽しく開発したいので、自分の好きな道路関係のスキルを作ることにします。
02.png

「スキルを作成」をクリックして少し待つと、以下の画面が表示されます。
右側のチェックリストにチェックマークが付くよう、設定していきます。
02-1.png

呼び出し名を設定します。
呼び出し名とは、「Alexa、○○を開いて××をして」の「○○」にあたる文言です。
Alexaへの呼びかけについては、公式ブログ に説明があります。
03.png

インテントを追加します。
04.png

「カスタムインテントを作成」をクリックすると以下の画面が表示されるので、サンプル発話を追加します。
サンプル発話とは、「Alexa、○○を開いて××をして」の「××をして」にあたる文言です。
各都道府県を走る国道を答えさせたいため、サンプルとして鳥取県を追加します。
05.png

ここまで終わったら、画面上部にある「モデルのビルド」をクリックします。

Lambdaで関数の作成

ここで一旦alexa developer consoleからは離れ、AWS Lambdaでバックエンド側を作成していきます。
AWSからLambdaのページを開き、「関数の作成」をクリックします。
06.png

「Serverless Application Repositoryの参照」を選択し、下部に表示される一覧から「alexa-skills-kit-nodejs-factskill」を選択します。
07.png

アプリケーション名を入力し、「デプロイ」をクリックします。
08.png

デプロイ完了後、サイドメニューの「関数」をクリックし新たに作られた関数を選択します。
選択すると以下の画面が表示されるので、ここでコードを修正していきます。修正後は「保存」をクリックして保存します。
09.png


コード全容
// /* eslint-disable  func-names */
// /* eslint-disable  no-console */

const Alexa = require('ask-sdk');

const LaunchHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'LaunchRequest';
  },
  handle(handlerInput) {
    const launchMessage = 'ようこそ。知りたい都道府県を言ってみてください。';
    return handlerInput.responseBuilder
      .speak(launchMessage)
      .reprompt(launchMessage)
      .getResponse();
  },
};

const PrefectureIntentHandler = {
    canHandle(handlerInput) {
        const request = handlerInput.requestEnvelope.request;
        return request.type === 'IntentRequest'
          && request.intent.name === 'PrefectureIntent';
    },
    handle(handlerInput) {
        const routeNoMessage = '9号、29号、53号、178号、179号、180号、181号、183号、313号、373号、431号、482号です';
        return handlerInput.responseBuilder
            .speak(routeNoMessage)
            .getResponse();
    }
};

const HelpHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
      && request.intent.name === 'AMAZON.HelpIntent';
  },
  handle(handlerInput) {
    return handlerInput.responseBuilder
      .speak(HELP_MESSAGE)
      .reprompt(HELP_REPROMPT)
      .getResponse();
  },
};

const ExitHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
      && (request.intent.name === 'AMAZON.CancelIntent'
        || request.intent.name === 'AMAZON.StopIntent');
  },
  handle(handlerInput) {
    return handlerInput.responseBuilder
      .speak(STOP_MESSAGE)
      .getResponse();
  },
};

const SessionEndedRequestHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'SessionEndedRequest';
  },
  handle(handlerInput) {
    console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`);

    return handlerInput.responseBuilder.getResponse();
  },
};

const ErrorHandler = {
  canHandle() {
    return true;
  },
  handle(handlerInput, error) {
    console.log(`Error handled: ${error.message}`);

    return handlerInput.responseBuilder
      .speak('Sorry, an error occurred.')
      .reprompt('Sorry, an error occurred.')
      .getResponse();
  },
};

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 skillBuilder = Alexa.SkillBuilders.standard();

exports.handler = skillBuilder
  .addRequestHandlers(
    LaunchHandler,
    PrefectureIntentHandler,
    HelpHandler,
    ExitHandler,
    SessionEndedRequestHandler
  )
  .addErrorHandlers(ErrorHandler)
  .lambda();


この関数を使用できるスキルを制限するために、トリガーとして設定されている「Alexa Skills Kit」を一度削除します。
その後、「トリガーを追加」から改めて「Alexa Skills Kit」を選択します。
10.png
11.png
スキル検証を設定し、追加します。
12.png
スキルIDは、alexa developer consoleの「エンドポイント」から確認できます。
13-1.png

これでLambdaの関数の作成と設定は完了です。

エンドポイントの設定

最後に、alexaが呼び出すエンドポイントを設定します。
スキルIDを確認した画面と同じ画面の「デフォルトの地域」で、エンドポイントを設定できます。
13-2.png
Lambdaで作成した関数の画面にある赤枠の部分を、上記画面に設定します。
14.png

「エンドポイントを保存」をクリックし、「テスト」タブから試してみると……
15.png
答えてくれました:thumbsup:

ちなみに、コードのエラーはCloudWatchのログで確認できます。
Alexaのテストをしてうまく動かない場合は、こちらを確認すると何が起こっているのか分かります。

また、alexa developer consoleでテストをする際に、キャッシュが残っている(?)のかLambda側を修正しても反映されていないことがありました。

おわりに

思っていたより手軽にできました!ブラウザでテストもできるので便利ですね。
LambdaやCloudWatchには無制限無料枠があり、こうやって遊ぶ程度では無料の範囲内に収まるはずなので気軽に試せました。
今は一覧と言っておきながら鳥取県を通る国道しか答えてくれないので、各都道府県を通る国道を答えてくれるようにしたいです。

参考

12
11
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
12
11