1

我正在使用 Microsoft Bot Framework(用于 .NET)、QnA Maker 和 QnAMakerDialog(https://github.com/garypretty/botframework/tree/master/QnAMakerDialog)开发一个聊天机器人。托管聊天框控件的机器人和 Web 项目部署在 Azure 中。我使用 Direct Line 作为渠道。

对话流程非常简单。用户从主分支开始。根据用户输入,对话继续使用 QnAMakerDialog 或用于反馈的自定义对话框。

问题如下:

用户从主分支开始。只要用户不键入“结束”,我就会将对话转发到 QnA 对话框并尝试回答他的问题。在某些时候,用户键入“结束”。所以,我启动了反馈对话框。用户键入反馈。现在,应该感谢他的反馈并将其送回 QnA 对话。取而代之的是,他得到的答复是在 QnA 知识数据库中没有找到好的答案。这意味着,不知何故,他发现自己在错误的分支上!机器人认为他在 QnA 分支,但实际上他应该在反馈分支......

按照相同的步骤,无法始终重现此错误。它随机发生,没有规律。甚至更多——它只发生在某些环境中。它从来没有发生在我的开发机器上,它很少发生在一个环境中,它经常发生在第三个环境中。(这两个环境的配置几乎相同,问题不会从那里出现)。此外,问题不能来自 QnAMakerDialog - 我使用自定义 QnADialog 进行了测试,它始终返回静态消息而不是来自 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);
    }
  }

}

4

2 回答 2

1

我正在发布答案,因为这可能会在将来对其他人有所帮助:

问题是我使用的是内存中的 bot 状态,而 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 的每个实例在内存中都有自己的“状态”。

因此,基本上解决方法是为机器人实现状态管理,而不是使用默认的内存状态。

于 2018-04-23T07:42:29.327 回答
0

由于HomeDialog中的MessageReceivedAsync只显示一个 PromptDialog,FeedbackDialogResumeAfter也应该只显示一个 PromptDialog。

我认为以下代码将产生所需的行为:

[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);
            }
        }
    }
}
于 2018-04-17T22:11:31.630 回答