其他分享
首页 > 其他分享> > CodeGo.net> Botframework v4:如何简化此瀑布对话框?

CodeGo.net> Botframework v4:如何简化此瀑布对话框?

作者:互联网

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

  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; }
}

解决方法:

因此,您的代码有很多问题,您可以做一些改善的事情.

对话框中的状态

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

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

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

最终,无论使用哪种实例化,该方法最终都会有缺陷,因为无论何时进行横向扩展,因为最多只能将您的状态固定到单个服务器实例,并且如果在ServerA上进行了一次对话而下一次对话对话发生在ServerM上,则ServerM将没有上一轮的值.

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

没有充分利用提示

>您正在使用TextPrompt作为完美的名称,您将从中获取一个字符串并进行设置.尽管您可能要考虑在其上扔一个验证器,以确保获得的东西看起来像名称,而不是仅仅允许任何旧值.
>您似乎正在使用NumberPrompt< T>的年龄(至少由名称“ numberPrompt”来判断),但随后您要.ToString()作为step.Result并最终在最后一步中进行int.Parse.使用NumberPrompt< int>可以保证您获得一个int值,并且可以(应该)直接使用该值,而不是将其返回为字符串,然后稍后自己再次对其进行解析.
>您有一个名为“ confirmPrompt”的提示,但它似乎不是实际的ConfirmPrompt,因为您自己进行了所有Choice工作和正值检测(例如,检查“ Yes”的变化).如果您实际使用ConfirmPrompt,它将为您完成所有这些操作,其结果将是布尔值,您可以根据自己的逻辑轻松进行测试.

小东西

>当前,您正在使用stepContext.Context.Activity.CreateReply创建活动.很好,但是long绕且不必要.我强烈建议仅使用MessageFactory API.
>我将始终确保将CancellationToken传递给所有使用它的XXXAsync API,这是一个好习惯.
>最后一步,如果他们不确认详细信息,则重新启动GetNameAndAgeDialog,或者如果确实确认详细信息,则启动MainDialog.重新启动ReplaceDialogAsync非常棒,这是正确的方法!我只是想指出,通过使用BeginDialogAsync启动MainDialog意味着在对话的整个生命周期中,您实际上都将GetNameAndAgeDialog留在了堆栈的底部.这没什么大不了的,但是考虑到您可能永远也不会弹出堆栈,我建议您也使用ReplaceDialogAsync来启动MainDialog.

重构代码

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

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);
        }
    });
}

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

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

标签:botframework,c
来源: https://codeday.me/bug/20191108/2007149.html