Manipulating waterfall steps Botframework v4

To be honest I’m not even sure that this is strictly supported or that it is even a good idea, but as a point of interest you can manipulate the waterfall steps in v4. E.g. The standard flow is; step 1 -> step 2 -> step 3 -> step n. If your code realises that the user should skip a step then it can invoke the ‘next’ function;

async (dc, args, next) =>
{
    if (someCondition)
    {
        await next(args);
        return;
    }
}

That’s pretty easy. The difficult question, and one that I’m not even sure is (or should be) a valid one, how do you go back a step? Well, it is possible but it’s messy and you can’t get back to the initial step (although that is just starting again). You can go back a step by manipulating the dialog state;

// at step n
async (dc, args, next) =>
{
    if (someCondition)
    {
        var targetStep = 1;
        dc.ActiveDialog.Step = targetStep - 1;
        await Step1Prompt();
        return;
    }
}

I don’t recommend this approach, it’s ugly, but you know…possible.

ChoicePrompt, how to always call the validator in Botframework v4

BotFramework v4 has a number of helper prompts, TextPrompt, ChoicePrompt, etc. One common mechanism they share is a Validator delegate. When the user enters a value the Validator is invoked and you have an opportunity to check/change the outcome

var obj = new ValidatingTextPrompt(async (context, toValidate) =>
    {
        if (toValidate.Text.Length < minimumLength)
        {
            toValidate.Status = null;
            await context.SendActivity(minimumLengthMessage);
        }
    }
    );

The presence of the RetryPromptString means the ChoicePrompt will automatically retry of the user enters the incorrect value, such as 'frog'. However, what happens if the user enters the value '3'? Unfortunately this is considered as the 3rd choice and 'quit' will be selected. If your UI is really serving up numbers like this, that could be a real problem. Imagine if the list was 2,4,6 and you entered '3' or even worse '2'!? So I really want to add a Validator delegate that all prompts support;

this.Dialogs.Add("choicePrompt", new ChoicePrompt(Culture.English, ValidateChoice));

private async Task ValidateChoice(ITurnContext context, ChoiceResult toValidate)
{
    var userMessage = context.Activity.Text;
    if (userMessage == "3")
    {
        toValidate.Status = null;
        await Task.Delay(1);
    }
}

Sorted right? Wrong. Unfortunately there are two problems with this solutions; a) this is only called when a value from the choices list is selected (really??) b) the resulting selected value is passed in and not the original, i.e. ‘quit’ is passed in rather than ‘3’. My solution is to derive a new ChoicePrompt that will always call the available Validator with the original values;

public class ChoicePromptAlwaysVerify : Microsoft.Bot.Builder.Dialogs.ChoicePrompt
{
    private readonly PromptValidatorEx.PromptValidator validator;

    public ChoicePromptAlwaysVerify(string culture, PromptValidatorEx.PromptValidator validator = null) : base(culture, validator)
    {
        this.validator = validator;
    }

    protected override async Task OnRecognize(DialogContext dc, PromptOptions options)
    {
        var recognize = await base.OnRecognize(dc, options);
        if (this.validator != null)
        {
            await this.validator.Invoke(dc.Context, recognize);
        }

        return recognize;
    }
}

The code works by forcing the recognize override to call the validator. The downside is that this code will be called twice when the user makes a good choice (sigh), but it’s a small sacrifice to regain some consistent control over the valid values. It also allows for more specialized messages as the RetryMessage is fixed and has no chance to give a contextual response.

Creating a reusable TextPrompt in Bot Framework V4

The TextPrompt mechanism in V4 is fine and can implement a variety of validation techniques because it uses a delegate. However, delegates can create a lot of code noise, especially if you have a validation mechanism that you wish to reuse. Consider the following ‘Hello World’ of Waterflow steps;

public MyBot()
{
    dialogs = new DialogSet();
    dialogs.Add("greetings", new WaterfallStep[]
    {
        async (dc, args, next) =>
        {
            // Prompt for the guest's name.
            await dc.Prompt("textPrompt","What is your name?");
        },
        async(dc, args, next) =>
        {
            // args; Value: "<name>", Text: "<name>"
            var userResponse = args["Text"] as string;
            await dc.Context.SendActivity($"Hi {args["Text"]}!");
            await dc.End();
        }
    });

    // add the prompt, of type TextPrompt
    dialogs.Add("textPrompt", new Microsoft.Bot.Builder.Dialogs.TextPrompt(TextValidation));
}

private async Task TextValidation(ITurnContext context, TextResult toValidate)
{
    if (toValidate.Text.Length < 4)
    {
        toValidate.Status = null;
        await context.SendActivity("Sorry needs to be > 4");
    }
}

The problem is that the TextValidation delegate is ugly to re-use. I.e. I want a nicer way to share a simple length validation. This is my solution;

public class ValidatingTextPrompt : Microsoft.Bot.Builder.Dialogs.TextPrompt
{

    public static ValidatingTextPrompt Create(int minimumLength, string minimumLengthMessage)
    {
        var obj = new ValidatingTextPrompt(async (context, toValidate) =>
            {
                if (toValidate.Text.Length < minimumLength)
                {
                    toValidate.Status = null;
                    await context.SendActivity(minimumLengthMessage);
                }
            }
            );
        return obj;
    }

    public ValidatingTextPrompt(PromptValidatorEx.PromptValidator<TextResult> validator) : base(validator)
    {
    }
}

Then you can swap out the TextPrompt with the more specialized code;

dialogs.Add("textPrompt", ValidatingTextPrompt.Create(5, "I can't remember such a short name, please try again"));

If you have thoughts about a better way then please feel free to comment.

MS Bot framework / LUIS gotcha

I’ve been training the LUIS service for use with the Microsoft Bot Framework and I did the cardinal sin of changing two things in my code; add some ‘clever’ OO stuff and add a new set of LUIS Intents. When I tested my Bot I kept getting, ‘The given key was not present in the dictionary’ when instantiating my LUIS Dialog. Turns out that if you have an Intent in LUIS but you have not YET implemented the handler in your LUIS Dialog then this is the error you get should the user hit that Intent. I suspect there is someway of specifying a default handler but I’ve yet to find it.