Sviluppo remoto su AWS: da Cloud9 a VS Code
20 Novembre 2024 - 2 min. read
Alessio Gandini
Cloud-native Development Line Manager
{ "interactionModel": { "languageModel": { "invocationName": "saluti", "intents": [ { "name": "AMAZON.CancelIntent", "samples": [ "no", "niente", "non fa niente", "lascia perdere", "annulla" ] }, { "name": "AMAZON.HelpIntent", "samples": [] }, { "name": "AMAZON.StopIntent", "samples": [ "abbandona", "esci", "fine", "stop" ] }, { "name": "Salutare", "slots": [], "samples": [ "salutarmi", "salutami", "saluti", "ciao" ] } ], "types": [] } } }Questo modello definisce un solo intento con identificativo “Salutare”, oltre agli intenti standard di Amazon “cancel”, “stop” ed “help”. Non sono necessari parametri, e sono fornite poche frasi di esempio.Nella sua semplicità, si tratta di un modello funzionante ed in grado di assolvere allo scopo di farsi salutare da Alexa.Manca tuttavia il back-end, ovvero il servizio che è in grado di rispondere alle richieste che Alexa esegue una volta individuato l’intento.Supponendo di utilizzare AWS Lambda, ecco uno snippet per rispondere correttamente alle chiamate relative all’intento definito sopra. Si tratta di una funzione Nodejs, il cui codice utilizza l’sdk di Alexa per node. Abbiamo evidenziato la funzione che viene invocata per l’intento “Salutare” in rosso.Il resto del codice è sostanzialmente solo boilerplate e rimane quasi completamente invariato anche in caso di skill più complesse.
let speechOutput; let reprompt; let welcomeOutput = "Benvenuto in 'Ciao' in italiano. Puoi salutarmi per essere salutato a tua volta"; let welcomeReprompt = "puoi solo dire 'ciao' per essere salutato"; "use strict"; const Alexa = require('alexa-sdk'); const APP_ID = undefined; speechOutput = ''; const handlers = { 'LaunchRequest': function() { this.emit(':ask', welcomeOutput, welcomeReprompt); }, 'AMAZON.HelpIntent': function() { speechOutput = 'Placeholder response for AMAZON.HelpIntent.'; reprompt = ''; this.emit(':ask', speechOutput, reprompt); }, 'AMAZON.CancelIntent': function() { speechOutput = 'Ok, annullato’; this.emit(':tell', speechOutput); }, 'AMAZON.StopIntent': function() { speechOutput = 'Arrivederci'; this.emit(':tell', speechOutput); }, 'SessionEndedRequest': function() { speechOutput = ''; this.emit(':tell', speechOutput); }, 'Salutare': function() { speechOutput = ''; speechOutput = "Ciao! Questo è tutto quello che puoi fare per ora”; this.emit(":tell", speechOutput, speechOutput); }, 'Unhandled': function() { speechOutput = "Non ho capito. Riprova"; this.emit(':ask', speechOutput, speechOutput); } }; exports.handler = (event, context) => { const alexa = Alexa.handler(event, context); alexa.appId = APP_ID; // To enable string internationalization (i18n) features, set a resources object. //alexa.resources = languageStrings; alexa.registerHandlers(handlers); //alexa.dynamoDBTableName = 'DYNAMODB_TABLE_NAME'; //uncomment this line to save attributes to DB alexa.execute(); }; // END of Intent Handlers {} ======================================================================================== // 3. Helper Function ================================================================================================= function resolveCanonical(slot) { //this function looks at the entity resolution part of request and returns the slot value if a synonyms is provided let canonical; try { canonical = slot.resolutions.resolutionsPerAuthority[0].values[0].value.name; } catch (err) { console.log(err.message); canonical = slot.value; }; return canonical; }; function delegateSlotCollection() { console.log("in delegateSlotCollection"); console.log("current dialogState: " + this.event.request.dialogState); if (this.event.request.dialogState === "STARTED") { console.log("in Beginning"); let updatedIntent = null; // updatedIntent=this.event.request.intent; //optionally pre-fill slots: update the intent object with slot values for which //you have defaults, then return Dialog.Delegate with this updated intent // in the updatedIntent property //this.emit(":delegate", updatedIntent); //uncomment this is using ASK SDK 1.0.9 or newer //this code is necessary if using ASK SDK versions prior to 1.0.9 if (this.isOverridden()) { return; } this.handler.response = buildSpeechletResponse({ sessionAttributes: this.attributes, directives: getDialogDirectives('Dialog.Delegate', updatedIntent, null), shouldEndSession: false }); this.emit(':responseReady', updatedIntent); } else if (this.event.request.dialogState !== "COMPLETED") { console.log("in not completed"); // return a Dialog.Delegate directive with no updatedIntent property. //this.emit(":delegate"); //uncomment this is using ASK SDK 1.0.9 or newer //this code necessary is using ASK SDK versions prior to 1.0.9 if (this.isOverridden()) { return; } this.handler.response = buildSpeechletResponse({ sessionAttributes: this.attributes, directives: getDialogDirectives('Dialog.Delegate', null, null), shouldEndSession: false }); this.emit(':responseReady'); } else { console.log("in completed"); console.log("returning: " + JSON.stringify(this.event.request.intent)); // Dialog is now complete and all required slots should be filled, // so call your normal intent handler. return this.event.request.intent; } } function randomPhrase(array) { // the argument is an array [] of words or phrases let i = 0; i = Math.floor(Math.random() * array.length); return (array[i]); } function isSlotValid(request, slotName) { let slot = request.intent.slots[slotName]; //console.log("request = "+JSON.stringify(request)); //uncomment if you want to see the request let slotValue; //if we have a slot, get the text and store it into speechOutput if (slot && slot.value) { //we have a value in the slot slotValue = slot.value.toLowerCase(); return slotValue; } else { //we didn't get a value in the slot. return false; } } //These functions are here to allow dialog directives to work with SDK versions prior to 1.0.9 //will be removed once Lambda templates are updated with the latest SDK function createSpeechObject(optionsParam) { if (optionsParam && optionsParam.type === 'SSML') { return { type: optionsParam.type, ssml: optionsParam['speech'] }; } else { return { type: optionsParam.type || 'PlainText', text: optionsParam['speech'] || optionsParam }; } } function buildSpeechletResponse(options) { let alexaResponse = { shouldEndSession: options.shouldEndSession }; if (options.output) { alexaResponse.outputSpeech = createSpeechObject(options.output); } if (options.reprompt) { alexaResponse.reprompt = { outputSpeech: createSpeechObject(options.reprompt) }; } if (options.directives) { alexaResponse.directives = options.directives; } if (options.cardTitle && options.cardContent) { alexaResponse.card = { type: 'Simple', title: options.cardTitle, content: options.cardContent }; if (options.cardImage && (options.cardImage.smallImageUrl || options.cardImage.largeImageUrl)) { alexaResponse.card.type = 'Standard'; alexaResponse.card['image'] = {}; delete alexaResponse.card.content; alexaResponse.card.text = options.cardContent; if (options.cardImage.smallImageUrl) { alexaResponse.card.image['smallImageUrl'] = options.cardImage.smallImageUrl; } if (options.cardImage.largeImageUrl) { alexaResponse.card.image['largeImageUrl'] = options.cardImage.largeImageUrl; } } } else if (options.cardType === 'LinkAccount') { alexaResponse.card = { type: 'LinkAccount' }; } else if (options.cardType === 'AskForPermissionsConsent') { alexaResponse.card = { type: 'AskForPermissionsConsent', permissions: options.permissions }; } let returnResult = { version: '1.0', response: alexaResponse }; if (options.sessionAttributes) { returnResult.sessionAttributes = options.sessionAttributes; } return returnResult; } function getDialogDirectives(dialogType, updatedIntent, slotName) { let directive = { type: dialogType }; if (dialogType === 'Dialog.ElicitSlot') { directive.slotToElicit = slotName; } else if (dialogType === 'Dialog.ConfirmSlot') { directive.slotToConfirm = slotName; } if (updatedIntent) { directive.updatedIntent = updatedIntent; } return [directive]; }In questo articolo abbiamo trattato i concetti fondamentali che stanno alla base del funzionamento di Alexa. Abbiamo poi analizzato l’anatomia di una skill semplice e funzionante, il primo passo per la realizzazione di skill più complesse e potenzialmente pubblicabili. Per la pubblicazione vi sono però vincoli di compliance e regole di cui tenere conto durante la progettazione di un’interfaccia vocale.Di questo e di molti altri aspetti importanti sulla fase di pubblicazione e realizzazione ci occuperemo nei prossimi articoli. Ma prima pubblicheremo un tutorial dettagliato per la realizzazione di una skill... non “banale”. Non vi anticipiamo nulla :-) Stay tuned!Vuoi approfondire l’uso di AWS Lambda o saperne di più sullo sviluppo Serverless? Leggi la nostra serie di articoli dedicati!