Stack Overflow: A New Way for Developers to Get Support for Alexa

Now you can find Alexa-specific tags and topics in the AWS Collective on Stack Overflow. Ask questions and get help from badged experts and the Alexa developer community!

question

Aidan avatar image
Aidan asked

Unable to chain: a Launch Request Intent to a Custom Intent

Hello,

I am making my first skill on Alexa. I've been persevering with trying to get intent chaining to work from a launch request without success.


The conversation flow I'd like (if possible) is as follows : (Note: I'm open to alternate suggestions!)

"Open Solent Weather" triggers either of the following launch requests:

  • If Users first time using skill --> Request that they choose a default weather station and store this in S3. (I've got this part working!)
  • If User has used skill before (a chosen weather station has been found in S3) --> this launch request will direct to the relevant intent (delegate directive) to read the data for that weather station. (Doesn't work yet.....)

At present, the skill successfully retrieves the users chosen default weather station from S3 storage, and triggers the WelcomeBack launch request. Alexa Responds:

"Welcome back, your weather station is {chosenWeatherStation}. Here is your full report".

However, nothing happens after. Alexa just leaves me hanging.......


Any help with this challenge would be much appreciated!


Many thanks in advance,

Aidan


Here is my code so far:


const Alexa = require('ask-sdk-core');
const persistenceAdapter = require('ask-sdk-s3-persistence-adapter');
// ********* Intents ************************
const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; 
        //This executes only if a 'defaultWeatherStation' could not be found in S3, such as 1st time using the skill.
    },
    handle(handlerInput) {
        const speechText = 'Welcome to Solent Weather. I can give the current weather conditions from Chai Met, CamberMet, BrambleMet or SotonMet. Which weather station would you like?';
        const repromptText = 'Please choose a weather station. You can say: Chai Met (Chichester Bar), Cambermet (Chichester Harbour), BrambleMet (Central Solent) or SotonMet (Southampton).';
        return handlerInput.responseBuilder
            .speak(speechText)
            .reprompt(repromptText)
            .addElicitSlotDirective('weatherStation', {
                name: 'SetDefaultWeatherStation',
                confirmationStatus: 'NONE',
                slots: {}
            })
            .getResponse();
    }
};
const HasDefaultWeatherStationLaunchRequestHandler = {
    canHandle(handlerInput) {
        const attributesManager = handlerInput.attributesManager;
        const sessionAttributes = attributesManager.getSessionAttributes() || {};
        const chosenWeatherStation = sessionAttributes.hasOwnProperty('defaultWeatherStation') ? sessionAttributes.defaultWeatherStation : null;
        
        return handlerInput.requestEnvelope.request.type === 'LaunchRequest' &&
            chosenWeatherStation; //executes when user has specified a chosen weather station
    },
    handle(handlerInput) {
        const attributesManager = handlerInput.attributesManager;
        const sessionAttributes = attributesManager.getSessionAttributes() || {};
        const chosenWeatherStation = sessionAttributes.hasOwnProperty('defaultWeatherStation') ? sessionAttributes.defaultWeatherStation : null;
        const speechText = `Welcome back, your weather station is: ${chosenWeatherStation}. Here is a full report`; 
        
        return handlerInput.responseBuilder
            .speak(speechText)
            .addDelegateDirective({
                name: chosenWeatherStation,
                confirmationStatus: 'NONE',
                slots: {}
             })
            .getResponse(); 
            //redirects to relevant weather station.
    }
};
const SetDefaultWeatherStationIntentHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
            && handlerInput.requestEnvelope.request.intent.name === 'SetDefaultWeatherStation';
    },
    async handle(handlerInput) {
        const chosenWeatherStation = handlerInput.requestEnvelope.request.intent.slots.weatherStation.resolutions.resolutionsPerAuthority[0].values[0].value.name;
        
        const attributesManager = handlerInput.attributesManager;
        const userAttributes = {
            "defaultWeatherStation" : chosenWeatherStation
        };
        attributesManager.setPersistentAttributes(userAttributes);
        await attributesManager.savePersistentAttributes();
        const speechText = `Thanks, I'll remember your default weather station is: ${chosenWeatherStation}`;
        return handlerInput.responseBuilder
            .speak(speechText)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .withShouldEndSession(true)
            .getResponse();
    }
};
const ChiMetIntentHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
            && handlerInput.requestEnvelope.request.intent.name === 'ChiMet';
    },
    handle(handlerInput) {
        const speechText = 'Chai Met Data......';
        return handlerInput.responseBuilder
            .speak(speechText)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};
const CamberMetIntentHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
            && handlerInput.requestEnvelope.request.intent.name === 'CamberMet';
    },
    handle(handlerInput) {
        const speechText = 'Camber Met Data......';
        return handlerInput.responseBuilder
            .speak(speechText)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};
const BrambleMetIntentHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
            && handlerInput.requestEnvelope.request.intent.name === 'BrambleMet';
    },
    handle(handlerInput) {
        const speechText = 'Bramble Met Data......';
        return handlerInput.responseBuilder
            .speak(speechText)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};
const SotonMetIntentHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
            && handlerInput.requestEnvelope.request.intent.name === 'SotonMet';
    },
    handle(handlerInput) {
        const speechText = 'Soton Met Data.....';
        return handlerInput.responseBuilder
            .speak(speechText)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};
..............
const LoadDefaultWeatherStationInterceptor = {
    async process(handlerInput) {
        const attributesManager = handlerInput.attributesManager;
        const sessionAttributes = await attributesManager.getPersistentAttributes() || {};
        
        const defaultWeatherStation = sessionAttributes.hasOwnProperty('defaultWeatherStation') ? sessionAttributes.defaultWeatherStation : null;
        if (defaultWeatherStation) {
            attributesManager.setSessionAttributes(sessionAttributes);
        }
    }
};
// 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()
    .withPersistenceAdapter(
        new persistenceAdapter.S3PersistenceAdapter({bucketName:process.env.S3_PERSISTENCE_BUCKET})
        )
    .addRequestHandlers(
        HasDefaultWeatherStationLaunchRequestHandler,
        LaunchRequestHandler,
        SetDefaultWeatherStationIntentHandler,
        ChiMetIntentHandler,
        CamberMetIntentHandler,
        BrambleMetIntentHandler,
        SotonMetIntentHandler,
        HelpIntentHandler,
        CancelAndStopIntentHandler,
        SessionEndedRequestHandler,
        IntentReflectorHandler
    ) // make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers
    .addRequestInterceptors(
        LoadDefaultWeatherStationInterceptor
    )
    .addErrorHandlers(
        ErrorHandler
    )
    .lambda();


alexa skills kitskillalexanodejsalexa skills challenge
3 comments
10 |5000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Aidan avatar image Aidan commented ·

Skill ID

amzn1.ask.skill.13234255-c392-4e42-a252-0c7e7b3bc163

0 Likes 0 ·
Aidan avatar image Aidan commented ·

Is this intended behaviour by Amazon that I cannot chain an intent request from my launch request for a returning user to my skill?

0 Likes 0 ·
Aidan avatar image Aidan commented ·

Can anyone point me in the right direction?

0 Likes 0 ·

1 Answer

Amazon_Bernardo Bezerra avatar image
Amazon_Bernardo Bezerra answered

Hello @Aidan and thank you for your message.

Sorry it took so long to get back to you.

When executing the HasDefaultWeatherStationLaunchRequestHandler your code is checking for information in the session attributes, which are always cleared in between sessions. However, in your SetDefaultWeatherStationIntentHandler, you are writing the information to the persistent attributes (aka, S3), which is how you manage to persist data between sessions.

My suggestion for you is to change your HasDefaultWeatherStationLaunchRequestHandler to query persistent attributes, instead of session attributes.

Regards,
Barry

10 |5000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.