对话框在 MS BotFramework v4 中的新对话中保留其变量值
Dialogs keep their variable values in new conversation in MS BotFramework v4
我正在使用 MS BotFramework v4。有一个 RootDialog
开始 Dialog_A
或 Dialog_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_A
和 Dialog_B
的结构相同。
问题
如果机器人处理了它的第一个对话,一切都会按预期进行(向用户打印长的欢迎文本,并且 isLongWelcomeText
在 WelcomeStep
中设置为 false
。当我然后开始新的对话(新的 conversationId 和 userId)isLongWelcomeText
仍然设置为 false
这导致机器人在新对话中向新用户输出简短的欢迎文本。
在 BotFramework v3 中,对话框与所有变量值一起被序列化和反序列化。
如果我在 BF v4 中是正确的,对话框将不再序列化。
问题
如何解决这个问题?有更好的方法吗?
备注
我正在使用 UserState
和 ConversationState
,它们在新的对话中被序列化和重置。但我不想在各州存储每个对话框的每个私有变量值。这不是要走的路。
提前致谢
通常您应该认为将实例成员变量放在对话框中是错误的class。在某些情况下它可能会起作用,但这些情况不会涉及尝试在回合之间保持某种状态。使用机器人 classes 的任何类型的内存变量在回合之间保持状态存在三个主要问题:
- 它的范围不正确。这是您已经注意到的问题。您已经明确地将
isLongWelcomeText
定义为应该特定于用户 and/or 对话的东西,但是因为它在您的机器人自己的内存中,用于处理每个用户的每个对话,所以它不会能够区分不同的对话和用户。
- 它不会正确缩放。 这意味着即使您的机器人只是在一次对话中与一个用户交谈,如果该机器人部署在某些托管服务(如 Azure)中横向扩展那么你的机器人的多个实例可能是 运行。你的机器人的不同实例会有不同的内存,所以如果你想正确地设计一个机器人,那么你需要表现得好像每一轮都会由一个完全不同的机器人实例处理,也许在一个完全不同的服务器上。一个实例无法访问另一个实例的内存。
- 应用重启后它会丢失。即使你只有一个用户,一个对话,和一个机器人实例,您仍然希望能够停止您的机器人,然后在不破坏对话的情况下重新启动它。如果你正在使用机器人的内存,那么你不能这样做。
后两个问题甚至在您使用 MemoryStorage
时也会出现,而不仅仅是在您使用内存变量时。您可能已经猜到解决方案是使用 bot 状态(并在部署 bot 时将 bot 状态连接到某些存储 class 而不是 MemoryStorage
)。
您是正确的,在 v3 中,对话框 classes 的整个实例对象将被序列化为持久状态。这带来了一系列问题并且并不总是合乎逻辑,因此在 v4 中,序列化的内容是 DialogInstance
objects. (Read about dialog instances here). Anything you want your dialog to keep track of, you should put in the associated dialog instance's state object, and the best place to see examples of how to do that is in the SDK source code itself. For example, you can see how a waterfall dialog 跟踪诸如其自定义值之类的内容以及它正在进行的步骤:
// Update persisted step index
var state = dc.ActiveDialog.State;
state[StepIndex] = index;
我正在使用 MS BotFramework v4。有一个 RootDialog
开始 Dialog_A
或 Dialog_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_A
和 Dialog_B
的结构相同。
问题
如果机器人处理了它的第一个对话,一切都会按预期进行(向用户打印长的欢迎文本,并且 isLongWelcomeText
在 WelcomeStep
中设置为 false
。当我然后开始新的对话(新的 conversationId 和 userId)isLongWelcomeText
仍然设置为 false
这导致机器人在新对话中向新用户输出简短的欢迎文本。
在 BotFramework v3 中,对话框与所有变量值一起被序列化和反序列化。
如果我在 BF v4 中是正确的,对话框将不再序列化。
问题
如何解决这个问题?有更好的方法吗?
备注
我正在使用 UserState
和 ConversationState
,它们在新的对话中被序列化和重置。但我不想在各州存储每个对话框的每个私有变量值。这不是要走的路。
提前致谢
通常您应该认为将实例成员变量放在对话框中是错误的class。在某些情况下它可能会起作用,但这些情况不会涉及尝试在回合之间保持某种状态。使用机器人 classes 的任何类型的内存变量在回合之间保持状态存在三个主要问题:
- 它的范围不正确。这是您已经注意到的问题。您已经明确地将
isLongWelcomeText
定义为应该特定于用户 and/or 对话的东西,但是因为它在您的机器人自己的内存中,用于处理每个用户的每个对话,所以它不会能够区分不同的对话和用户。 - 它不会正确缩放。 这意味着即使您的机器人只是在一次对话中与一个用户交谈,如果该机器人部署在某些托管服务(如 Azure)中横向扩展那么你的机器人的多个实例可能是 运行。你的机器人的不同实例会有不同的内存,所以如果你想正确地设计一个机器人,那么你需要表现得好像每一轮都会由一个完全不同的机器人实例处理,也许在一个完全不同的服务器上。一个实例无法访问另一个实例的内存。
- 应用重启后它会丢失。即使你只有一个用户,一个对话,和一个机器人实例,您仍然希望能够停止您的机器人,然后在不破坏对话的情况下重新启动它。如果你正在使用机器人的内存,那么你不能这样做。
后两个问题甚至在您使用 MemoryStorage
时也会出现,而不仅仅是在您使用内存变量时。您可能已经猜到解决方案是使用 bot 状态(并在部署 bot 时将 bot 状态连接到某些存储 class 而不是 MemoryStorage
)。
您是正确的,在 v3 中,对话框 classes 的整个实例对象将被序列化为持久状态。这带来了一系列问题并且并不总是合乎逻辑,因此在 v4 中,序列化的内容是 DialogInstance
objects. (Read about dialog instances here). Anything you want your dialog to keep track of, you should put in the associated dialog instance's state object, and the best place to see examples of how to do that is in the SDK source code itself. For example, you can see how a waterfall dialog 跟踪诸如其自定义值之类的内容以及它正在进行的步骤:
// Update persisted step index var state = dc.ActiveDialog.State; state[StepIndex] = index;