繁体   English   中英

Botframework v4:如何简化这个瀑布式对话框?

[英]Botframework v4: How to simplify this waterfall dialog?

我有这个代码,但我认为它过于复杂,可以简化。 如果用户键入“返回”而不重新启动整个对话框,还有一种方法可以返回到spefici瀑布步骤吗? 我是新手,很难找到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将没有上ServerM的值。

好吧,很明显你需要存储一些适当的状态管理机制。 您显然已经熟悉使用BotState (无论是会话还是用户范围),因为您已经在使用状态属性访问器,但是将您在多转弯提示中收集的值存储到某个地方可能为时过早。永久,直到你收集过程结束。 幸运的是,对话框本身存储在状态中,当您为DialogState设置状态属性访问器时,您可能已经想到了这种状态,因此提供了一个临时持久性机制,该机制与对话框堆栈上每个对话框的生命周期相关联。 使用这种状态并不是很明显或者记录得很好(但是),但是WaterfallDialog实际上更进了一步,并通过其WaterfallStepContext伴侣类公开了第一类Values集合,该伴随类被输入到每个步骤中。 这意味着瀑布流的每个步骤都可以将值添加到Values集合中,并访问先前步骤可能放入其中的值。 在标题为使用分支和循环创建高级会话流的文档页面中有一个非常好的示例。

没有充分利用提示

  • 您正在使用TextPrompt作为完美的名称,您将从中获取一个字符串并将其全部设置。 虽然你可能想考虑在那里抛出一个验证器,以确保你得到的东西看起来像一个名字,而不是只允许任何旧值。
  • 您似乎使用NumberPrompt<T>作为年龄(至少通过名称"numberPrompt"判断),但是然后您.ToString() step.Result并最终在最后一步中执行int.Parse 使用NumberPrompt<int>可以保证你得到一个int ,你可以/应该只是按原样使用该值,而不是将其转回一个字符串,然后再自己解析它。
  • 你有一个名为"confirmPrompt"的提示,但它似乎不是一个真正的ConfirmPrompt因为你自己正在做所有的Choice工作和正值检测(例如检查“是”的变化)。 如果您实际使用的是ConfirmPrompt ,它将为您完成所有这一切,其结果将是一个bool ,您可以在逻辑中轻松测试。

小东西

  • 目前,您正在使用stepContext.Context.Activity.CreateReply来创建活动。 这很好,但是长篇大论且不必要。 我强烈建议您只使用MessageFactory API。
  • 我总是确保将CancellationToken传递给所有采用它的XXXAsync API ...这只是一个好习惯。
  • 您的最后一步是重新启动GetNameAndAgeDialog如果他们没有确认详细信息,或者如果他们确认详细信息,则启动MainDialog 使用ReplaceDialogAsync重新启动非常棒,这是正确的方法! 我只想指出,通过使用BeginDialogAsync来启动MainDialog意味着您有效地将GetNameAndAgeDialog留在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);
        }
    });
}

如果用户键入“返回”而不重新启动整个对话框,还有一种方法可以返回到spefici瀑布步骤吗?

不,今天没有办法做到这一点。 这个话题已经与团队进行了内部讨论,但还没有任何决定。 如果你认为这是一个有用的功能, 请在GitHub上提交一个问题 ,我们可以看看它是否有足够的动力来增加功能。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM