Search code examples
node.jsbotframework

Bot is mixing up prompts from concurrent users


I'm having an issue where multiple users concurrently accessing the same dialog are having their prompt values mixed up. The dialog in question is an Order Status dialog where, among other things, the order number is prompted. I am seeing cases where User 1 queries Order A, User 2 queries Order B (at nearly the same time) and then both users receive information for Order B.

This is a complex dialog and sharing the complete code wouldn't be helpful, but here are a few points I feel may be relevant:

  • I am using this.queryData = {} in the constructor to initiate an object to hold all of the order query parameters I am prompting for, including order number.
    • I thought perhaps this object was getting overridden by the second user, but I'm also setting the user and conversation state here, as I do in every dialog, so I'm not sure that's it. I have always assumed each instance of this dialog is created uniquely for each user.
  • I am using a simple text prompt:
return await step.prompt(TEXT_PROMPT, {
    prompt: `Please provide your ${step.values.orderTypeText} number.`,
    retryPrompt: `Please enter a valid ${step.values.orderTypeText} number.`,
});
  • And then in the next step assigning to the queryData object as this.queryData.orderNumber = step.result.trim().split(" ").splice(-1)[0];

And that's really it, unless there is some way that it's not the prompt value but the entire message that is getting send to the wrong user (but that seems quite unlikely, plus it is going to both users in this example).

I have considered instead of using this.queryData to store the information in conversationState, but I don't want to redo the code unless I can confirm this is an issue with the implementation I have used, specifically this.queryData rather than some other issue.

For reference, here is the class definition, down to the end of the constructor:

class viewOrderDialog extends ComponentDialog {
    constructor(dialogId, userDialogStateAccessor, userState, appInsightsClient, dialogState, conversationState) {
        super(dialogId);

        this.addDialog(new ChoicePrompt(CRITERIA_PROMPT));
        this.addDialog(new TextPrompt(TEXT_PROMPT));
        this.addDialog(new TextPrompt(LINE_PROMPT, this.validateLineNumber));
        this.addDialog(new TextPrompt(EMAIL_PROMPT,this.validateEmail));
        this.addDialog(new TextPrompt(ZIP_PROMPT,this.validateZip));
        this.addDialog(new ConfirmPrompt(CONFIRM_PROMPT));
        this.addDialog(new ChoicePrompt(CHOICE_PROMPT));
        this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
            this.requestOrderNumber.bind(this),
            this.selectOrderType.bind(this), // This is being bypassed by current Filtration store implementation
            this.selectSearchCriteria.bind(this),
            this.getQueryData.bind(this),
            this.confirmBillingZip.bind(this),
            this.confirmBillingEmail.bind(this),
            this.displayLineStatus.bind(this),
            this.getEmailAddress.bind(this),
            this.setFollowUp.bind(this),
            this.loopStep.bind(this)
        ]));

        this.initialDialogId = WATERFALL_DIALOG;

        this.queryData = {};

        // State accessors
        this.userDialogStateAccessor = userDialogStateAccessor;
        this.userState = userState;
        this.dialogState = dialogState;
        this.conversationState = conversationState;

        this.appInsightsClient = appInsightsClient;

        this.addDialog(new PaginateDialog(PAGINATE_DIALOG, this.userDialogStateAccessor, userState, this.appInsightsClient));

        // Luis Recognizer
        this.luisRecognizer = new LuisRecognizer({
            applicationId: process.env.LuisAppId,
            endpointKey: process.env.LuisAPIKey,
            endpoint: `https://${ process.env.LuisAPIHostName }`
        }, {
            includeAllIntents: true,
            includeInstanceData: true,
            spellCheck: true,
            bingSpellCheckSubscriptionKey: process.env.bingAPIKey
        }, true);

    } // End constructor

Solution

  • @Arie Rodrigues got me going in the right direction but the solution was not quite correct. Bot Framework does in fact seem to be using a single instance of the dialog, with it being controlled by the conversation/dialog state. So the this.queryData object defined in the constructor was actually being shared by all dialogs. With the right timing, prompt inputs were crossing from one user's dialog to the other.

    This could be solved with any method to save these values non-globally. Conversation state would be a good one, but I didn't need or want to save the values for the duration of the conversation nor mess with retrieving and saving the state every step. Instead, I used step.values in place of this.queryData. My code had actually relied on some values of this.queryData being persisted when the dialog was looped at a certain step via replaceDialog(), so I had to make sure to pass those in the options of that method. This seems to have fixed the issue, as I had 4 testers hitting the same dialogs at the same time with different prompt values without issue.