问题
I'm using MS BotFramework v4. There is a RootDialog
which starts Dialog_A
or Dialog_B
depending on what the user input is.
TL;DR
If a new conversation is started after a conversation and the bot isn't restarted, private variables of dialogs to which a value (which is not the initial value) has already be assigned, are not reseted to their initial value leading to unexpected behavior. How can this be avoided?
Detailed
Let's assume the following scenario: Each of these dialogs has some private variables to control whether to output a long or a short introduction message. The long one should only be be outputted on the first time this dialog is started. If the conversation again reaches the dialog only the short message should be printed.
Implementation looks like this:
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
and Dialog_B
are structured the same way.
The problem
If the bot handles its first conversation, everything works as expected (the long welcome text is printed to the user and isLongWelcomeText
is set to false
in WelcomeStep
. When I then start a new conversation (new conversationId and userId) isLongWelcomeText
still is set to false
which leads to the bot outputting the short welcome text in a new conversation to a new user.
In BotFramework v3 dialogs were serialized and deserialized together with all variable values.
If I'm right in BF v4 dialogs aren't serialized anymore.
The question
How can this be fixed? Is there a better way to do this?
Remarks
I'm using UserState
and ConversationState
which are serialized and reset on new conversations. But I don't want to store every private variable value of each dialog in the states. This cannot be the way to go.
Thanks in advace
回答1:
Generally you should think of it as a mistake to put instance member variables in a dialog class. There may be some cases when it could work, but those cases will not involve trying to persist some kind of state between turns. There are three main problems with using any kind of in-memory variables of your bot classes to persist state between turns:
- It won't be scoped correctly. This is the problem you noticed already. You've clearly defined your
isLongWelcomeText
as something that should be specific to a user and/or a conversation, but because it's in your bot's own memory that's used to process every conversation for every user then it won't be able to distinguish between different conversations and users. - It won't scale correctly. This means that even if your bot is just talking to one user in one conversation, if the bot is deployed in some hosting service like Azure that scales sideways then multiple instances of your bot may be running. Different instances of your bot will have different memory, so if you want to design a bot correctly then you need to act as though every turn will be processed by a totally different instance of the bot, maybe on a totally different server. One instance can't access the memory of another instance.
- It will be lost when the app restarts. Even if you only have one user, one conversation, and one bot instance, you still want to be able to stop your bot and then start it again without ruining the conversation. If you're using the bot's memory then you can't do that.
The second two problems even apply when you're using MemoryStorage
and not just when you're using in-memory variables. You may have guessed that the solution is to use bot state (and to connect the bot state to some storage class other than MemoryStorage
when the bot is deployed).
You are correct that in v3 entire instance objects of dialog classes would get serialized into persistent state. That came with its own set of problems and didn't always make logical sense, so in v4 the things that gets serialized are 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 keeps track of things like its custom values and what step it's on:
// Update persisted step index var state = dc.ActiveDialog.State; state[StepIndex] = index;
来源:https://stackoverflow.com/questions/64801536/dialogs-keep-their-variable-values-in-new-conversation-in-ms-botframework-v4