I am using the ASK SDK 2.0 for Node.js. I have a skill that uses a dialog model to prompt the user for a series of inputs, all of which are required slots with the type AMAZON.NUMBER. When a user gives a numerical response, everything works fine. However, if the user gives a non-numeric response, such as "yellow", the slot value is filled in with:
"value": "?"
and moves on to propmpt the user for the input for the next slot. How can I get it to reprompt the user for that slot again if they provide an invalid response? I've poured over the documentation and can't find anything. Ideally I would want the skill to reprompt the user until a valid input is given (i.e., the value isn't "?"
)
(Strangely, if i set the type to AMAZON.DATE, it will automatically reprompt once, and then if an invalid type is provided a second time the skill will just quit out.)
My response looks like this:
"response": {
"directives": [{
"type": "Dialog.Delegate",
"updatedIntent": {
"name": "MRRIntent",
"confirmationStatus": "NONE",
"slots": {
"growthValue": {
"name": "growthValue",
"value": "?",
"confirmationStatus": "NONE"
},
"churnValue": {
"name": "churnValue",
"value": "?",
"confirmationStatus": "NONE"
},
"startingValue": {
"name": "startingValue",
"value": "10",
"confirmationStatus": "NONE"
}
}
}
}]
}
Where in this example startingValue
was given a numerical response, but the other two (growthValue
and churnValue
) were given non-number responses.
Where in the intent handler would I be able to check for this value and reprompt on the particular slots that are failing? I am using the Dialog.Directive which the docs say not to use reprompt with ( https://developer.amazon.com/docs/custom-skills/dialog-interface-reference.html#details ), unless I am misunderstanding it.
My handlers look like this:
const InProgressPlanMyTripHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'IntentRequest' &&
request.intent.name === 'MRRIntent' &&
request.dialogState !== 'COMPLETED';
},
handle(handlerInput) {
const currentIntent = handlerInput.requestEnvelope.request.intent;
return handlerInput.responseBuilder
.addDelegateDirective(currentIntent)
.getResponse();
},
};
const CompletedPlanMyTripHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'IntentRequest' && request.intent.name === 'MRRIntent';
},
handle(handlerInput) {
const responseBuilder = handlerInput.responseBuilder;
const filledSlots = handlerInput.requestEnvelope.request.intent.slots;
const slotValues = getSlotValues(filledSlots);
let speechOutput = `Your values are: startingValue: ${slotValues.startingValue.synonym} growthValue: ${slotValues.growthValue.synonym}, and churnValue: ${slotValues.churnValue.synonym}`
return responseBuilder
.speak(speechOutput)
.getResponse();
},
};
I used the Plan My Trip example as my starting point, so the vast majority of my code is going to be identical to it: https://github.com/alexa/alexa-cookbook/tree/master/feature-demos/skill-demo-plan-my-trip
What am I missing / not understanding? Thanks
Always validate slots in your backend, and whenever your number-slot is not returning a numeric value, do not delegate, instead use Dialog.ElicitSlot
directive to make Alexa ask for that particular slot.
Ex:
// if number-slot validation fails
return handlerInput.responseBuilder
.addElicitSlotDirective(slotToElicit)
.speak("Please provide a number")
.reprompt("Please provide a number")
.getResponse();
With Dialog.Delegate
directive you cannot send outputSpeech
or reprompt
from your code. Instead those defined in interaction model will be used. However, at any point, you can take over the dialog rather than continuing to delegate to Alexa.
More on Dialog directives here
I believe I've found a solution -- I'm not sure it's the best solution, but it appears to work.
In the CompletedPlanMyTripHandler
, I added the following check to the handle
method:
handle(handlerInput) {
const currentIntent = handlerInput.requestEnvelope.request.intent;
let slots = currentIntent.slots;
let badInputSlot;
for (let x in slots) {
if (slots.hasOwnProperty(x) && slots[x].hasOwnProperty('value')) {
if (isNaN(slots[x].value)) {
badInputSlot = x;
break;
}
}
}
if (badInputSlot) {
return handlerInput.responseBuilder.speak('I do not understand. Please respond with a number.').reprompt('Please respond with a number').addElicitSlotDirective(badInputSlot, currentIntent).getResponse();
} else {
return handlerInput.responseBuilder
.addDelegateDirective(currentIntent)
.getResponse();
}
},
What it's doing is in the for loop its checking the the slots on the intent, seeing if each one has a value
property (which is only added once a response has been supplied), and then does an isNaN
check on it to see if the value is in fact valid. If it is not, we need to ask the user again for that value, so we store the slot's name in badInputSlot
and break out of the loop.
Now, we move on to the if statement, and if there is a value assigned to badInputSlot
we return an elicit slot directive for the specific slot that had a bad value.
Then, after the user supplies a new value, we repeat the process until every slot in handlerInput.requestEnvelope.request.intent.slots
has a value
property that passes our isNaN
validation check. Once that happens, badInputSlot
will be undefined, and our if statement will go to the else
block and return a delegate directive to finish off the intent.
I also answered you on the Alexa Slack, however I will post it here again for others to see:
You still have to do error handling in code, this is mentioned multiple times in the docs. You have the following in your code to retrieve the slot values:
const filledSlots = handlerInput.requestEnvelope.request.intent.slots;
const slotValues = getSlotValues(filledSlots);
const startingValue = slotValues.startingValue.synonym;
What you have to do is check in your code for the correct format, e.g.:
if (!startingValue || startingValue === '?') {
return handlerInput.responseBuilder
.speak('This is not a number, please try again')
.reprompt('Please try again.')
.addElicitSlotDirective('SLOTNAMEGOESHERE')
.getResponse();
}
This will elicit the slot again, in case userInput
is undefined
(which means we are still missing that slot in the dialog) or userInput
equals the ?
. Using above, you can very nicely design a conversation by eliciting slot after slot.
来源:https://stackoverflow.com/questions/51992393/alexa-input-validation-for-type-amazon-number-using-dialog-model