0
votes

I want my bot to process LUIS and QnAMaker utterance by users when there is no active dialog.

I ended up with this code and its working, the only problem is, its only working for one turn. the second time the user type anything the bot start the main dialog again.

enter image description here End here is cancel all dialogs. The answer from who made you is from QnAMaker.

How can i prevent the bot from auto starting main dialog?

  public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        await base.OnTurnAsync(turnContext, cancellationToken);

        var recognizerResult = turnContext.TurnState.Get<RecognizerResult>("recognizerResult");
        var topIntent = turnContext.TurnState.Get<string>("topDispatchIntent");
        var dc = await Dialogs.CreateContextAsync(turnContext, cancellationToken);

        var dialogResult = await dc.ContinueDialogAsync();

        if (!dc.Context.Responded)
        {
            switch (dialogResult.Status)
            {
                //dispatch to luis or qna when there is no active dialog
                case DialogTurnStatus.Empty:
                    await DispatchToLUISorQnAMakerAsync(turnContext, topIntent, recognizerResult, cancellationToken);
                    break;

                case DialogTurnStatus.Waiting:
                    break;

                case DialogTurnStatus.Complete:
                    await dc.EndDialogAsync();
                    break;

                default:
                    await dc.CancelAllDialogsAsync();
                    break;
            }
        }


        // Save any state changes that might have occured during the turn.
        await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
        await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
    }

    private async Task DispatchToTopIntentAsync(ITurnContext turnContext, string intent, RecognizerResult recognizerResult, CancellationToken cancellationToken)
    {
        switch (intent)
        {
            case "none":
                await turnContext.SendActivityAsync("Sorry i did not get that.");
                break;

            case "q_SabikoKB":
                await DispatchToQnAMakerAsync(turnContext, cancellationToken);
                break;

            case "l_SabikoV2":
                await DispatchToLuisModelAsync(turnContext, recognizerResult.Properties["luisResult"] as LuisResult, cancellationToken);
                break;
        }
    }

    private async Task DispatchToQnAMakerAsync(ITurnContext turnContext, CancellationToken cancellationToken)
    {
        if (!string.IsNullOrEmpty(turnContext.Activity.Text))
        {
            var results = await BotServices.QnaService.GetAnswersAsync(turnContext);
            if (results.Any())
            {
                await turnContext.SendActivityAsync(MessageFactory.Text(results.First().Answer), cancellationToken);
            }
            else
            {
                await turnContext.SendActivityAsync(MessageFactory.Text("Sorry, could not find an answer in the Q and A system."), cancellationToken);
            }
        }
    }

    private async Task DispatchToLuisModelAsync(ITurnContext turnContext, LuisResult luisResult, CancellationToken cancellationToken)
    {
        var result = luisResult.ConnectedServiceResult;
        var topIntent = result.TopScoringIntent.Intent; 

        switch (topIntent)
        {
            case "Greeting":
                //..

            default:
                break;
        }
    }

main dialog

    namespace BotV2.DialogsV2.Main_Menu
{
    public class MainDialog : InterruptDialog
    {
        private const string InitialId = nameof(MainDialog);
        private readonly IStatePropertyAccessor<BasicUserState> _userProfileAccessor;

        public MainDialog(UserState userState, ConversationState conversationState, IConfiguration config)
            : base(nameof(MainDialog),userState)
        {
            _userProfileAccessor = userState.CreateProperty<BasicUserState>("UserProfile");

            InitialDialogId = InitialId;
            WaterfallStep[] waterfallSteps = new WaterfallStep[]
             {
                CheckWelcomeMessageStepAsync,
                FirstStepAsync,
                SecondStepAsync,
             };
            AddDialog(new WaterfallDialog(InitialId, waterfallSteps));
            //AddDialogs..
        }

        private async Task<DialogTurnResult> CheckWelcomeMessageStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            var userstate = await _userProfileAccessor.GetAsync(stepContext.Context, () => new BasicUserState(), cancellationToken);

            if (!userstate.DialogCheckers.SentWelcomeMessage)
            {
                return await stepContext.BeginDialogAsync(nameof(WelcomeMessageDialog));
            }

            return await stepContext.NextAsync(cancellationToken: cancellationToken);
        }

        private async Task<DialogTurnResult> FirstStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            var userstate = await _userProfileAccessor.GetAsync(stepContext.Context, () => new BasicUserState(), cancellationToken);

            //only suggestions, interruption will trigger the dialog to begin them.
            return await stepContext.PromptAsync(
                nameof(TextPrompt),
                new PromptOptions
                {
                    Prompt = new Activity
                    {
                        Type = ActivityTypes.Message,
                        Text = $"So {userstate.FirstName}, What can i do for you?",
                        SuggestedActions = new SuggestedActions()
                        {
                            Actions = new List<CardAction>()
                            {
                                    new CardAction() { Title = "Financial Literacy", Type = ActionTypes.ImBack, Value = "Financial Literacy" },
                                    new CardAction() { Title = "My Compass", Type = ActionTypes.ImBack, Value = "My Compass" },
                            },
                        },
                    },
                });             
        }

        private static async Task<DialogTurnResult> SecondStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            return await stepContext.EndDialogAsync();
        }
    }
}

Startup

     services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();

  services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();

    services.AddSingleton<IBotServices, BotServices>();

    services.AddSingleton<ConcurrentDictionary<string, ConversationReference>>();

    services.AddTransient<MainDialog>();

    services.AddTransient<WelcomeMessageDialog>();

    services.AddTransient<IBot, DialogBot<MainDialog>>();
1
Can you include the code for your dialog? Does it call EndDialogAsync at the end? And can you clarify, is the bot restarting the main dialog for every message?mdrichardson
@mdrichardson-MSFT Yes it call EndDialogAsync on the final step. I edited and added main dialog and startup code thanks!user10860402
@mdrichardson-MSFT I added an image. no it does not restart main dialog from the very beginning every message, it just continue/starts it. So when i type "end" or "cancel" that calls the CancelAllDialogsAsync(). And then i ask a question and the bot answer from the QnAMaker. and then i asked the same question again. It continues main dialog.user10860402
Hmm...nothing really stands out. Are you able to link me to your code or send it to me? I don't believe I can solve this without doing some debugging.mdrichardson
just curious - why taking all the hassle of managing the dialog turn status and dispatching by yourself in the bot class? can't you simply put branching into the main dialog and let the dialog handle it for you? I see a lot of people started adding biz logic into the Bot class. This doesn't seem right to me.sidecus

1 Answers

0
votes

The MainDialog auto-restarts because in DialogBot.cs -> OnMessageActivityAsync(), you have:

await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);

This calls the MainDialog on every single message from the user.

You have good checks for whether or not the user has done a part of the MainDialog:

if (!userstate.DialogCheckers.SentWelcomeMessage)
[...]
if (!userstate.DialogCheckers.AskedDiagnosticQuestion)

but the dialog, itself, is still going to be called on every single message.

I think you misunderstand CancelAllDialogs() to mean that they never fire. Instead, it just clears your dialog stack, but they can still be called again.

You have a couple of different options for "fixing" this:

  1. You could have some sort of userState flag like, hasCancelledDialogs which gets set when the user calls an intent that's meant to cancel all the dialogs, check for it in MainDialog, and if it's found, EndDialog() on the first step.

  2. Remove the Dialog.RunAsync call from DialogBot.cs and instead, just route to the appropriate dialog based on dispatch intent and/or MembersAdded.


I also noticed that you use a combination of methods for routing dialogs. You have both:

var dc = await Dialogs.CreateContextAsync(turnContext, cancellationToken);

var dialogResult = await dc.ContinueDialogAsync();

if (!dc.Context.Responded)

[...]

and

await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);

RunAsync takes care of most of that first code block for you.

This might also be causing you some issues. Most likely, you started developing your bot with the first code block, and then we made the shift to the second style and you tried integrating it without fully understanding what was going on behind the scenes.

Your bot is starting to get pretty complex and will become very difficult to troubleshoot these kinds of issues. I'd recommend converting everything over to the Dialog.RunAsync style and removing the older style. It's a lot of work to refactor, but will save a lot of time with debugging and troubleshooting.


All of that being said, I'm only ~80% sure I understand your issue. Again, your bot was pretty complex with lots of looping and branching, so I'm not completely sure this answer addresses your question. If it doesn't, please provide a very detailed description what you expect to be happening.