Reputation: 1012
I don't know if i can explain this well but please bear with me. There are codes and images below.
So I am migrating my codes from the first version of Bot Framework V4 to the latest version.
I am trying to make a base for a bot that can call other dialog and cancel the current dialog anytime. and also answer questions with QnAMaker when there are no active dialogs.
There are no errors but the bot is not behaving as expected.
Expecting: When user first interracts with "Get Started" the main menu will be called because I added "Get started" to the intents of Main menu. Actual Results: The main menu is being called twice.
Expecting: When i call DialogA from interruption via intent. The dialogA will be called and if there is any active dialog it will be cancelled. Actual Results: Dialog A is called and current active dialog is ended BUT dialogA also ends abruptly.(Even when you did not answer its choice prompt yet). Note: when i call dialogA via choice prompt in the main menu. the dialog A is starting normally and not ending.
Expecting: when there are no active dialog(Example if you cancel dialogs) user can ask a question and the bot check for answers in the QnaMaker. Actual results: the bot answer the question then start main menu. and even when there are active dialog the bot still answer the questions.
Here are the codes:
DialogBot:
namespace SabikoBotV2.Bots
{
public class DialogBot<T> : ActivityHandler
where T : Dialog
{
public readonly IStatePropertyAccessor<DialogState> _dialogAccessor;
protected readonly Dialog Dialog;
protected readonly BotState ConversationState;
protected readonly BotState UserState;
protected readonly ILogger Logger;
private readonly IBotServices BotServices;
private DialogSet Dialogs { get; set; }
public DialogBot(IBotServices botServices, ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger)
{
ConversationState = conversationState;
UserState = userState;
Dialog = dialog;
Logger = logger;
BotServices = botServices;
Dialogs = new DialogSet(conversationState.CreateProperty<DialogState>(nameof(DialogBot<T>)));
RegisterDialogs(Dialogs);
}
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
await base.OnTurnAsync(turnContext, cancellationToken);
await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Running dialog with Message Activity.");
string text = string.IsNullOrEmpty(turnContext.Activity.Text) ? string.Empty : turnContext.Activity.Text.ToLower();
string topIntent = string.Empty;
RecognizerResult luisRecognizerResult = null;
string topDispatch = string.Empty;
RecognizerResult dispatchRecognizerResult = null;
if (!string.IsNullOrEmpty(text))
{
dispatchRecognizerResult = await BotServices.DispatchService.RecognizeAsync(turnContext, cancellationToken);
var topScoringDispatch = dispatchRecognizerResult?.GetTopScoringIntent();
topDispatch = topScoringDispatch.Value.intent;
luisRecognizerResult = await BotServices.LuisService.RecognizeAsync(turnContext, cancellationToken);
var topScoringIntent = luisRecognizerResult?.GetTopScoringIntent();
topIntent = topScoringIntent.Value.intent;
turnContext.TurnState.Add("topDispatch", topDispatch);
turnContext.TurnState.Add("dispatchRecognizerResult", dispatchRecognizerResult);
turnContext.TurnState.Add("botServices", BotServices);
turnContext.TurnState.Add("topIntent", topIntent);
}
var dc = await Dialogs.CreateContextAsync(turnContext, cancellationToken);
var dialogResult = await dc.ContinueDialogAsync();
if (!dc.Context.Responded)
{
switch (dialogResult.Status)
{
case DialogTurnStatus.Empty:
await DispatchToTopIntentAsync(turnContext, topDispatch, dispatchRecognizerResult, cancellationToken);
break;
case DialogTurnStatus.Waiting:
break;
case DialogTurnStatus.Complete:
await dc.EndDialogAsync();
break;
default:
await dc.CancelAllDialogsAsync();
break;
}
}
await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
}
private void RegisterDialogs(DialogSet dialogs)
{
dialogs.Add(new MainDialog());
dialogs.Add(new DialogA());
dialogs.Add(new DialogB());
}
private async Task DispatchToTopIntentAsync(ITurnContext turnContext, string intent, RecognizerResult recognizerResult, CancellationToken cancellationToken)
{
switch (intent)
{
case QnAModel:
await DispatchToQnAMakerAsync(turnContext, 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);
}
}
}
}
}
startup
namespace SabikoBotV2
{
public class Startup
{
public Startup()
{
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
services.AddSingleton<IStorage, MemoryStorage>();
services.AddSingleton<UserState>();
services.AddSingleton<ConversationState>();
services.AddSingleton<IBotServices, BotServices>();
services.AddTransient<MainDialog>();
services.AddTransient<DialogA>();
services.AddTransient<DialogB>();
services.AddTransient<IBot, DialogBot<MainDialog>>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseMvc();
}
}
}
CancelAndHelpDialog
namespace SabikoBotV2.DialogsV2
{
public class CancelAndHelpDialog : ComponentDialog
{
public CancelAndHelpDialog(string id)
: base(id)
{
}
protected override async Task<DialogTurnResult> OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default(CancellationToken))
{
var result = await IsTurnInterruptedAsyncHelpAndCancel(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnBeginDialogAsync(innerDc, options, cancellationToken);
}
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
var result = await IsTurnInterruptedAsyncHelpAndCancel(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
protected virtual async Task<DialogTurnResult> IsTurnInterruptedAsyncHelpAndCancel(DialogContext innerDc, CancellationToken cancellationToken)
{
var topIntent = innerDc.Context.TurnState.Get<string>("topIntent");
var text = innerDc.Context.TurnState.Get<string>("text");
if (topIntent.Equals("Cancel"))
{
if (innerDc.ActiveDialog != null)
{
await innerDc.CancelAllDialogsAsync();
await innerDc.Context.SendActivityAsync("👍 Ok. I've cancelled our last activity.");
}
else
{
await innerDc.Context.SendActivityAsync("I don't have anything to cancel.");
}
}
if (topIntent.Equals("Help"))
{
await innerDc.Context.SendActivityAsync("Let me help you");
if (innerDc.ActiveDialog != null)
{
await innerDc.RepromptDialogAsync();
}
}
if (topIntent.Equals("MainDialog"))
{
if (innerDc.ActiveDialog != null)
{
await innerDc.CancelAllDialogsAsync();
await innerDc.BeginDialogAsync(nameof(MainDialog));
}
else
{
await innerDc.BeginDialogAsync(nameof(MainDialog));
}
}
if (topIntent.Equals("DialogA"))
{
if (innerDc.ActiveDialog != null)
{
await innerDc.CancelAllDialogsAsync();
await innerDc.BeginDialogAsync(nameof(DialogA));
}
else
{
await innerDc.BeginDialogAsync(nameof(DialogA));
}
}
if (topIntent.Equals("DialogB"))
{
if (innerDc.ActiveDialog != null)
{
await innerDc.CancelAllDialogsAsync();
await innerDc.BeginDialogAsync(nameof(DialogB));
}
else
{
await innerDc.BeginDialogAsync(nameof(DialogB));
}
}
return null;
}
}
}
MainDialog
namespace SabikoBotV2.Dialogs
{
public class MainDialog : CancelAndHelpDialog
{
private const string InitialId = nameof(MainDialog);
public MainDialog()
: base(nameof(MainDialog))
{
InitialDialogId = InitialId;
WaterfallStep[] waterfallSteps = new WaterfallStep[]
{
FirstStepAsync,
SecondStepAsync,
ThirdStepAsync,
};
AddDialog(new WaterfallDialog(InitialId, waterfallSteps));
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new DialogA());
AddDialog(new DialogB());
}
private static async Task<DialogTurnResult> FirstStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.Context.SendActivityAsync("Start of Main");
return await stepContext.PromptAsync(
nameof(ChoicePrompt),
new PromptOptions
{
Prompt = MessageFactory.Text($"What do you want to do next?"),
Choices = new List<Choice>
{
new Choice
{
Value = "choice1",
},
new Choice
{
Value = "choice2",
},
},
RetryPrompt = MessageFactory.Text($"Please choose one of the options."),
});
}
private static async Task<DialogTurnResult> SecondStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
{
switch ((stepContext.Result as FoundChoice).Value.ToString().ToLower())
{
case "choice1":
return await stepContext.BeginDialogAsync(nameof(DialogA));
case "choice2":
return await stepContext.BeginDialogAsync(nameof(DialogB));
default:
return await stepContext.ReplaceDialogAsync(nameof(MainDialog));
}
}
private static async Task<DialogTurnResult> ThirdStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
{
await stepContext.Context.SendActivityAsync("End of Main");
return await stepContext.EndDialogAsync();
}
}
}
DialogA and DialogB are same with maindialog but they inherit ComponentDialog instead of CancelAndHelpDialog.
some screenshot for reference:
when dialogA is called via choice of main menu it starts normally
dialogA is called via intent interruption on it end abruptly.
qna maker anwering questions even if there is an active dialog
Upvotes: 0
Views: 169
Reputation: 1012
Hello I managed to fix my problem. For future reference of others here is how i fixed it.
I fixed the running of dialog via intent interruption by adding this line in the cancelandhelpdialog
return new DialogTurnResult(DialogTurnStatus.Waiting);
if (topIntent.Equals("MainDialog"))
{
if (innerDc.ActiveDialog != null)
{
await innerDc.CancelAllDialogsAsync();
await innerDc.BeginDialogAsync(nameof(MainDialog));
}
else
{
await innerDc.BeginDialogAsync(nameof(MainDialog));
}
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
and the QnaMaker to only answer when there are no active dialog by doing this in the OnTurnAsync:
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
await base.OnTurnAsync(turnContext, cancellationToken);
var topDispatch = turnContext.TurnState.Get<string>("topDispatch");
var dispatchRecognizerResult = turnContext.TurnState.Get<RecognizerResult>("dispatchRecognizerResult");
var dc = await Dialogs.CreateContextAsync(turnContext, cancellationToken);
var dialogResult = await dc.ContinueDialogAsync();
if (!dc.Context.Responded)
{
switch (dialogResult.Status)
{
case DialogTurnStatus.Empty:
await DispatchToTopIntentAsync(turnContext, topDispatch, dispatchRecognizerResult, 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);
}
Hope this will help somebody.
Upvotes: 1