Dialogs keep their variable values in new conversation in MS BotFramework v4

帅比萌擦擦* 提交于 2021-01-28 04:22:29

问题


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:

  1. 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.
  2. 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.
  3. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!