Search code examples
c#asp.netazurebotframeworkchatbot

How to stay in a bots dialog (Bot Framework, C#)


I am currently planing to use dialogs inside my C# bot. I have already designed a complete dialog and implemented it into my current solution but when I test it out, I can only trigger the first part of it.

My bot is configured as following:

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    string[] PostCodeDialogStartTrigger = new string[] { "trigger1", "trigger2", "trigger3" };
    if (PostCodeDialogStartTrigger.Contains(turnContext.Activity.Text) /* Maybe || ConversationState != null */)
    {
        await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
    }
    else
    {
        // First, we use the dispatch model to determine which cognitive service (LUIS or QnA) to use.
        var recognizerResult = await BotServices.Dispatch.RecognizeAsync(turnContext, cancellationToken);

        // Top intent tell us which cognitive service to use.
        var topIntent = recognizerResult.GetTopScoringIntent();

        // Next, we call the dispatcher with the top intent.
        await DispatchToTopIntentAsync(turnContext, topIntent.intent, recognizerResult, cancellationToken);
    }
}

I have a set of strings inside PostCodeDialogStartTrigger. I want that the dialog starts when the users message matches one of the triggers. Otherwise my regular process should start, which includes LUIS and the QnA Maker for one-dimensional Conversations. The thing is that i cant stay in the dialog because the next message wouldnt trigger the dialog again, obviously.

Is there a way to check the ConversationState or UserState and if it has progressed, will the dialog continue?


Solution

  • What you want is called interruptions: https://learn.microsoft.com/azure/bot-service/bot-builder-howto-handle-user-interrupt

    The core bot sample includes a somewhat complicated way of handling interruptions by creating a base CancelAndHelpDialog class that makes any dialog "interruptible," but there is a simpler way of doing it. The key is to call ContinueDialogAsync on every turn by default, and only don't call it if an interruption takes place on that turn. You can think of each turn as having three cases:

    1. An interruption takes place
    2. There's no interruption but there's an active dialog
    3. There's no interruption and no active dialog

    Here's how the logic might play out:

    string[] PostCodeDialogStartTrigger = new string[] { "trigger1", "trigger2", "trigger3" };
    
    if (PostCodeDialogStartTrigger.Contains(turnContext.Activity.Text))
    {
        // Case 1: Handle interruptions by starting a dialog
        await dialogContext.BeginDialogAsync(DIALOG_ID);
    }
    else
    {
        // Case 2: Try to continue the dialog in case there is one
        var result = await dialogContext.ContinueDialogAsync();
    
        if (result.Status == DialogTurnStatus.Empty)
        {
            // Case 3: If there aren't any dialogs on the stack
    
            // First, we use the dispatch model to determine
            // which cognitive service (LUIS or QnA) to use.
            // . . .
        }
    }
    

    You might notice that I'm not using RunAsync here. RunAsync works best with your bot's "main dialog" that's meant to always be on the stack, and it doesn't work so well if you don't really have a main dialog. The original purpose of RunAsync was to combine a few method calls into one, since it was such a common pattern for bots to call ContinueDialogAsync and then check the result and then start the main dialog if the dialog stack was empty. You want to do something similar to that, but instead of beginning a main dialog in the case of an empty dialog stack you just want to call dispatch.

    In case you're curious, here are some other Stack Overflow posts where you can read about interruptions:

    I should mention that you're trying to do things in a sort of outdated way. You probably already noticed that Dispatch has been replaced by Orchestrator, so you really should be using Orchestrator instead. Also, things like interruptions have been made easy by Composer, and it's recommended for everyone to be using Composer now anyway.