使用带有反馈的 QnAMaker 示例时出错

Error using QnAMaker sample with feedback

我一直在尝试将 Microsoft Cognitive 和 AI 工具包与 QnAMaker API 结合使用,以创建一个简单的聊天机器人。

虽然我的普通 qnaMakerAi 聊天机器人运行良好,但在我尝试增​​强其功能并将机器人反馈包含在响应中时出现了问题。

我一直在遵循所引用的确切代码示例 here

我遇到的问题是:

Exception: Object reference not set to an instance of an object.
[File of type 'text/plain']. 

调试器在代码部分给出错误 -(在文件 WebApiConfig.cs)

    JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
    {
        ContractResolver = new CamelCasePropertyNamesContractResolver(),
        Formatting = Newtonsoft.Json.Formatting.Indented,
        NullValueHandling = NullValueHandling.Ignore,
    };

我在-https://github.com/Microsoft/BotBuilder/issues/4267中也提出了该问题的详细描述。

请检查并提出建议。

根据用户评论,这里是 MessagesController 的代码 -

using System;
using System.Threading.Tasks;
using System.Web.Http;

using Microsoft.Bot.Connector;
using Microsoft.Bot.Builder.Dialogs;
using System.Web.Http.Description;
using System.Net.Http;
using QnABot.Dialogs;

namespace Microsoft.Bot.Sample.QnABot
{
    [BotAuthentication]
    public class MessagesController : ApiController
    {
        /// <summary>
        /// POST: api/Messages
        /// receive a message from a user and send replies
        /// </summary>
        /// <param name="activity"></param>
        [ResponseType(typeof(void))]
        public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
        {
            // check if activity is of type message
            if (activity.GetActivityType() == ActivityTypes.Message)
            {
                //await Conversation.SendAsync(activity, () => new RootDialog());
                await Conversation.SendAsync(activity, () => new QnaDialog());
            }
            else
            {
                HandleSystemMessage(activity);
            }
            return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
        }

        private Activity HandleSystemMessage(Activity message)
        {
            if (message.Type == ActivityTypes.DeleteUserData)
            {
                // Implement user deletion here
                // If we handle user deletion, return a real message
            }
            else if (message.Type == ActivityTypes.ConversationUpdate)
            {
                // Handle conversation state changes, like members being added and removed
                // Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info
                // Not available in all channels
            }
            else if (message.Type == ActivityTypes.ContactRelationUpdate)
            {
                // Handle add/remove from contact lists
                // Activity.From + Activity.Action represent what happened
            }
            else if (message.Type == ActivityTypes.Typing)
            {
                // Handle knowing tha the user is typing
            }
            else if (message.Type == ActivityTypes.Ping)
            {

            }

            return null;
        }
    }
}

对于 QnADialog -

using Microsoft.Bot.Builder.Azure;
using Microsoft.Bot.Builder.CognitiveServices.QnAMaker;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
using System.Web;

namespace QnABot.Dialogs
{
    [Serializable]
    public class QnaDialog : QnAMakerDialog
    {
        public QnaDialog() : base(new QnAMakerService(new QnAMakerAttribute("b372e477-0a2f-4a5a-88d5-3a664d16a4c3", "4ee02ead3xxxxxx", "Sorry, I couldn't find an answer for that", 0.5)))
        {
        }

        protected override async Task RespondFromQnAMakerResultAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
        {
            // answer is a string
            var answer = result.Answers.First().Answer;

            Activity reply = ((Activity)context.Activity).CreateReply();

            string[] qnaAnswerData = answer.Split(';');
            int dataSize = qnaAnswerData.Length;

            string title = qnaAnswerData[0];
            string description = qnaAnswerData[1];
            string url = qnaAnswerData[2];
            string imageURL = qnaAnswerData[3];

            HeroCard card = new HeroCard
            {
                Title = title,
                Subtitle = description,
            };

            card.Buttons = new List<CardAction>
            {
                new CardAction(ActionTypes.OpenUrl, "Learn More", value: url)
            };

            card.Images = new List<CardImage>
            {
                new CardImage( url = imageURL)
            };

            reply.Attachments.Add(card.ToAttachment());


            await context.PostAsync(reply);
        }

        protected override async Task DefaultWaitNextMessageAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
        {
            // get the URL
            var answer = result.Answers.First().Answer;
            string[] qnaAnswerData = answer.Split(';');
            string qnaURL = qnaAnswerData[2];

            // pass user's question
            var userQuestion = (context.Activity as Activity).Text;

            context.Call(new FeedbackDialog(qnaURL, userQuestion), ResumeAfterFeedback);

        }

        private async Task ResumeAfterFeedback(IDialogContext context, IAwaitable<IMessageActivity> result)
        {
            if (await result != null)
            {
                await MessageReceivedAsync(context, result);
            }
            else
            {
                context.Done<IMessageActivity>(null);
            }
        }

    }
}

对于反馈对话框 -

using Microsoft.ApplicationInsights;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;

namespace QnABot.Dialogs
{
    [Serializable]
    public class FeedbackDialog : IDialog<IMessageActivity>
    {
        private string qnaURL;
        private string userQuestion;

        public FeedbackDialog(string url, string question)
        {
            // keep track of data associated with feedback
            qnaURL = url;
            userQuestion = question;
        }

        public async Task StartAsync(IDialogContext context)
        {
            var feedback = ((Activity)context.Activity).CreateReply("Did you find what you need?");

            feedback.SuggestedActions = new SuggestedActions()
            {
                Actions = new List<CardAction>()
                {
                    new CardAction(){ Title = "", Type=ActionTypes.PostBack, Value=$"yes-positive-feedback" },
                    new CardAction(){ Title = "", Type=ActionTypes.PostBack, Value=$"no-negative-feedback" }
                }
            };

            await context.PostAsync(feedback);

            context.Wait(this.MessageReceivedAsync);
        }

        public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
        {
            var userFeedback = await result;

            if (userFeedback.Text.Contains("yes-positive-feedback") || userFeedback.Text.Contains("no-negative-feedback"))
            {
                // create telemetry client to post to Application Insights 
                TelemetryClient telemetry = new TelemetryClient();

                if (userFeedback.Text.Contains("yes-positive-feedback"))
                {
                    // post feedback to App Insights
                    var properties = new Dictionary<string, string>
                    {
                        {"Question", userQuestion },
                        {"URL", qnaURL },
                        {"Vote", "Yes" }
                        // add properties relevant to your bot 
                    };

                    telemetry.TrackEvent("Yes-Vote", properties);
                }
                else if (userFeedback.Text.Contains("no-negative-feedback"))
                {
                    // post feedback to App Insights
                }

                await context.PostAsync("Thanks for your feedback!");

                context.Done<IMessageActivity>(null);
            }
            else
            {
                // no feedback, return to QnA dialog
                context.Done<IMessageActivity>(userFeedback);
            }
        }
    }
}

1st,配置错误

好的,第一个问题是您在 QnaDialog 声明中反转了 2 个参数:

public QnaDialog() : base(new QnAMakerService(new QnAMakerAttribute("b372e477-0a2f-4a5a-88d5-3a664d16a4c3", "4ee02ead3xxxxxx", "Sorry, I couldn't find an answer for that", 0.5)))

语法为:Qn

public QnAMakerAttribute(string subscriptionKey, string knowledgebaseId,  ...

这里你颠倒了你的Key和你的knowledgebaseId。 Guid 应该在第二位,而不是第一位。

请注意,我在问题和回复中修改了您的订阅密钥,您应该注意这样分享。

代码改进

您使用的示例似乎无效:

  • 在没有匹配的情况下
  • 当您的答案不是由带有 ; 分隔符的字符串组成时(例如当您输入 "hi" 时,回复是 "hello"

我添加了一些安全措施以避免在这些情况下出现错误:

protected override async Task RespondFromQnAMakerResultAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
    // answer is a string
    var answer = result.Answers.First().Answer;

    Activity reply = ((Activity)context.Activity).CreateReply();

    var qnaAnswerData = answer.Split(';');
    var dataSize = qnaAnswerData.Length;

    if (dataSize == 3)
    {
        var title = qnaAnswerData[0];
        var description = qnaAnswerData[1];
        var url = qnaAnswerData[2];
        var imageUrl = qnaAnswerData[3];

        var card = new HeroCard
        {
            Title = title,
            Subtitle = description,
            Buttons = new List<CardAction>
            {
                new CardAction(ActionTypes.OpenUrl, "Learn More", value: url)
            },
            Images = new List<CardImage>
            {
                new CardImage(url = imageUrl)
            },
        };

        reply.Attachments.Add(card.ToAttachment());
    }
    else
    {
        reply.Text = answer;
    }

    await context.PostAsync(reply);
}

protected override async Task DefaultWaitNextMessageAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
    if (result.Answers.Count > 0)
    {
        // get the URL
        var answer = result.Answers.First().Answer;
        var qnaAnswerData = answer.Split(';');

        var dataSize = qnaAnswerData.Length;

        if (dataSize == 3)
        {
            var qnaUrl = qnaAnswerData[2];

            // pass user's question
            var userQuestion = (context.Activity as Activity).Text;

            context.Call(new FeedbackDialog(qnaUrl, userQuestion), ResumeAfterFeedback);
        }
        else
        {
            await ResumeAfterFeedback(context, new AwaitableFromItem<IMessageActivity>(null));
        }
    }
    else
    {
        await ResumeAfterFeedback(context, new AwaitableFromItem<IMessageActivity>(null));
    }
}

private async Task ResumeAfterFeedback(IDialogContext context, IAwaitable<IMessageActivity> result)
{
    if (await result != null)
    {
        await MessageReceivedAsync(context, result);
    }
    else
    {
        context.Done<IMessageActivity>(null);
    }
}