【问题标题】:Dialogs keep their variable values in new conversation in MS BotFramework v4对话框在 MS BotFramework v4 的新对话中保留其变量值
【发布时间】:2021-02-24 07:37:21
【问题描述】:

我正在使用 MS BotFramework v4。有一个 RootDialogDialog_ADialog_B 开头,具体取决于用户输入的内容。

TL;DR

如果在对话后开始新对话并且机器人未重新启动,则已分配值(不是初始值)的对话的私有变量不会重置为其初始值,从而导致意外的行为。如何避免这种情况?

详细

让我们假设以下场景: 这些对话框中的每一个都有一些私有变量来控制是输出长的还是短的介绍消息。仅应在第一次启动此对话框时输出长的。如果对话再次到达对话框,则只应打印短消息。

实现如下:

RootDialog.cs

public class RootDialog : ComponentDialog
{
    private bool isLongWelcomeText = true;
    // Some more private variables follow here

    public RootDialog() : base("rootId") {
        AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] {
            WelcomeStep,
            DoSomethingStep,
            FinalStep
        });
    }

    private async Task<DialogTurnContext> WelcomeStep(WaterfallStepContext ctx, CancellationToken token) {
        if(isLongWelcomeText) {
            await ctx.SendActivityAsync(MessageFactory.Text("A welcome message and some detailed bla bla about the bot"));
            isLongWelcomeText = false;
        } else {
            await ctx.SendActivityAsync(MessageFactory.Text("A short message that hte bot is waiting for input"));
        }
    }

    private async Task<DialogTurnContext> DoSomethingStep(WaterfallStepContext ctx, CancellationToken token) {
        // call Dialog_A or Dialog_B depending on the users input
        // Dialog X starts
        await ctx.BeginDialogAsync("Dialog_X", null, token);
    }

    private async Task<DialogTurnContext> FinalStep(WaterfallStepContext ctx, CancellationToken token) {
        // After dialog X has ended, RootDialog continues here and simply ends
        await ctx.EndDialogAsync(null, token);
    }
}

Dialog_ADialog_B 的结构相同。

问题

如果机器人处理了它的第一次对话,一切都会按预期进行(长欢迎文本会打印给用户,并且WelcomeStep 中的isLongWelcomeText 设置为false。然后当我开始新的对话时(新的conversationId和 userId)isLongWelcomeText 仍设置为 false,这会导致机器人在新对话中向新用户输出简短的欢迎文本。

在 BotFramework v3 中,对话框与所有变量值一起被序列化和反序列化。

如果我在 BF v4 中是对的,则不再序列化。

问题

如何解决这个问题?有没有更好的方法来做到这一点?

备注

我正在使用UserStateConversationState,它们在新对话中被序列化并重置。但我不想将每个对话框的每个私有变量值都存储在状态中。这不可能。

提前感谢

【问题讨论】:

  • 我的回答可以接受吗?

标签: c# azure-storage chatbot botframework


【解决方案1】:

通常你应该认为将实例成员变量放在对话框类中是错误的。在某些情况下它可能会起作用,但这些情况不会涉及尝试在轮次之间保持某种状态。使用 bot 类的任何类型的内存变量在轮次之间保持状态存在三个主要问题:

  1. 它的范围不正确。这是您已经注意到的问题。您已明确将您的 isLongWelcomeText 定义为特定于用户和/或对话的内容,但因为它位于您的机器人自己的内存中,用于处理每个用户的每个对话,所以它无法区分在不同的对话和用户之间。
  2. 它无法正确扩展。这意味着即使您的机器人只是在一次对话中与一位用户交谈,如果该机器人部署在像 Azure 这样横向扩展的托管服务中,那么您的多个实例机器人可能正在运行。你的机器人的不同实例会有不同的内存,所以如果你想正确地设计一个机器人,那么你需要表现得好像每一轮都由一个完全不同的机器人实例处理,也许在一个完全不同的服务器上。一个实例无法访问另一个实例的内存。
  3. 应用重新启动时它会丢失。即使您只有一个用户、一个对话、一个机器人实例,您仍然希望能够停止您的机器人并然后在不破坏对话的情况下重新开始。如果您使用的是机器人的内存,则不能这样做。

后两个问题甚至在您使用MemoryStorage 时也适用,而不仅仅是在您使用内存变量时。您可能已经猜到解决方案是使用 bot 状态(并在部署 bot 时将 bot 状态连接到 MemoryStorage 以外的某个存储类)。

您是正确的,在 v3 中,对话框类的整个实例对象将被序列化为持久状态。这带来了一系列问题并且并不总是合乎逻辑的,所以在 v4 中,被序列化的东西是 DialogInstance 对象。 (阅读对话框实例here)。您希望对话框跟踪的任何内容都应放入相关对话框实例的状态对象中,查看如何执行此操作的示例的最佳位置是 SDK 源代码本身。例如,您可以看到 waterfall dialog 如何跟踪其自定义值以及它所处的步骤:

// Update persisted step index
var state = dc.ActiveDialog.State;
state[StepIndex] = index;

【讨论】:

  • 这意味着我只需将那些实例成员值保存在 ActiveDialog.State 中?在该州保存值的最佳位置是什么?我怀疑必须在步骤结束之前在每个 WaterfallStep 中完成保存?并且可能从 ComponentDialogs ResumeDialogAsync 中的状态加载?
  • 瀑布对话框在其瀑布步骤上下文中提供了一个“值”属性,因此您不必担心显式访问对话框状态。我现在意识到我应该提到对话实例的状态可能不适用于您的情况,因为您希望 isLongWelcomeText 的范围仅限于对话而不是对话。这意味着像在 v3 中那样序列化对话框也不会起作用,因为每次将对话框类的新实例添加到堆栈中时都会构造它。但是,如果它是一个持久的根对话框,你可能会很好。
  • 嗯,好吧,现在我很困惑。当实例成员仅限于单个对话时,使用实例成员是错误的。使用对话框实例状态不起作用。然后我应该在哪里存储这些值(不管这可能是什么值)?在对话状态?对于每个单独的 Dialog??你不能是认真的!
  • 如果我理解正确,isLongWelcomeText 会跟踪是否已经向用户发送了欢迎消息。为此,您不需要任何类型的状态,因为应发送欢迎消息以响应一次性事件,即对话更新。但是,如果您确实想跟踪它,那么似乎该信息将被限定为对话或用户而不是对话,这意味着您应该使用与对话状态分开的对话状态或用户状态,而不是在一个对话框实例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-11
  • 1970-01-01
  • 2023-03-13
  • 1970-01-01
相关资源
最近更新 更多