Botframework v4:如何简化此瀑布对话框? [英] Botframework v4: How to simplify this waterfall dialog?

查看:72
本文介绍了Botframework v4:如何简化此瀑布对话框?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这段代码,但是我认为它过于复杂并且可以简化. 如果用户在不重新启动整个对话框的情况下键入返回",是否还有一种方法可以返回到spefici Waterfall步骤?我是新来的,因为它是新的,所以很难找到关于botframework v4的指南或在线课程.任何帮助将不胜感激,谢谢!

I have this code but but i think its over-complicated and can be simplified. Also is there a way to go back to a spefici waterfall step if ever the user type "back" without restarting the whole dialog? I am new to this and it's hard to find a guide or online course on botframework v4 since it is new. Any help would be appreciated thanks!

  public GetNameAndAgeDialog(string dialogId, IEnumerable<WaterfallStep> steps = null) : base(dialogId, steps)
    {
        var name = "";
        var age = "";

        AddStep(async (stepContext, cancellationToken) =>
        {
            return await stepContext.PromptAsync("textPrompt",
                new PromptOptions
                {
                    Prompt = stepContext.Context.Activity.CreateReply("What's your name?")
                });
        });

        AddStep(async (stepContext, cancellationToken) =>
        {
            name = stepContext.Result.ToString();

            return await stepContext.PromptAsync("numberPrompt",
                new PromptOptions
                {
                    Prompt = stepContext.Context.Activity.CreateReply($"Hi {name}, How old are you ?")
                });
        });

        AddStep(async (stepContext, cancellationToken) =>
        {
            age= stepContext.Result.ToString();

            return await stepContext.PromptAsync("confirmPrompt",
              new PromptOptions
              {
                  Prompt = stepContext.Context.Activity.CreateReply($"Got it you're {name}, age {age}. {Environment.NewLine}Is this correct?"),
                  Choices = new[] {new Choice {Value = "Yes"},
                                   new Choice {Value = "No"},
                   }.ToList()
              });

        });

        AddStep(async (stepContext, cancellationToken) =>
        {
            var result = (stepContext.Result as FoundChoice).Value;

            if(result == "Yes" || result == "yes" || result == "Yeah" || result == "Correct" || result == "correct")
            {
                var state = await (stepContext.Context.TurnState["FPBotAccessors"] as FPBotAccessors).FPBotStateAccessor.GetAsync(stepContext.Context);
                state.Name = name;
                state.Age = int.Parse(age);

                return await stepContext.BeginDialogAsync(MainDialog.Id, cancellationToken);
            }
            else
            {
                //restart the dialog
                return await stepContext.ReplaceDialogAsync(GetNameAndAgeDialog.Id);
            }

        });

    }

    public static string Id => "GetNameAndAgeDialog";
    public static GetNameAndAgeDialog Instance { get; } = new GetNameAndAgeDialog(Id);
}

这是我的访问者代码:

    public class FPBotAccessors
{
    public FPBotAccessors(ConversationState conversationState)
    {
        ConversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState));
    }

    public static string FPBotAccessorName { get; } = $"{nameof(FPBotAccessors)}.FPBotState";
    public IStatePropertyAccessor<FPBotState> FPBotStateAccessor { get; internal set; }

    public static string DialogStateAccessorName { get; } = $"{nameof(FPBotAccessors)}.DialogState";
    public IStatePropertyAccessor<DialogState> DialogStateAccessor { get; internal set; }
    public ConversationState ConversationState { get; }
    //
    public static string ConversationFlowName { get; } = "ConversationFlow";
    public IStatePropertyAccessor<ConversationFlow> ConversationFlowAccessor { get; set; }
}

推荐答案

因此,您的代码存在一些问题,您可以采取一些措施使其变得更好.

So, there are a couple issues with your code and things you can do to make it better.

首先,让我们从关闭构造函数中的局部变量并从代表步骤的闭包中访问局部变量开始.这种工作"现在就可以了,但最终还是有缺陷的.最初的缺陷会有所不同,具体取决于您实例化GetNameAndAgeDialog对话框所采用的方法.

First, let's start with the fact that you're closing over local variables in your constructor and accessing those from the closures that represent your steps. This "works" right now but is ultimately flawed. The initial flaw is different depending on the approach you've taking with instancing your GetNameAndAgeDialog dialog.

如果您将其作为单例使用,则意味着用户与您的机器人之间的所有活动对话都将通过该实例,并且您将遇到并发问题,即两个用户同时与该机器人对话将它们的值存储到相同的内存(这些变量)中,并逐步访问彼此的数据.

If you're using it as a singleton, that means all active conversations between users and your bot would be going through that one instance and you would have a concurrency issue where two users talking to the bot at the same time would be storing their values into the same memory (those variables) and stepping on each other's data.

根据所遵循的示例,也有可能在每个回合中实例化GetNameAndAgeDialog.这意味着在每次对话时,这些变量都将初始化为一个空字符串,并且您将失去对原始值的跟踪.

It's also possible, depending on which samples you're following, that you're instead instantiating your GetNameAndAgeDialog on every turn. This would mean that those variables are then initialized to an empty string on every turn of the conversation and you'd lose track of the original values.

最终,无论使用哪种实例化方法,无论何时进行横向扩展,该方法最终都将存在缺陷,因为充其量,您的状态将与单个服务器实例挂钩,并且如果在ServerA,并且下一轮对话发生在ServerM上,则ServerM将没有上一轮对话的值.

Ultimately, regardless of the instancing used, the approach ends up being flawed no matter what when it comes to scale out because at best your state would be pegged to a single server instance and if one turn of the conversation took place on ServerA and the next turn of the conversation took place on ServerM then ServerM would not have the values from the previous turn.

好的,很明显,您需要使用某种适当的状态管理机制来存储它们.您显然已经很熟悉使用BotState(无论是对话还是用户范围),因为您已经在使用状态属性访问器,但是将您在多转提示中收集的值存储到其中可能为时过早直到您在收集过程结束时为止,在某个地方变得更永久.幸运的是,对话框本身存储在状态中,您可以在为DialogState设置状态属性访问器时弄清楚该状态,因此可以提供一种临时持久性机制,该机制与对话框栈中每个对话框的生命周期相关联.使用此状态尚不明显或没有很好的记录(尚未),但是WaterfallDialog实际上更进一步,并通过其WaterfallStepContext伴随类公开了第一类Values集合,该类随同进入每个步骤.这意味着瀑布流的每个步骤都可以将值添加到Values集合中,并可以访问先前步骤可能已放入其中的值.在标题为

Alright, so clearly you need to store them with some kind of proper state management mechanism. You're clearly somewhat familiar with using BotState (be it conversation or user scope) already being that you're already using the state property accessors, but it's probably premature to store values you're collecting throughout a multi-turn prompt into someplace more permanent until you're at the end of the collection process. Luckily, dialogs themselves are stored into state, which you may have figured out when you set up a state property accessor for DialogState, and therefore offer a temporarily persistence mechanism that is tied to each dialog's lifetime on the dialog stack. Using this state is not obvious or documented well (yet), but WaterfallDialog actually goes a step further and exposes a first class Values collection via its WaterfallStepContext companion class which is fed into each step. This means that each step of your waterfall flow can add values into the Values collection and access values that previous steps may have put into there. There is a pretty good sample of this in the documentation page titled Create advanced conversation flow using branches and loops.

  • 您正在使用TextPrompt作为完美的名称,您将从中获取一个字符串并进行设置.尽管您可能要考虑在其上扔一个验证器,以确保获得的东西看起来像一个名称,而不是仅仅允许任何旧值.
  • 您似乎在使用NumberPrompt<T>作为年龄(至少以名称"numberPrompt"来判断),但是随后您在最后一步中.ToString() step.Result并最终执行了int.Parse.使用NumberPrompt<int>可以保证您得到int,并且可以(应该)直接使用该值,而不必将其返回为字符串,然后稍后再次自行解析.
  • 您已经收到一个名为"confirmPrompt"的提示,但它似乎不是实际的ConfirmPrompt,因为您正在执行所有Choice工作和正值检测(例如,检查是"的变化) ) 你自己.如果您实际使用ConfirmPrompt ,它将为您完成所有这些操作,其结果将是bool,您可以根据自己的逻辑轻松对其进行测试.
  • You're using a TextPrompt for name which is perfect and you'll get a string from it and be all set. Though you might want to consider throwing a validator on there to make sure you get something that looks like a name instead of just allowing any old value.
  • You appear to be using a NumberPrompt<T> for the age (judging by the name "numberPrompt" at least), but then you .ToString() the step.Result and ultimately do an int.Parse in the final step. Using a NumberPrompt<int> would guarantee you get an int and you can/should just use that value as is rather than turning it back into a string and then parsing it yourself again later.
  • You've got a prompt named "confirmPrompt", but it does not appear to be an actual ConfirmPrompt because you're doing all the Choice work and positive value detection (e.g. checking for variations of "Yes") yourself. If you actually use a ConfirmPrompt it will do this all of this for you and its result will be a bool which you can then just easily test in your logic.
  • 当前,您正在使用stepContext.Context.Activity.CreateReply创建活动.很好,但是long绕且不必要.我强烈建议您仅使用MessageFactory API.
  • 我将始终确保将CancellationToken传递给所有使用它的XXXAsync API……这是一个很好的实践.
  • 如果未确认详细信息,则最后一步将重新启动GetNameAndAgeDialog;如果已确认详细信息,则将启动MainDialog.重新启动ReplaceDialogAsync非常棒,这是正确的方法!我只是想指出,通过使用BeginDialogAsync来启动MainDialog意味着在对话的整个生命周期中,您实际上将GetNameAndAgeDialog留在了堆栈的底部.没什么大不了的,但是考虑到您可能永远都不会弹出堆栈,我建议您也使用ReplaceDialogAsync来启动MainDialog.
  • Currently you're using stepContext.Context.Activity.CreateReply to create activities. This is fine, but long winded and unecessary. I would highly recommend just using the MessageFactory APIs.
  • I would always make sure to pass the CancellationToken through to all the XXXAsync APIs that take it... it's just good practice.
  • Your final step either restarts the GetNameAndAgeDialog if they don't confirm the details or starts the MainDialog if they do confirm the details. Restarting with ReplaceDialogAsync is awesome, that's the right way to do it! I just wanted to point out that by using BeginDialogAsync to start the MainDialog means that you're effectively leaving the GetNameAndAgeDialog at the bottom of the stack for the remainder of the conversation's lifetime. It's not a huge deal, but considering you'll likely never pop the stack back to there I would instead suggest using ReplaceDialogAsync for starting up the MainDialog as well.

以下是使用以上所有建议重写的代码:

Here is the code rewritten using all of the advice above:

public GetNameAndAgeDialog(string dialogId, IEnumerable<WaterfallStep> steps = null) : base(dialogId, steps)
{
    AddStep(async (stepContext, cancellationToken) =>
    {
        return await stepContext.PromptAsync("textPrompt",
            new PromptOptions
            {
                Prompt = MessageFactory.Text("What's your name?"),
            },
            cancellationToken: cancellationToken);
    });

    AddStep(async (stepContext, cancellationToken) =>
    {
        var name = (string)stepContext.Result;

        stepContext.Values["name"] = name;

        return await stepContext.PromptAsync("numberPrompt",
            new PromptOptions
            {
                Prompt = MessageFactory.Text($"Hi {name}, How old are you ?"),
            },
            cancellationToken: cancellationToken);
    });

    AddStep(async (stepContext, cancellationToken) =>
    {
        var age = (int)stepContext.Result;

        stepContext.Values["age"] = age;

        return await stepContext.PromptAsync("confirmPrompt",
            new PromptOptions
            {
                Prompt = MessageFactory.Text($"Got it you're {name}, age {age}.{Environment.NewLine}Is this correct?"),
            },
            cancellationToken: cancellationToken);

    });

    AddStep(async (stepContext, cancellationToken) =>
    {
        var result = (bool)stepContext.Result;

        if(result)
        {
            var state = await (stepContext.Context.TurnState["FPBotAccessors"] as FPBotAccessors).FPBotStateAccessor.GetAsync(stepContext.Context);
            state.Name = stepContext.Values["name"] as string;
            state.Age = stepContext.Values["age"] as int;

            return await stepContext.ReplaceDialogAsync(MainDialog.Id, cancellationToken: cancellationToken);
        }
        else
        {
            //restart the dialog
            return await stepContext.ReplaceDialogAsync(GetNameAndAgeDialog.Id, cancellationToken: cancellationToken);
        }
    });
}

如果用户在不重新启动整个对话框的情况下键入"back",是否还可以返回到spefici瀑布步骤?

Also is there a way to go back to a spefici waterfall step if ever the user type "back" without restarting the whole dialog?

不,今天没有办法做到这一点.在与团队进行内部讨论时提出了这个话题,但尚未做出任何决定.如果您认为此功能很有用,请请在GitHub上提交问题,然后我们可以看到它是否有足够的动力来添加该功能.

No, there is not a way to do this today. The topic has come up in internal discussions with the team, but nothing has been decided yet. If you think this is a feature that would be useful, please submit an issue over on GitHub and we can see if it gains enough momentum to get the feature added.

这篇关于Botframework v4:如何简化此瀑布对话框?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆