【问题标题】:C# bot V4 Text Prompt inside a waterfall dialog瀑布对话框中的 C# bot V4 文本提示
【发布时间】:2019-11-22 08:27:01
【问题描述】:

我需要添加一个问题“这有帮助吗?”在得到 QnA 的回复并听取用户的反馈后。如果对此没有响应并且下一个输入是全新的查询,则流程应从 bot.cs 重新启动

我尝试使用 textprompt,但在模拟器中测试时,bot 不会在提示后等待用户输入。

Bot.cs

public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
    var activity = turnContext.Activity;
    var dc = await _dialogs.CreateContextAsync(turnContext);

    if (turnContext == null)
    {
        throw new ArgumentNullException(nameof(turnContext));
    }

    if (turnContext.Activity.Type == ActivityTypes.Message)
    {
        if (turnContext.Activity.Text != null)
        {                   
            var luisResults = await _services.LuisServices[LuisConfiguration].RecognizeAsync(dc.Context, cancellationToken);                    
            var luisProperties = LuisEntities.FromLuisResults(luisResults);                  
            await _luisEntitiesAccessor.SetAsync(turnContext, luisProperties);                   
            var topScoringIntent = luisResults?.GetTopScoringIntent();
            var topIntent = topScoringIntent.Value.intent;

            switch (topIntent)
            {
                case NoneIntent:
                    await dc.BeginDialogAsync(QnADialog.Name);
                    break;
                case GreetingsIntent:
                    await dc.BeginDialogAsync(QnAGreetingsDialog.Name);                                       
                    break;
                case CredentialsIntent:
                    await dc.BeginDialogAsync(CredentialsDialog.Name);
                    break;
                case ContactusIntent:                                       
                    await dc.BeginDialogAsync(FeedbackDialog.Name);
                    break;
                case FeedbackIntent:
                    await dc.BeginDialogAsync(FeedbackDialog.Name);                                       
                    break;
                default:                                        
                    await dc.Context.SendActivityAsync("I didn't understand what you just said to me.");                                       
                    break;
            }
        }
        else if (string.IsNullOrEmpty(turnContext.Activity.Text))
        {
            await HandleSubmitActionAsync(turnContext, userProfile);
        }
    }
    else if (turnContext.Activity.Type == ActivityTypes.ConversationUpdate)
    {
        if (turnContext.Activity.MembersAdded != null)
        {
            await SendWelcomeMessageAsync(turnContext);
        }
    }
    else if (turnContext.Activity.Type == ActivityTypes.Event)
    {
        await SendWelcomeMessageAsync(turnContext);
    }
    else
    {
        await turnContext.SendActivityAsync($"{turnContext.Activity.Type} event detected");
    }

    // Save the dialog state into the conversation state.
    await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
}

QnADialog.cs - 我希望提示在其中工作的对话框

public class QnADialog : ComponentDialog
{        
    public const int QnaNumResults = 1;
    public const double QnaConfidenceThreshold = 0.5;

    public const string QnaConfiguration = "QnAFaqSubscriptionKey";
    private const string QnAFeedbackDialog = "qnaDialog";
    public const string Name = "QnA";
    public const string TextPrompt = "textPrompt";

    private readonly BotServices _services;
    private readonly IStatePropertyAccessor<UserProfile> _userProfileAccessor;

    Action<string, string, bool, int, int> updateQna;
    private int InvalidMessageCount = 0;
    string Query = string.Empty;
    List<int> qnaIdStorage;
    UserProfile userProfile = new UserProfile();

    public QnADialog(Action<string, string, bool, int, int> updateQna, bool isCollection, List<int> rotationTemStorage, BotServices services, UserProfile _userProfile, IStatePropertyAccessor<UserProfile> userProfileAccessor, int invalidMessageCount = 0, string dialogId = null)
        : base(Name)
    {
        _services = services ?? throw new ArgumentNullException(nameof(services));
        _userProfileAccessor = userProfileAccessor ?? throw new ArgumentNullException(nameof(userProfileAccessor));
        userProfile = _userProfile;

        this.updateQna = updateQna;
        this.InvalidMessageCount = invalidMessageCount;
        qnaIdStorage = rotationTemStorage;

        var waterfallSteps = new WaterfallStep[]
        {
            BeginStepAsync,
            FetchFAQResultStepAsync,
            FeedbackStepAsync,                                                                 
            FeedbackResponseStepAsync,                       

        };           

        AddDialog(new WaterfallDialog(QnAFeedbackDialog, waterfallSteps));            
        AddDialog(new TextPrompt("userFeed"));      
    }

    public async Task<DialogTurnResult> BeginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        var messageToForward = stepContext.Context.Activity;
        UserProfile.previousQuestion = messageToForward.Text;

        string[] supportList = { "HELP", "FEEDBACK", "SUPPORT", "ESCALATE", "AGENT" };

        if (messageToForward.Text == null || messageToForward.Text.ToLower() == "no")
        {
            await stepContext.Context.SendActivityAsync("Sorry, I was not able to help you.");
            return await stepContext.EndDialogAsync();
        }
        else if (messageToForward.Text == null || supportList.Any(x => x == messageToForward.Text.ToUpper()))
        {
            await stepContext.Context.SendActivityAsync("Please reach out to... ");
            return await stepContext.EndDialogAsync();
        }
        else
        {
            return await stepContext.NextAsync();
        }
    }

    private async Task<DialogTurnResult> FetchFAQResultStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
        var message = stepContext.Context.Activity;

        var qnaResult = await FaqQnaMakerService.GetQnaResult(_services, stepContext, this.Query);
        var qnaIdColl = GetQnaIdColl(this.Query, qnaResult);
        int qnaPreviousId = 0;
        int qnaNewId = 0;

        if (qnaIdColl != null && qnaIdColl.Count > 1)
        {
            qnaIdColl = qnaIdColl.Distinct().OrderBy(x => x).ToList();
            //Compare the previous Qnaid collection and existing collection , if it is matching produce the result.
            var matchItem = qnaIdColl.Intersect(qnaIdStorage);

            if (matchItem.Count() == 0)
            {
                //If there is no previous collection Qna id then take the first item from the existing Qna collection
                qnaNewId = qnaIdColl.FirstOrDefault();
            }
            else
            {
                //If there any previous Qnaid that contain in the existing collection then pick the next value and generate a new qna result.
                qnaPreviousId = matchItem.FirstOrDefault();
                qnaNewId = GetNextRotationKey(qnaIdColl, qnaPreviousId);
            }

            //Create a new response based on selected new qna id.                
            qnaResult = new[] { qnaResult.Where(x => x.Id == qnaNewId).Single() };                
        }

        if (qnaResult.First().Answer.Length > 0)
        {
            if (qnaResult.First().Score > 0)
            {
                updateQna(this.Query, qnaResult.First().Answer, false, qnaPreviousId, qnaNewId);
                InvalidMessageCount = 0;
                var QuestionCollection = TextFormatter.FormattedQuestionColl(qnaResult.First().Answer);

                if (QuestionCollection != null)
                {
                    userProfile.IsAswerCollection = true;
                    updateQna(this.Query, qnaResult.First().Answer, true, qnaPreviousId, qnaNewId);
                    var replyMessage = stepContext.Context.Activity.CreateReply();
                    replyMessage.Attachments = new List<Attachment>() { AllAdaptiveCard.QnaAttachment(new Tuple<string, string[]>(QuestionCollection.Item2, QuestionCollection.Item3)) };

                    if (!string.IsNullOrEmpty(QuestionCollection.Item1))
                    {
                        await stepContext.Context.SendActivityAsync(QuestionCollection.Item1);
                    }

                    await stepContext.Context.SendActivityAsync(replyMessage);
                    return await stepContext.EndDialogAsync();                      
                }
                else
                {
                    await stepContext.Context.SendActivityAsync(qnaResult.First().Answer);
                }                   
            }             
            else
            {
                InvalidMessageCount++;
                return await stepContext.ContinueDialogAsync();                   
            }
        }          

        return await stepContext.NextAsync();
    }

    private async Task<DialogTurnResult> FeedbackStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
        return await stepContext.PromptAsync("userFeed", new PromptOptions
        {
            Prompt = stepContext.Context.Activity.CreateReply("Did this help?")                
        });
    }

    private async Task<DialogTurnResult> FeedbackResponseStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
        var message = stepContext.Context.Activity;
        var mesgActivity = message as Activity;

        string var = userProfile.qnaData;
        var qnaResultModel = new { InvalidMessageCount = 0, originalQueryText = string.Empty };
        NeedMoreInformation needmoreInfo = NeedMoreInformation.NotSelected;

        if (message != null && message.Text == null && message.Value != null)
        {
            dynamic value = mesgActivity.Value.ToString();
            UserReply response = JsonConvert.DeserializeObject<UserReply>(value);
            if (!string.IsNullOrEmpty(response.Reply))
            {
                mesgActivity.Text = response.Reply;
            }

        }

        //This if condition work only the user reply back to the question "Did this help?"
        if (userProfile.needMoreInformation == true && message?.Text?.ToLower() != "yes" && message?.Text?.ToLower() != "no")
        {
            //The response message pass to LUIS service to understand the intention of the conversation is “yes” or “no” 
            bool? moreInformationYes = await LUISService.GetResultAESChatBotYesNo(message?.Text);

            if (moreInformationYes != null && moreInformationYes == true)
            {
                //Once the LUIS understand the conversation change the original message to yes.
                message.Text = "yes";
                //needmoreInfo = NeedMoreInformation.Yes;
            }
            else if (moreInformationYes != null && moreInformationYes == false)
            {
                ////Once the LUIS understand the conversation change the original message to no.
                message.Text = "no";
                needmoreInfo = NeedMoreInformation.No;
            }
            else
            {
                needmoreInfo = NeedMoreInformation.None;
            }
        }

        if (userProfile.needMoreInformation == true && message?.Text?.ToLower() == "yes")
        {                   
            userProfile.qnaInvalidMessageCount = 0;
            userProfile.needMoreInformation = false;
            dynamic value = stepContext.Context.Activity.Value;
            var output = JsonConvert.DeserializeObject<UserReply>(stepContext.Context.Activity.Value.ToString());
            if (userProfile.feedbackCard == false)
            {
                var replyMessage = stepContext.Context.Activity.CreateReply();
                replyMessage.Attachments = new List<Attachment>() { AllAdaptiveCard.FeedbackAdapativecard() };
                await stepContext.Context.SendActivityAsync(replyMessage);
            }
            if (output.Reply != "yes")
            {
                await AdaptiveCardReplyAsync(_services, stepContext, userProfile);
            }                   
        }
        else if (userProfile.needMoreInformation == true && message?.Text?.ToLower() == "no")
        {
            userProfile.qnaInvalidMessageCount = 0;
            userProfile.needMoreInformation = false;
            dynamic value = stepContext.Context.Activity.Value;

            if (value.Type == "GetMoreContent")
            {
                await AdaptiveCardGetMoreContent(_services, stepContext, userProfile);
            }
            else if (value.Type == "GetHelpSubmit")
            {
                await AdaptiveCardReplyAsync(_services, stepContext, userProfile);
            }
            else if (userProfile.getMoreContentCard == false)
            {
                var replyMessage = stepContext.Context.Activity.CreateReply();
                replyMessage.Attachments = new List<Attachment>() { AllAdaptiveCard.GetMoreContent() };
                await stepContext.Context.SendActivityAsync(replyMessage);
            }

            // context.Wait(AdaptiveCardGetMoreContent);
        } 
        else
        { 
            await stepContext.BeginDialogAsync(nameof(Bot.cs));                    
        }

        return await stepContext.EndDialogAsync();
    }
}

在此提示之后,它应该进入瀑布步骤中添加的下一步,但它没有。任何可能的建议/帮助将不胜感激。提前致谢!

【问题讨论】:

  • bot 框架是无状态的,因此除非您将其添加到状态管理器中,否则它不会知道提问之间经过了多少时间。因此,它无法自动假定下一个用户响应是针对知识库的,而不是对“是否有帮助”问题的响应。
  • 但是,理想情况下,它必须在提示后调用下一步
  • 我计划通过检查用户是否回答是或否来处理该瀑布步骤中的流程。在任何其他情况下,将其发送到 bot.cs

标签: c# botframework


【解决方案1】:

如果没有看到您在瀑布中的其他步骤的代码,例如 BeginStepAsyncFetchFAQResultStepAsync,很难为您的场景提供准确的答案。

我建议您如何做到这一点是通过在该消息下方使用带有建议操作的消息,一旦单击其中任何一个操作,两个选项都将消失,从而消除了同一用户为同一用户多次提交的可能性回复回复。

你有几个选择:

1) 使用这个过时的 sample,它使用 v3.9.0 的 Microsoft.Bot.Builder NuGet 包,其内容在 QnADialogFeedbackDialog 类中。

重要的部分是QnADialog 实现了QnAMakerDialog

2) 在您向用户发送带有答案的回复之后(我假设在 FetchFAQResultsStepAsync 内),您可以添加以下代码:

var feedback = ((Activity)context.Activity).CreateReply("Did you find what you need?");

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

await context.PostAsync(feedback);

编辑

感谢您为您的 QnADialog 类提供完整代码,不幸的是,我无法在本地运行它,因为您调用的其他方法和类包括 GetQnaIdCollGetNextRotationKeyTextFormatter.FormattedQuestionColl 等方法的实现,但是没有提供。您提示用户做出响应的代码看起来正确,但听起来您甚至没有显示反馈提示,或者您正在显示反馈提示但您卡在那里 - 您可以确认它是哪个?您是否尝试过单步执行代码以查看它所采用的路径?

另一个建议是将您的 QnA 步骤和反馈步骤分开到单独的对话框中,我已经证明了下面的示例反馈对话框。

using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using System.Threading;
using System.Threading.Tasks;

namespace ChatBot.VirtualAssistant.Dialogs
{
    public class RateAnswerDialog : ComponentDialog
    {
        public RateAnswerDialog()
            : base(nameof(RateAnswerDialog))
        {
            InitialDialogId = nameof(RateAnswerDialog);

            var askToRate = new WaterfallStep[]
            {
                AskRating,
                FinishDialog
            };

            AddDialog(new TextPrompt(nameof(TextPrompt)));
            AddDialog(new WaterfallDialog(InitialDialogId, askToRate));
        }

        private async Task<DialogTurnResult> AskRating(WaterfallStepContext sc, CancellationToken cancellationToken)
        {
            PromptOptions promptOptions = new PromptOptions
            {
                Prompt = MessageFactory.Text("Was this helpful?")
            };

            return await sc.PromptAsync(nameof(TextPrompt), promptOptions);
        }

        private async Task<DialogTurnResult> FinishDialog(WaterfallStepContext sc, CancellationToken cancellationToken)
        {
            return await sc.EndDialogAsync(sc);
        }

        protected override async Task<DialogTurnResult> EndComponentAsync(DialogContext outerDc, object context, CancellationToken cancellationToken)
        {
            var waterfallContext = (WaterfallStepContext)context;

            var userResponse = ((string)waterfallContext.Result).ToLowerInvariant();

            if (userResponse == "yes")
            {
                await waterfallContext.Context.SendActivityAsync("Thank you for your feedback");
            }
            else if (userResponse == "no")
            {
                await waterfallContext.Context.SendActivityAsync("Sorry I couldn't help you");
            }
            else
            {
                await waterfallContext.Context.SendActivityAsync("The valid answers are 'yes' or 'no'");

                // TODO reprompt if required
            }

            return await outerDc.EndDialogAsync();
        }
    }
}

【讨论】:

  • 嗨@Matt,我已经用完整的 QnADialog 更新了我的帖子。此对话框基本上从 QnA 获取响应。关于您对使用建议操作的建议,我们的业务要求不允许我们在问题后有任何类型的按钮。因此,我在下一步中添加了代码来处理用户响应。但是,在给出响应后(在模拟器中),控件完全退出对话框并将其视为新响应。
  • @SM 我已经编辑了我的答案,您是否也可以在某处上传您的项目副本以便我可以调试它,或指定所需的行为(逐步),然后是实际行为(一步一步)帮助调试。谢谢。例如预期 = 1) 用户提出问题,2) 机器人回答问题,3) 机器人询问用户反馈,4) 用户回答是/否反馈 5) 机器人接收用户反馈,6) QnA 对话结束。实际 = 1) 用户提出问题,2) 机器人回答问题,3) 机器人要求用户提供反馈,4) 用户回答是/否以获取反馈......等等
  • 嗨@Matt Stannett,我可以将对话文件发送到任何可能的邮件 ID 吗?
  • 我能够通过添加等待 dc.ContinueDialogAsync() 来解决问题;但是,当出现qna 答案和反馈消息之间的自适应卡片
  • 将其作为新问题发布stackoverflow.com/questions/57061353/…
猜你喜欢
  • 1970-01-01
  • 2019-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-18
  • 1970-01-01
  • 1970-01-01
  • 2020-08-20
相关资源
最近更新 更多