【完結?】2時間でAlexaスキルを作ってみたけどできなかった話 その6

はじめに

  • 以前、これまで作成を進めてきた「面接練習」スキルをストアへ申請したところ、リジェクトされた
  • リジェクトされた理由が詳しくフィードバックされたため、対応を行なおうとしたが、「どのインテントにも該当しない場合には、StartIntentにマッチしてしまう」という問題が解消できないまま、一旦作業を中断していた
  • 解消方法を有識者から伺ったので、対応を再開することにした

やってみたこと

有識者からのアドバイスを整理

現状の対話モデルの設定などを見てもらったところ、次の問題が発生しているとのことだった

  • AMAZON.FallbackIntentが定義されていないので、StartIntentに該当しない場合に、FallbackIntentHandlerに処理が渡らない状態になっていた。

また、上記とは別に、Amazon.NoIntentやAmazon.YesIntent等を活用すればもっとシンプルになるのではということだった。

やることをチームで相談

前述の内容を踏まえて、チームでこれをやることにした

  • Amazon.FallbackIntentを対話モデルに追加
  • Amazon.NoIntentを対話モデルに追加
  • Amazon.YesIntentを対話モデルに追加
  • EtcIntentは対話モデルから削除
  • コードエディタ上で、StartIntentHandlerのcanHandle内の条件式を「Amazon. YesIntent」もしくは「StartIntent」にマッチするかどうか、という条件に変更

なお、ここに至るまでにいくつか躓いた点があった。

  • StartIntentを対話モデルから削除すると、モデルをビルドした際に次のエラーが表示された。カスタムインテントは1つ以上必要であるとのことだった。そのため、StartIntentは残すことにした
Interaction Model does not include Custom Intents. Add a Custom Intent with at least one Sample Utterance to your Interaction Model.
  • ケアレスミスで、canHandleの条件で「Amazon.YesIntent」ではなく「YesIntent」としていたため、マッチせずに「IntentReflectorHandler」が実行されてしまう状態になっていた。

コードの修正

最終的に、細かい修正も含めて、前回の申請でいただいたフィードバック(その5を参照)にすべて対応し、以下のようなコードとして再申請した。

コード

/* *
 * 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' || Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.YesIntent');
    },
    handle(handlerInput) {
        // 質問リスト
        const array = [
            "志望理由は何ですか?",
            "あなたの強みはなんですか?",
            "あなたの弱みはなんですか?",
            "最近苦労したことはなんですか?",
            "最近楽しかったことはなんですか?",
            "今後やりたいことはなんですか?",
            "何か質問ありますか?ふふふ",
            "いまのあなたの課題はなんですか?",
            "どのようなリーダーシップの経験がありますか?",
            "仕事で大事にしていることはなんですか?",
        ]
        const after = "では、お答えください。さようなら。"
        const min = Math.ceil(0);
        const max = Math.floor(array.length - 1);
        let randomInt = Math.floor(Math.random() * (max - min) + min);

        const speakOutput = array[randomInt] + after;

        return handlerInput.responseBuilder
            .speak(speakOutput)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};

const NoIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.NoIntent';
    },
    handle(handlerInput) {
        const speakOutput = "面接練習を終了します";

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


const HelpIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'このスキルでは、質問をランダムに生成します。質問を返した時点でこのスキルは終了します。開始する場合には、「面接練習をはじめて」と言ってください';

        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 = 'すみません、おっしゃっていることが理解できませんでした。このスキルでは、質問をランダムに生成します。質問を返した時点でこのスキルは終了します。開始する場合には、「面接練習をはじめて」と言ってください';

        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,
        HelpIntentHandler,
        StartIntentHandler,
        NoIntentHandler,
        CancelAndStopIntentHandler,
        FallbackIntentHandler,
        SessionEndedRequestHandler,
        IntentReflectorHandler)
    .addErrorHandlers(
        ErrorHandler)
    .withCustomUserAgent('sample/hello-world/v1.2')
    .lambda();

最後に

これで申請が通るかどうか、待つ。

参考

特に無し。

というか、ドキュメントからうまく必要な情報を探せなかった。結構手探りで解決してた。

コメントを残す

メールアドレスが公開されることはありません。