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

2024-01-07

我有这段代码,但我认为它过于复杂并且可以简化。 如果用户键入“后退”而不重新启动整个对话框,是否有办法返回到特定的瀑布步骤?我对此很陌生,很难找到有关 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 dialog.

如果您将其用作单例,这意味着用户和您的机器人之间的所有活动对话都将通过该实例,并且您将遇到并发问题,即两个用户同时与机器人交谈将存储他们的值进入相同的内存(那些变量)并踩在彼此的数据上。

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

最终,无论使用何种实例,无论在横向扩展时如何,该方法最终都会存在缺陷,因为最好的情况是您的状态将与单个服务器实例挂钩,并且如果一轮对话发生在ServerA下一次谈话发生在ServerM then ServerM不会有上一轮的值。

好吧,很明显你需要用某种适当的状态管理机制来存储它们。您显然对使用有些熟悉BotState(无论是对话还是用户范围)已经是您已经在使用状态属性访问器,但是将您在多轮提示中收集的值存储到更永久的地方,直到您结束时可能还为时过早收集过程。幸运的是,对话框本身存储在状态中,当您设置状态属性访问器时,您可能已经发现了这一点DialogState,因此提供了一种与对话框堆栈上每个对话框的生命周期相关的临时持久性机制。使用这种状态并不明显,也没有很好的记录,但是WaterfallDialog实际上更进一步并暴露了一流的Values通过其收集WaterfallStepContext伴随类被输入到每个步骤中。这意味着瀑布流的每一步都可以为Values先前步骤可能已放入其中的集合和访问值。在标题为的文档页面中有一个很好的示例.

没有充分利用提示

  • 您正在使用一个TextPrompt对于完美的名称,您将从中得到一个字符串并准备就绪。尽管您可能需要考虑在其中添加一个验证器,以确保您获得看起来像名称的东西,而不是只允许任何旧值。
  • 您似乎正在使用NumberPrompt<T>对于年龄(从名字来看"numberPrompt"至少),但是然后你.ToString() the step.Result并最终做一个int.Parse在最后一步。用一个NumberPrompt<int>会保证你得到int您可以/应该按原样使用该值,而不是将其转回字符串,然后稍后再次自行解析。
  • 您有一个名为"confirmPrompt",但它似乎不是实际的ConfirmPrompt因为你正在做所有的事情Choice自己进行工作和积极价值检测(例如检查“是”的变化)。如果你实际上使用ConfirmPrompt 它会为你做这一切它的结果将是bool然后您可以轻松地用您的逻辑进行测试。

小事

  • 目前您正在使用stepContext.Context.Activity.CreateReply创建活动。这很好,但是冗长且不必要。我强烈建议只使用MessageFactory APIs.
  • 我总是确保通过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);
        }
    });
}

如果用户键入“后退”而不重新启动整个对话框,是否有办法返回到特定的瀑布步骤?

不,今天没有办法做到这一点。该主题已在团队内部讨论中提出,但尚未做出任何决定。如果您认为这是一个有用的功能,请在 GitHub 上提交问题 https://github.com/Microsoft/BotBuilder/issues我们可以看看它是否有足够的动力来添加该功能。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Botframework v4:如何简化这个瀑布对话框? 的相关文章

随机推荐