2時間でAlexaスキルを作ってみたけどできなかった話

はじめに

Alexa道場で勉強を進め、基本的なスキルを作成する方法を学んだため、一度オリジナルスキルを作って復習&知識の定着を図ってみることにした。

結論、2時間じゃ無理だったので、続きはまた今度やる。

やってみたこと

計画を立ててみた

  1. 何作るべきか考える(30分)
    1. 自分が何欲しいか考えてみる(5分)
    2. 自分以外に欲しい人がいそうか考えてみる(5分)
    3. 似たようなモノで、市場で必要とされているものを調べてみる(10分)
    4. 自分が作る方向性を決める(10分)
  2. モックを作る
    1. 設計する(30分)
    2. 実装する(60分)

21:00 – 21:30 アイディア出しをしてみた

自分たちが欲しい物を色々と考えてみる。思いついたのは以下。

  • バーチャル面接官(加藤)
     →志望動機はなんですか?とか聞いてくる
      →しゃべったことをテキスト化して、あとで回答が見れる
     →何秒間堪えられなかったか記録
      →何秒空白があったか記録(くま)/(けん)

21:30 – 21:40 似たようなモノで、市場で必要とされているものを調べてみる

  • Googleで検索してみた
    • Amazon.co.jp: 圧迫面接 : Alexaスキル https://www.amazon.co.jp/Ga9-%E5%9C%A7%E8%BF%AB%E9%9D%A2%E6%8E%A5/dp/B07BQHXLJF
    • ベリーグッドは「Amazon Alexa」に対応する面接練習用の「面接シミュレーション」をリリース。 | 株式会社ベリーグッド https://www.verygoodinc.biz/archives/293
       →今は存在しない?
  • その他、スキルストアで検索してみた。結果は次の通り

スキルストアには面接系のAlexaアプリはあまりなさそうだった。

なので、「圧迫面接」じゃない方向性で面接対策スキルを作るとよさそう、という話になった。

21:37 – 22:12 方向性の話し合いをしてみる

誰に向けて作るか話し合ってみた。結果は以下。

  • for ユーザ
     →バイトの面接を控えている学生や、転職活動したい社会人の練習台になる
  • for 自分たち
     →Alexaの勉強になる
     →(儲からなくてもいい)

どんな質問がされるといいか調べながら話し合ってみた。結果は以下の記事にあるような方向性

  • 面接対策まとめ|よくある質問の回答例、準備~面接後のやりとり・流れ|マイナビ転職 https://tenshoku.mynavi.jp/knowhow/mensetsu
      →「面接でよくある質問の回答50例」の内容をAlexaに仕込んで見る
      →とりあえず「自己紹介」と「志望理由」

22:12 – 22:45 設計

ユーザにどうなってもらうかを考えてみた。結果は以下。

  • 想定ユーザ:けんとくんみたいなアルバイトの面接控えている、ガジェット好きな学生
  • ユーザの動き:
      ・(そもそもAlexaで面接したい、って思わせる何か)
       →Alexaで面接できるという認知をさせる?
      ・ユーザが面接の練習をAlexaでできないかな?と思う
      ・ユーザがAlexaストアで「面接」で検索する
       →Alexaストアに登録する必要あり
      ・ユーザがスキルの説明を見て、使いたいと思う
       →説明ちゃんと入れる必要あり
      ・ユーザがAlexaスキルをインストールする
      ・ユーザがAlexaでウェイクワードを発する
      ・Alexaが、ユーザは何をすればいいかを説明する
       →「これから面接練習をします。質問に回答してください。よろしいですか?」
      ・ユーザが「はい」と答える
      ・Alexaが最初の質問をする
      ・ユーザが最初の質問に答える
      ・Alexaが次の質問をする
      ・ユーザが次の質問に答える
      〜ループ〜
      ・Alexaが最後の質問をする
      ・ユーザが最後の質問に答える
      ・Alexaが、すべての質問に回答し終わったことを伝える
      ・Alexaが、どこかで回答内容が確認できるようにする
       →例えば、テキスト化して、どこかに置いておく
      ・Alexaが、回答内容の確認方法をユーザに伝える

どんな実装作業が必要か考えてみた。結果は以下。

  • じゃあどんな機能とかが必要か
    • ウェイクワードを設定する
      • スキル名、アイコン、とか・・
    • インテントを作る
      • 「初めて」インテント
    • コードエディタで編集する
      • 起動時に最初に呼ばれるリクエストハンドラを作る
        • ユーザは何をすればいいかを説明「これから面接練習をします。質問に回答してください。よろしいですか?」
      • 質問リクエストハンドラを作る
        • 質問を順番に行う
        • 回答内容を記録する
        • 最後の質問が終わったら、説明をする
          • どこかで回答内容が確認できるようにする

22:45 – 23:00 作り始めてみた

Developer Consoleで新しいスキルを作成

  • Alexa-hosted(Node.js)
  • スクラッチ

スキルの呼び出し名を設定

インテントを作成

  • StartIntentというインテントを作る

リクエストハンドラなどをコードエディタで実装(途中まで)

初めに呼ばれるリクエストハンドラと、StartIntentのハンドラを実装。

こんな感じ

/* *
 * This sample demonstrates handling intents from an Alexa skill using the Alexa Skills Kit SDK (v2).
 * Please visit https://alexa.design/cookbook for additional examples on implementing slots, dialog management,
 * session persistence, api calls, and more.
 * */
const Alexa = require('ask-sdk-core');

const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
    },
    handle(handlerInput) {
        const speakOutput = 'これから面接練習をします。質問に回答してください。よろしいですか?';

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

const StartIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'StartIntent';
    },
    handle(handlerInput) {
        // 流れ

    // 1回目の場合(引数が空の場合):
         // 質問をする
         // 回答をテキストで、どこかに記録する
         // StartIntentHandlerをまた呼び出す()
    // 2回目の場合(引数が空でない場合):
         // 質問をする
         // 回答をテキストで、どこかに記録する
         // 最後の質問だったかどうかを判別する
            // 最後の質問だった場合
               // 最後の処理をする
            // 最後の質問じゃなかった場合
                   // StartIntentHandlerをまた呼び出す(次の質問を引数に入れる)

        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 HelloWorldIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'Hello World!';

        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 = 'You can say hello to me! How can I help?';

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

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 = 'Goodbye!';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .getResponse();
    }
};
/* *
 * FallbackIntent triggers when a customer says something that doesn’t map to any intents in your skill
 * It must also be defined in the language model (if the locale supports it)
 * This handler can be safely added but will be ingnored in locales that do not support it yet 
 * */
const FallbackIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.FallbackIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'Sorry, I don\'t know about that. Please try again.';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};
/* *
 * SessionEndedRequest notifies that a session was ended. This handler will be triggered when a currently open 
 * session is closed for one of the following reasons: 1) The user says "exit" or "quit". 2) The user does not 
 * respond or says something that does not match an intent defined in your voice model. 3) An error occurs 
 * */
const SessionEndedRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
    },
    handle(handlerInput) {
        console.log(`~~~~ Session ended: ${JSON.stringify(handlerInput.requestEnvelope)}`);
        // Any cleanup logic goes here.
        return handlerInput.responseBuilder.getResponse(); // notice we send an empty response
    }
};
/* *
 * The intent reflector is used for interaction model testing and debugging.
 * It will simply repeat the intent the user said. You can create custom handlers for your intents 
 * by defining them above, then also adding them to the request handler chain below 
 * */
const IntentReflectorHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest';
    },
    handle(handlerInput) {
        const intentName = Alexa.getIntentName(handlerInput.requestEnvelope);
        const speakOutput = `You just triggered ${intentName}`;

        return handlerInput.responseBuilder
            .speak(speakOutput)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};
/**
 * Generic error handling to capture any syntax or routing errors. If you receive an error
 * stating the request handler chain is not found, you have not implemented a handler for
 * the intent being invoked or included it in the skill builder below 
 * */
const ErrorHandler = {
    canHandle() {
        return true;
    },
    handle(handlerInput, error) {
        const speakOutput = 'Sorry, I had trouble doing what you asked. Please try again.';
        console.log(`~~~~ Error handled: ${JSON.stringify(error)}`);

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

/**
 * This handler acts as the entry point for your skill, routing all request and response
 * payloads to the handlers above. Make sure any new handlers or interceptors you've
 * defined are included below. The order matters - they're processed top to bottom 
 * */
exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
        LaunchRequestHandler,
        StartIntentHandler,
        HelloWorldIntentHandler,
        HelpIntentHandler,
        CancelAndStopIntentHandler,
        FallbackIntentHandler,
        SessionEndedRequestHandler,
        IntentReflectorHandler)
    .addErrorHandlers(
        ErrorHandler)
    .withCustomUserAgent('sample/hello-world/v1.2')
    .lambda();

動かしてみる

テストしてみる。モックがとりあえず動いた。

最後に

方向性は決まったものの、スキルとして必要最低限の機能はまだまだ足りない。

とりあえず、次は固定の質問をして、回答内容をテキスト化して、どこかでユーザが参照できるようにする。

参考

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です