Microsoft Bot Framework - 用户被重定向到错误的对话框
Microsoft Bot Framework - User is redirected to the wrong Dialog
我正在使用 Microsoft Bot Framework(适用于 .NET)、QnA Maker 和 QnAMakerDialog (https://github.com/garypretty/botframework/tree/master/QnAMakerDialog) 开发聊天机器人。托管聊天框控件的机器人和 Web 项目部署在 Azure 中。我是
使用 Direct Line 作为渠道。
对话流程非常简单。用户从主分支开始。根据用户输入,对话继续使用 QnAMakerDialog 或用于反馈的自定义对话框。
问题如下:
用户在主分支中启动。只要用户不键入 'end',我就会将对话转发到 QnA 对话框并尝试回答他的问题。在某个时候,用户键入 'end'。因此,我启动了“反馈”对话框。用户输入反馈。而现在,应该感谢他的反馈并将其发送回 QnA 对话。取而代之的是,他得到的答复是在 QnA 知识数据库中没有找到好的答案。这意味着,不知何故,他发现自己走错了路! bot以为自己在QnA分支,其实他应该在feedback分支...
按照相同的步骤,无法始终重现此错误。它随机发生,没有规律。甚至更多 - 它只发生在某些环境中。它从来没有发生在我的开发机器上,它很少发生在一个环境中,它经常发生在第三个环境中。 (这两个环境的配置几乎相同,问题不可能出自那里)。此外,问题不可能来自 QnAMakerDialog – 我使用自定义 QnADialog 进行了测试,它总是 returns 静态消息而不是来自 QnAMaker 的答案,问题仍然存在。
这是代码。非常欢迎任何想法。
[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);
}
}
}
由于 MessageReceivedAsync 在 HomeDialog 中只显示 PromptDialog,FeedbackDialogResumeAfter 也应该只显示提示对话框。
我认为以下代码会产生所需的行为:
[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 QnaDialogResumeAfter(context, result);
}
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 QnaDialog.FeedbackDialog(), FeedbackDialogResumeAfter);
}
else
{
await context.Forward(new QnaDialog(), QnaDialogResumeAfter, context.Activity, CancellationToken.None);
}
}
private async Task FeedbackDialogResumeAfter(IDialogContext context, IAwaitable<object> result)
{
PromptDialog.Text(context,
ResumeAfterQuestionTyped,
"Thank you for your feedback. You can now continue to ask me more questions.",
"Please retry", 3);
}
[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);
}
}
}
}
我发布了答案,因为这可能会在将来帮助其他人:
问题是我使用的是内存中的机器人状态,而 Microsoft 文档中清楚地记录了这应该仅用于测试目的。
var store = new InMemoryDataStore(); // volatile in-memory store
builder.Register(c => store)
.Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
.AsSelf()
.SingleInstance();
内置的 Azure 负载均衡器将 DirectLine 的请求随机转发到其中一个实例,这就是 API 完全丢失的原因,因为 API 的每个实例都有自己的“状态”记忆。
因此,基本上解决方法是为机器人实施状态管理,而不是使用默认的内存状态。
我正在使用 Microsoft Bot Framework(适用于 .NET)、QnA Maker 和 QnAMakerDialog (https://github.com/garypretty/botframework/tree/master/QnAMakerDialog) 开发聊天机器人。托管聊天框控件的机器人和 Web 项目部署在 Azure 中。我是 使用 Direct Line 作为渠道。
对话流程非常简单。用户从主分支开始。根据用户输入,对话继续使用 QnAMakerDialog 或用于反馈的自定义对话框。
问题如下:
用户在主分支中启动。只要用户不键入 'end',我就会将对话转发到 QnA 对话框并尝试回答他的问题。在某个时候,用户键入 'end'。因此,我启动了“反馈”对话框。用户输入反馈。而现在,应该感谢他的反馈并将其发送回 QnA 对话。取而代之的是,他得到的答复是在 QnA 知识数据库中没有找到好的答案。这意味着,不知何故,他发现自己走错了路! bot以为自己在QnA分支,其实他应该在feedback分支...
按照相同的步骤,无法始终重现此错误。它随机发生,没有规律。甚至更多 - 它只发生在某些环境中。它从来没有发生在我的开发机器上,它很少发生在一个环境中,它经常发生在第三个环境中。 (这两个环境的配置几乎相同,问题不可能出自那里)。此外,问题不可能来自 QnAMakerDialog – 我使用自定义 QnADialog 进行了测试,它总是 returns 静态消息而不是来自 QnAMaker 的答案,问题仍然存在。
这是代码。非常欢迎任何想法。
[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);
}
}
}
由于 MessageReceivedAsync 在 HomeDialog 中只显示 PromptDialog,FeedbackDialogResumeAfter 也应该只显示提示对话框。
我认为以下代码会产生所需的行为:
[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 QnaDialogResumeAfter(context, result);
}
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 QnaDialog.FeedbackDialog(), FeedbackDialogResumeAfter);
}
else
{
await context.Forward(new QnaDialog(), QnaDialogResumeAfter, context.Activity, CancellationToken.None);
}
}
private async Task FeedbackDialogResumeAfter(IDialogContext context, IAwaitable<object> result)
{
PromptDialog.Text(context,
ResumeAfterQuestionTyped,
"Thank you for your feedback. You can now continue to ask me more questions.",
"Please retry", 3);
}
[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);
}
}
}
}
我发布了答案,因为这可能会在将来帮助其他人:
问题是我使用的是内存中的机器人状态,而 Microsoft 文档中清楚地记录了这应该仅用于测试目的。
var store = new InMemoryDataStore(); // volatile in-memory store
builder.Register(c => store)
.Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
.AsSelf()
.SingleInstance();
内置的 Azure 负载均衡器将 DirectLine 的请求随机转发到其中一个实例,这就是 API 完全丢失的原因,因为 API 的每个实例都有自己的“状态”记忆。
因此,基本上解决方法是为机器人实施状态管理,而不是使用默认的内存状态。