Give your bot the ability to validate user's responce
Validation in Bot Framework allows the developper to ensure that a compatible answer is given by the user before proceeding to the next prompt. Provided that the information gathered by the bot is validated, the accuracy of the attained information is increased.
How it works
A perfect example to see how validation works in a prompt can be seen using a choice prompt, because it provides validation functionality out of the box. When an input in the choice prompt is not in the given choices, the choice prompt repeats the question using a new message, until the question is answered correctly. However validation is not unique in the choice prompt. Below you will find a validation implementation in a text prompt.
Let’s get started!
Validate a Text prompt
We will now validatate a text prompt that asks for the user’s age. Create an new Task<bool> function that gets a PromptValidatorContext<string> as an argument. Inside we check if there is any number in the users text using a Regular Expression. If it does, we parse the number into a variable and return True as it passed the validation. If not, we return false to prompt the question again to the user.
private async Task<bool> TextPromptValidatorAsync(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken) { if (Regex.Match(promptContext.Context.Activity.Text, @"\d+").Value != "") { Age = Int32.Parse(Regex.Match(promptContext.Context.Activity.Text, @"\d+").Value); return await Task.FromResult(true); } else return await Task.FromResult(false); }
Our validator is done. Now we just need to let the text prompt know that the validator exists. Find the line below
AddDialog(new TextPrompt(nameof(TextPrompt)));
and replace it with this line. Essentially we add the name of our validator function ine the text prompt’s declaration.
AddDialog(new TextPrompt(nameof(TextPrompt), TextPromptValidatorAsync));
Optionally we can change the message that gets repeated after the validation returns false. FInd the line below which calls the text prompt
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken);
and add a RetryPrompt with your MessageFactory.
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage, RetryPrompt = MessageFactory.Text("Your age must be a number") }, cancellationToken);
Intergrade LUIS in the validator
We are intergrading LUIS to a text prompt that asks for the user’s name. It works the same way as before, except you just send a request to LUIS and wait for it to respond. Thus the use of await is crucial. After that you save the LUIS response in the corresponding variable. If the variable is now null, it means that no name was identified from the user’s utterance, so we return False. If it is not null the name was captured correctly and we return True to let the user proceed.
private async Task<bool> TextPromptValidatorAsync(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken) { luisResult = await MainDialog.Get_luisRecognizer().RecognizeAsync<FlightBooking>(promptContext.Context, cancellationToken); Name = (luisResult.Entities.personName != null ? char.ToUpper(luisResult.Entities.personName[0][0]) + luisResult.Entities.personName[0].Substring(1) : null); if (Name == null) return await Task.FromResult(false); else return await Task.FromResult(true); }
Multiple validators for the same prompt
If you have implemented both of those validators, you probably came to the realization that having more that one validator for the same type of prompt (in our case text prompt) is prohibited. To get around that we can create a switch statemend and pass a value that indicates which validation should be performed when the validator is called.
Firstly let’s create an enum that contains all the possible validations (Name and Age).
private enum Validator { Name, Age };
Next we implement the switch statement in the first function and cut-paste the functionality of the second one into the first, like below. Puting a null check in the switch is also helpful because some text prompts might not be utilizing any validation. For that reason we also set the default case to return true.
private async Task<bool> TextPromptValidatorAsync(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken) { switch (promptContext.Options.Validations != null ? (Validator)promptContext.Options.Validations : (Validator)(-1)) { case Validator.Name: luisResult = await MainDialog.Get_luisRecognizer().RecognizeAsync<FlightBooking>(promptContext.Context, cancellationToken); Name = (luisResult.Entities.personName != null ? char.ToUpper(luisResult.Entities.personName[0][0]) + luisResult.Entities.personName[0].Substring(1) : null); if (Name == null) return await Task.FromResult(false); else return await Task.FromResult(true); case Validator.Age: if (Regex.Match(promptContext.Context.Activity.Text, @"\d+").Value != "") { Age = Int32.Parse(Regex.Match(promptContext.Context.Activity.Text, @"\d+").Value); return await Task.FromResult(true); } else return await Task.FromResult(false); default: return await Task.FromResult(true); } }
Lastly we should include our validation type into the prompt’s options when the prompt is being called.
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage, RetryPrompt = MessageFactory.Text("Can you please repeat your name?"), Validations = Validator.Name }, cancellationToken);
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage, RetryPrompt = MessageFactory.Text("Your age must be a number"), Validations = Validator.Age }, cancellationToken);
Your finished result should look like this.
You can also work on the implementation of the validators to make it seem “smarter”. For example you can request LUIS for an age entity before trying the regular expression or if the user answers only with one word in the name field you can register it as a name, even if LUIS fails to identify it.