I am developing a chatbot using Microsoft Bot Framework (for .NET), QnA Maker & QnAMakerDialog (https://github.com/garypretty/botframework/tree/master/QnAMakerDialog). The bot and the web project hosting the chatbox control are deployed in Azure. I am using Direct Line as a channel.
The conversation flow is very simple. The user starts on a main branch. Based on user input, the conversation continues with a QnAMakerDialog or a custom dialog used for feedback.
The problem is as it follows:
The user starts in the main branch. As long as the user does not type 'end', I forward the conversation to the QnA dialog and try to give an answer to his question. At some point, the user types 'end'. So, I start the Feedback dialog. The user types the feedback. And now, he is supposed to be thanked for that feedback and sent back to the QnA dialog. Instead of this, he is being replied that no good answer was found in the QnA database of knowledge. This means that, somehow, he finds himself on the wrong branch! The bot thinks he is on the QnA branch, but in fact he should be on the feedback branch...
This error cannot be reproduced all the time, following the same steps. It happens randomly, without a pattern. Even more - it only happens in some environments. It never happens on my development machine, it very rarely happens on one environment and it happens very often on a third environment. (The two environments are configured almost identical and the problem cannot arise from there). Also, the problem cannot come from QnAMakerDialog – I made a test with a custom QnADialog which always returns a static message instead of an answer from QnAMaker and the problem is still present.
Here's the code. Any ideas are very much welcomed.
[BotAuthentication]
public class MessagesController : ApiController
{
private readonly ILog log;
public MessagesController(ILog log)
{
this.log = log;
}
internal static IDialog<object> MakeRoot()
{
return Chain.From(() => new HomeDialog());
}
public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
var client = new ConnectorClient(new Uri(activity.ServiceUrl));
try
{
switch (activity.GetActivityType())
{
case ActivityTypes.Message:
var typingReply = activity.CreateReply();
typingReply.Type = ActivityTypes.Typing;
await client.Conversations.ReplyToActivityAsync(typingReply);
await Conversation.SendAsync(activity, MakeRoot);
break;
default:
HandleSystemMessage(activity);
break;
}
}
catch (Exception ex)
{
var errorReply = activity.CreateReply();
errorReply.Type = ActivityTypes.Message;
errorReply.Text ="I'm sorry, I'm having issues understanding you. Let's try again.";
await client.Conversations.ReplyToActivityAsync(errorReply);
log.Error("Issue in the bot.", ex);
}
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
}
private Activity HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
}
else if (message.Type == ActivityTypes.Typing)
{
}
else if (message.Type == ActivityTypes.Ping)
{
}
return null;
}
}
[Serializable]
public class HomeDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
await RedirectToQnaDialog(context);
}
private async Task RedirectToQnaDialog(IDialogContext context)
{
await context.Forward(new QnaDialog(), QnaDialogResumeAfter, context.Activity, CancellationToken.None);
}
private async Task QnaDialogResumeAfter(IDialogContext context, IAwaitable<object> result)
{
var message = await result;
PromptDialog.Text(context,
ResumeAfterQuestionTyped,
"Type your question or 'end' to end this conversation.",
"Please retry", 3);
}
private async Task ResumeAfterQuestionTyped(IDialogContext context, IAwaitable<string> inputFromUser)
{
var question = await inputFromUser;
if (question.ToLower().Equals("end"))
{
await context.PostAsync("You would really help me out by giving feedback. " +
"What subjects should we include to provide answers for your questions?");
context.Call(new FeedbackDialog(), FeedbackDialogResumeAfter);
}
else
{
await context.Forward(new QnaDialog(), QnaDialogResumeAfter, context.Activity, CancellationToken.None);
}
}
private async Task FeedbackDialogResumeAfter(IDialogContext context, IAwaitable<object> result)
{
await context.PostAsync("Thank you for your feedback. You can now continue to ask me more questions.");
context.Wait(MessageReceivedAsync);
}
[Serializable]
public class QnaDialog : QnAMakerDialog
{
public QnaDialog() : base(new QnAMakerService
(new QnAMakerAttribute(ConfigurationManager.AppSettings["QnaSubscriptionKey"],
ConfigurationManager.AppSettings["QnaKnownledgeBaseKey"],
ConfigurationManager.AppSettings["QnaNotFoundReply"],
Convert.ToDouble(ConfigurationManager.AppSettings["QnaPrecentageMatch"]), 5)))
{
}
protected override async Task RespondFromQnAMakerResultAsync(IDialogContext context, IMessageActivity message,
QnAMakerResults results)
{
if (results.Answers.Count > 0)
{
var response = results.Answers.First().Answer;
await context.PostAsync(response);
}
}
protected override async Task DefaultWaitNextMessageAsync(IDialogContext context, IMessageActivity message,
QnAMakerResults result)
{
context.Done<IMessageActivity>(null);
}
[Serializable]
public class FeedbackDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
context.Done(message);
}
}
}