Give your bot the ability to validate user's responce

December 10, 2020

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.

About Me

Hi, my name is Demetris Bakas and I am a software engineer that loves to write code and be creative. I always find new technologies intriguing and I like to work with other people and be a part of a team. My goal is to develop software that people will find useful and will aid them in their everyday lives.
For any questions feel free to contact me at social media using the links below.