Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Azure Bot Framework Bot with LUIS and WaterFall Dialogs executing in abnormal manner. Unexpected dialog flow

I am working on hacking up the GitHub 'CoreBot' sample code for Bot Framework V4 (with LUIS) for my own purposes, and have hit a snag in the way the responses and waterfall dialog steps are being executed.

I have a top level dialog. My expectation is this dialog makes an initial call to LUIS based on input, and routes to different dialogs based on that input. At the moment, there is only being able to greet the bot, and report a hazard. My Dialog setup is as below (ignore BookingDialog, it's a part of the sample).

public MainDialog(IConfiguration configuration, ILogger<MainDialog> logger)
    : base(nameof(MainDialog))
{
    Configuration = configuration;
    Logger = logger;

    AddDialog(new TextPrompt(nameof(TextPrompt)));
    AddDialog(new BookingDialog());
    AddDialog(new HazardDialog());
    AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
    {
        MainStepAsync,
        EndOfMainDialogAsync
    }));

    // The initial child Dialog to run.
    InitialDialogId = nameof(WaterfallDialog);
}

My expectation is that MainStepAsync is executed, which runs the following:

   private async Task<DialogTurnResult> MainStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {

        CoreBot.StorageLogging.LogToTableAsync($"Main step entered. I am contacting LUIS to determine the intent");

        string what_i_got = await LuisHelper.ExecuteMyLuisQuery(Configuration, Logger, stepContext.Context, cancellationToken);

        CoreBot.StorageLogging.LogToTableAsync($"LUIS intent matched: {what_i_got}");
        //await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text($"Hi! My name is Sorella. Your intent was {what_i_got}") }, cancellationToken);

        switch (what_i_got)
        {
            case "Hazard":
                StorageLogging.LogToTableAsync($"We have been asked to report a hazard");
                StorageLogging.LogToTableAsync(stepContext);
                var hazardDetails = new ResponseSet.Hazard();
                return await stepContext.BeginDialogAsync(nameof(HazardDialog), hazardDetails, cancellationToken);
            case "Greeting":
                StorageLogging.LogToTableAsync($"We have been asked to provide a greeting. After this greeting the waterfall will end");
                StorageLogging.LogToTableAsync(stepContext);
                await stepContext.Context.SendActivityAsync(MessageFactory.Text("Hi there! :). What can I help you with today? "), cancellationToken);
                return await stepContext.EndDialogAsync(null, cancellationToken);
            default:
                StorageLogging.LogToTableAsync($"We got an intent we haven't catered for. After this the waterfall will end");
                StorageLogging.LogToTableAsync(stepContext);
                return await stepContext.NextAsync(null, cancellationToken);
        }
    }

If the intent is Harzard, then begin the HazardDialog. Otherwise if they are greeting the bot, just say hello and end this top level waterfall dialog. If the user is routed to HazardDialog, start the next Waterfall, set up as follows:

   public class HazardDialog : CancelAndHelpDialog 
    {
        public HazardDialog()
        : base(nameof(HazardDialog))
        {
            AddDialog(new TextPrompt(nameof(TextPrompt)));
            AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
            {
                GetInitialHazardInfoAsync,
                GetHazardUrgencyAsync,
                FinalStepAsync
            }));
            // The initial child Dialog to run.
            InitialDialogId = nameof(WaterfallDialog);
        }

They are first asked for a description of the hazard:

    private async Task<DialogTurnResult> GetInitialHazardInfoAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
        CoreBot.StorageLogging.LogToTableAsync("Entered hazard step 1. Asking for hazard type");

        var hazardDetails = (ResponseSet.Hazard)stepContext.Options;

        hazardDetails.HazardType = (string)stepContext.Result;

        if (hazardDetails.HazardType == null)
        {
            CoreBot.StorageLogging.LogToTableAsync($"No hazard type provided. Asking");
            return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text("What kind of hazard would you like to report? Provide me a brief description") }, cancellationToken);
        }
        else
        {
            CoreBot.StorageLogging.LogToTableAsync($"Hazard provided. Moving on");
            return await stepContext.NextAsync(hazardDetails.HazardType, cancellationToken);
        }
    }

Then for the urgency:

   private async Task<DialogTurnResult> GetHazardUrgencyAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
        CoreBot.StorageLogging.LogToTableAsync($"Entered hazard step 2. Asking for urgency");
        var hazardDetails = (ResponseSet.Hazard)stepContext.Options;
        hazardDetails.HazardType = (string)stepContext.Result;
        var hazardAsJson = JsonConvert.SerializeObject(hazardDetails);
        StorageLogging.LogToTableAsync(hazardAsJson);

        if (hazardDetails.HarzardUrgency == null)
        {
            CoreBot.StorageLogging.LogToTableAsync($"No urgency provided. Asking");
            return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text($"Thanks. So your hazard is {hazardDetails.HazardType}? How urgent is it?") }, cancellationToken);
        }
        else
        {
            CoreBot.StorageLogging.LogToTableAsync($"Urgency given. We're all done");
            var guid = Guid.NewGuid();
            var ticketId = "HAZ" + Convert.ToString(guid).ToUpper().Substring(1,4);
            await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Thanks! I've got all the informatio I need. I'll raise this with the API team on your behalf. Your Ticket ID is: {ticketId} "), cancellationToken);
            return await stepContext.NextAsync(cancellationToken, cancellationToken);
        }
    }

If we have both urgency and type, we then 'raise a ticket' and go to the final step, which just ends the stack.

  private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
        CoreBot.StorageLogging.LogToTableAsync($"Entered hazard step 3. Final step");
        var hazardDetails = (ResponseSet.Hazard)stepContext.Options;
        hazardDetails.HarzardUrgency = (string)stepContext.Result;
        var hazardAsJson = JsonConvert.SerializeObject(hazardDetails);
        StorageLogging.LogToTableAsync(hazardAsJson);
        return await stepContext.EndDialogAsync(hazardDetails, cancellationToken);
    }

My expectation is ending the HarzardDialog should then return to the next step in the 'Parent' waterfall dialog, which is EndOfMainDialogAsync, which just says we're all done, and can I do anything else to help?

  private async Task<DialogTurnResult> EndOfMainDialogAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
        StorageLogging.LogToTableAsync($"Ending the main dialog");
        StorageLogging.LogToTableAsync(stepContext);
        await stepContext.Context.SendActivityAsync(MessageFactory.Text("Ok, I think we're all done with that. Can I do anything else to help?"), cancellationToken);
        return await stepContext.EndDialogAsync(null, cancellationToken);
    }

However, in my actual conversation, the flow ends up misbehaving, and in fact (if you look at the conversation below) firing GetHazardUrgencyAsync from the child waterfall, then MainStepAsync from the parent waterfall, and then GetHazardUrgencyAsync from the child waterfall a second time?

enter image description here

UPDATE: As per suggestions, I have updated my WaterFallDialogs to have unique names, and re-tested. I am still getting the wrong behavior. See screenshot below:

enter image description here

My expectation is that after describing the hazard I am next asked how urgent it is. Instead, I get the following dialog responses all in a 'block'.

  • Asking how urgent it is (Correct)
  • Welcoming me again (Incorrect - this is from the parent Waterfall)
  • Asking how urgent it is again (Incorrect)

I'm a bit lost here. I would have imagined / thought from my code, that the child waterfall dialog would need to be completely fulfilled/ended before returning to the parent dialog.

like image 754
JamesMatson Avatar asked Jun 19 '19 02:06

JamesMatson


2 Answers

Dialog names are globally unique within a bot. Both your waterfall dialogs are named "WaterfallDialog". So you're basically swapping out the waterfall on the fly.

Change them to unique names.

    AddDialog(new WaterfallDialog("MainDialogWaterfall", new WaterfallStep[]
    {
        MainStepAsync,
        EndOfMainDialogAsync
    }));
    AddDialog(new WaterfallDialog("HazardInfoWaterfallDialog", new WaterfallStep[]
    {
        GetInitialHazardInfoAsync,
        GetHazardUrgencyAsync,
        FinalStepAsync
    }));
like image 89
Mike Kinney Avatar answered Sep 30 '22 17:09

Mike Kinney


Everything seems fine in your example. I actually tried it out and found no issues with it so it must be some external dependency that you omitted in the question.

Please refer to this sample that is exactly using your code and working as expected. The only difference is that it's not contacting LUIS but that should not make any difference.

Repository: https://github.com/jvanderbiest/Core-bot-troubleshooting

Output:

enter image description here

like image 38
emp Avatar answered Sep 30 '22 17:09

emp