Bot 混淆了并发用户的提示
Bot is mixing up prompts from concurrent users
我遇到了一个问题,多个用户同时访问同一个对话框时,他们的提示值混淆了。有问题的对话框是订单状态对话框,其中提示订单号等。我看到用户 1 查询订单 A,用户 2 查询订单 B(几乎同时)的情况,然后两个用户都收到订单 B 的信息。
这是一个复杂的对话框,分享完整的代码不会有帮助,但我认为以下几点可能相关:
- 我在构造函数中使用
this.queryData = {}
来启动一个对象来保存我提示输入的所有订单查询参数,包括订单号。
- 我想也许这个对象被第二个用户覆盖了,但我也在这里设置用户和对话状态,就像我在每个对话框中所做的那样,所以我不确定是不是这样。我一直假设此对话框的每个实例都是为每个用户创建的。
- 我使用的是简单的文本提示:
return await step.prompt(TEXT_PROMPT, {
prompt: `Please provide your ${step.values.orderTypeText} number.`,
retryPrompt: `Please enter a valid ${step.values.orderTypeText} number.`,
});
- 然后在下一步中将 queryData 对象分配为
this.queryData.orderNumber = step.result.trim().split(" ").splice(-1)[0];
就是这样,除非有某种方式不是提示值而是整个消息被发送给错误的用户(但这似乎不太可能,而且在这个例子中它会同时发送给两个用户).
我考虑过不使用 this.queryData
将信息存储在 conversationState
中,但我不想重做代码,除非我可以确认这是我的实现的问题使用,特别是 this.queryData
而不是其他问题。
作为参考,这里是 class 定义,一直到构造函数的末尾:
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
我习惯在.net上使用Botframework,但我认为整体机制与节点不会有太大差异。
在 BotBuilder 的 .net 实现中,正如您所说,所有对话框实例都存储在应用程序启动时的单个实例中,以避免加载对话框实例 运行 几乎相同的数据。
为避免对话框实例之间的信息混合,您有一些选择:
- 使用状态管理来存储上下文数据,例如 conversationData,用于对话范围的域信息并在所有对话之间共享数据;
- 或者您可以使用
stepContext.options
存储对话域信息,当对话保留在堆栈中时,它可以在任何步骤中恢复(在您到达最后一步之前,调用 endDialog
或 replaceDialog
).
这里有一些关于如何存储数据的信息:
https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-storage?view=azure-bot-service-4.0&tabs=javascript
@Arie Rodrigues 让我朝着正确的方向前进,但解决方案并不完全正确。 Bot Framework 实际上似乎确实在使用对话框的单个实例,它由 conversation/dialog 状态控制。所以构造函数中定义的 this.queryData
对象实际上被所有对话框共享。在正确的时机,提示输入从一个用户的对话框传递到另一个用户的对话框。
这可以通过保存这些值的任何方法来解决non-globally。对话状态会是一个很好的状态,但我不需要或不想在对话期间保存值,也不需要在每一步检索和保存状态时搞得一团糟。相反,我使用 step.values
代替 this.queryData
。当对话框通过 replaceDialog()
在某个步骤循环时,我的代码实际上依赖于 this.queryData
的某些值,因此我必须确保在该方法的选项中传递这些值。这似乎解决了这个问题,因为我有 4 名测试人员同时使用不同的提示值点击相同的对话框而没有问题。
我遇到了一个问题,多个用户同时访问同一个对话框时,他们的提示值混淆了。有问题的对话框是订单状态对话框,其中提示订单号等。我看到用户 1 查询订单 A,用户 2 查询订单 B(几乎同时)的情况,然后两个用户都收到订单 B 的信息。
这是一个复杂的对话框,分享完整的代码不会有帮助,但我认为以下几点可能相关:
- 我在构造函数中使用
this.queryData = {}
来启动一个对象来保存我提示输入的所有订单查询参数,包括订单号。- 我想也许这个对象被第二个用户覆盖了,但我也在这里设置用户和对话状态,就像我在每个对话框中所做的那样,所以我不确定是不是这样。我一直假设此对话框的每个实例都是为每个用户创建的。
- 我使用的是简单的文本提示:
return await step.prompt(TEXT_PROMPT, {
prompt: `Please provide your ${step.values.orderTypeText} number.`,
retryPrompt: `Please enter a valid ${step.values.orderTypeText} number.`,
});
- 然后在下一步中将 queryData 对象分配为
this.queryData.orderNumber = step.result.trim().split(" ").splice(-1)[0];
就是这样,除非有某种方式不是提示值而是整个消息被发送给错误的用户(但这似乎不太可能,而且在这个例子中它会同时发送给两个用户).
我考虑过不使用 this.queryData
将信息存储在 conversationState
中,但我不想重做代码,除非我可以确认这是我的实现的问题使用,特别是 this.queryData
而不是其他问题。
作为参考,这里是 class 定义,一直到构造函数的末尾:
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
我习惯在.net上使用Botframework,但我认为整体机制与节点不会有太大差异。
在 BotBuilder 的 .net 实现中,正如您所说,所有对话框实例都存储在应用程序启动时的单个实例中,以避免加载对话框实例 运行 几乎相同的数据。
为避免对话框实例之间的信息混合,您有一些选择:
- 使用状态管理来存储上下文数据,例如 conversationData,用于对话范围的域信息并在所有对话之间共享数据;
- 或者您可以使用
stepContext.options
存储对话域信息,当对话保留在堆栈中时,它可以在任何步骤中恢复(在您到达最后一步之前,调用endDialog
或replaceDialog
).
这里有一些关于如何存储数据的信息:
https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-storage?view=azure-bot-service-4.0&tabs=javascript
@Arie Rodrigues 让我朝着正确的方向前进,但解决方案并不完全正确。 Bot Framework 实际上似乎确实在使用对话框的单个实例,它由 conversation/dialog 状态控制。所以构造函数中定义的 this.queryData
对象实际上被所有对话框共享。在正确的时机,提示输入从一个用户的对话框传递到另一个用户的对话框。
这可以通过保存这些值的任何方法来解决non-globally。对话状态会是一个很好的状态,但我不需要或不想在对话期间保存值,也不需要在每一步检索和保存状态时搞得一团糟。相反,我使用 step.values
代替 this.queryData
。当对话框通过 replaceDialog()
在某个步骤循环时,我的代码实际上依赖于 this.queryData
的某些值,因此我必须确保在该方法的选项中传递这些值。这似乎解决了这个问题,因为我有 4 名测试人员同时使用不同的提示值点击相同的对话框而没有问题。