当用户键入 "exit"、"quit" 等时终止所有对话框并退出 MS Bot Framework 中的对话
Terminate all dialogs and exit conversation in MS Bot Framework when the user types "exit", "quit" etc
我不知道如何在 MS Bot Framework 中做一件非常简单的事情:允许用户中断任何对话,离开当前对话框并通过键入 return 到主菜单"quit"、"exit" 或 "start over"。
我的主要对话是这样设置的:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
try
{
if (activity.Type == ActivityTypes.Message)
{
UserActivityLogger.LogUserBehaviour(activity);
if (activity.Text.ToLower() == "start over")
{
//Do something here, but I don't have the IDialogContext here!
}
BotUtils.SendTyping(activity); //send "typing" indicator upon each message received
await Conversation.SendAsync(activity, () => new RootDialog());
}
else
{
HandleSystemMessage(activity);
}
}
我知道如何用 context.Done<DialogType>(this);
终止对话,但是在这个方法中,我没有访问 IDialogContext 对象的权限,所以我不能调用 .Done()
。
除了在所有对话框的每个步骤中添加检查之外,还有其他方法可以在用户键入特定消息时终止整个对话框堆栈吗?
Posted赏金:
我需要一种方法来终止所有 IDialog
,而无需使用我在此处发布的令人发指的黑客攻击(它会删除我需要的所有用户数据,例如用户设置和首选项)。
基本上,当用户键入 "quit" 或 "exit" 时,我需要退出当前正在进行的 IDialog
并 return 到新状态,就好像用户刚刚发起对话。
我需要能够从 MessageController.cs,
执行此操作,但我仍然无法访问 IDialogContext
。我似乎拥有的唯一有用数据是 Activity
对象。如果有人指出其他方法可以做到这一点,我会很高兴。
解决这个问题的另一种方法是找到一些其他方法来检查机器人的其他地方的 "exit" 和 "quit" 关键字,而不是在 Post 方法中。
但不应该在 IDialog
的每一步都进行检查,因为那是太多的代码而且甚至不可能总是可行(当使用 PromptDialog
时,我没有访问用户键入的文本)。
两种我没有探索的可能方式:
- 不是终止所有当前的
IDialog
s,而是开始新的对话
与用户(新 ConversationId
)
- 获取
IDialogStack
对象并使用它来管理对话框堆栈。
Microsoft 文档没有提及此对象,因此我不知道如何获取它。我没有在机器人的任何地方使用允许 .Switch()
的 Chain
对象,但如果您认为可以重写它以使用它,它也可以是解决此问题的方法之一。但是,我还没有找到如何在各种类型的对话框(FormFlow
和普通的 IDialog
)之间进行分支,这些对话框又调用它们自己的子对话框等
这是一个非常丑陋的骇人听闻的黑客。它基本上会删除所有用户数据(您可能确实需要),这会导致对话重新开始。
如果有人知道更好的方法,又不删除用户数据,请分享。
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
try
{
if (activity.Type == ActivityTypes.Message)
{
//if the user types certain messages, quit all dialogs and start over
string msg = activity.Text.ToLower().Trim();
if (msg == "start over" || msg == "exit" || msg == "quit" || msg == "done" || msg =="start again" || msg == "restart" || msg == "leave" || msg == "reset")
{
//This is where the conversation gets reset!
activity.GetStateClient().BotState.DeleteStateForUser(activity.ChannelId, activity.From.Id);
}
//and even if we reset everything, show the welcome message again
BotUtils.SendTyping(activity); //send "typing" indicator upon each message received
await Conversation.SendAsync(activity, () => new RootDialog());
}
else
{
HandleSystemMessage(activity);
}
}
问题分解
根据我对你问题的理解,你想要实现的是重置对话框堆栈而不完全破坏机器人状态。
事实(来自我从 github 存储库中读取的内容)
- 框架保存对话框堆栈的方式如下:
BotDataStore > BotData > DialogStack
- BotFramework 正在使用 AutoFac 作为 DI 容器
- DialogModule 是他们用于对话框组件的 Autofac 模块
怎么做
从上面了解事实,我的解决方案是
- 注册依赖项以便我们可以在我们的控制器中使用:
// in Global.asax.cs
var builder = new ContainerBuilder();
builder.RegisterModule(new DialogModule());
builder.RegisterModule(new ReflectionSurrogateModule());
builder.RegisterModule(new DialogModule_MakeRoot());
var config = GlobalConfiguration.Configuration;
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterWebApiFilterProvider(config);
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
- 获取 Autofac 容器(随意放置在您的代码中您认为合适的任何位置)
private static ILifetimeScope Container
{
get
{
var config = GlobalConfiguration.Configuration;
var resolver = (AutofacWebApiDependencyResolver)config.DependencyResolver;
return resolver.Container;
}
}
- 在范围
中加载BotData
- 加载 DialogStack
- 重置 DialogStack
- 将新的 BotData 推送回 BotDataStore
using (var scope = DialogModule.BeginLifetimeScope(Container, activity))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(default(CancellationToken));
var stack = scope.Resolve<IDialogStack>();
stack.Reset();
await botData.FlushAsync(default(CancellationToken));
}
希望对您有所帮助。
更新 1 (27/08/2016)
感谢@ejadib 指出,Container 已经在对话中公开 class。
我们可以去掉上面答案中的第2步,最后的代码会是这样
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(default(CancellationToken));
var stack = scope.Resolve<IDialogStack>();
stack.Reset();
await botData.FlushAsync(default(CancellationToken));
}
我知道这有点老了,但我遇到了同样的问题,发布的解决方案不再是最佳方法。
我不确定这是哪个版本可用,但在 3.8.1 上,您可以注册 IScorable
可以在对话框中的任何位置触发的服务。
有一个示例代码展示了它是如何工作的,它确实有一个 "cancel" 全局命令处理程序:
https://github.com/Microsoft/BotBuilder-Samples/tree/master/CSharp/core-GlobalMessageHandlers
部分代码如下所示:
protected override async Task PostAsync(IActivity item, string state, CancellationToken token)
{
this.task.Reset();
}
对其他人有效的附加代码:
private async Task _reset(Activity activity)
{
await activity.GetStateClient().BotState
.DeleteStateForUserWithHttpMessagesAsync(activity.ChannelId, activity.From.Id);
var client = new ConnectorClient(new Uri(activity.ServiceUrl));
var clearMsg = activity.CreateReply();
clearMsg.Text = $"Reseting everything for conversation: {activity.Conversation.Id}";
await client.Conversations.SendToConversationAsync(clearMsg);
}
此代码由用户 mmulhearn 在此处发布:https://github.com/Microsoft/BotBuilder/issues/101#issuecomment-316170517
我不知道如何在 MS Bot Framework 中做一件非常简单的事情:允许用户中断任何对话,离开当前对话框并通过键入 return 到主菜单"quit"、"exit" 或 "start over"。
我的主要对话是这样设置的:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
try
{
if (activity.Type == ActivityTypes.Message)
{
UserActivityLogger.LogUserBehaviour(activity);
if (activity.Text.ToLower() == "start over")
{
//Do something here, but I don't have the IDialogContext here!
}
BotUtils.SendTyping(activity); //send "typing" indicator upon each message received
await Conversation.SendAsync(activity, () => new RootDialog());
}
else
{
HandleSystemMessage(activity);
}
}
我知道如何用 context.Done<DialogType>(this);
终止对话,但是在这个方法中,我没有访问 IDialogContext 对象的权限,所以我不能调用 .Done()
。
除了在所有对话框的每个步骤中添加检查之外,还有其他方法可以在用户键入特定消息时终止整个对话框堆栈吗?
Posted赏金:
我需要一种方法来终止所有 IDialog
,而无需使用我在此处发布的令人发指的黑客攻击(它会删除我需要的所有用户数据,例如用户设置和首选项)。
基本上,当用户键入 "quit" 或 "exit" 时,我需要退出当前正在进行的 IDialog
并 return 到新状态,就好像用户刚刚发起对话。
我需要能够从 MessageController.cs,
执行此操作,但我仍然无法访问 IDialogContext
。我似乎拥有的唯一有用数据是 Activity
对象。如果有人指出其他方法可以做到这一点,我会很高兴。
解决这个问题的另一种方法是找到一些其他方法来检查机器人的其他地方的 "exit" 和 "quit" 关键字,而不是在 Post 方法中。
但不应该在 IDialog
的每一步都进行检查,因为那是太多的代码而且甚至不可能总是可行(当使用 PromptDialog
时,我没有访问用户键入的文本)。
两种我没有探索的可能方式:
- 不是终止所有当前的
IDialog
s,而是开始新的对话 与用户(新ConversationId
) - 获取
IDialogStack
对象并使用它来管理对话框堆栈。
Microsoft 文档没有提及此对象,因此我不知道如何获取它。我没有在机器人的任何地方使用允许 .Switch()
的 Chain
对象,但如果您认为可以重写它以使用它,它也可以是解决此问题的方法之一。但是,我还没有找到如何在各种类型的对话框(FormFlow
和普通的 IDialog
)之间进行分支,这些对话框又调用它们自己的子对话框等
这是一个非常丑陋的骇人听闻的黑客。它基本上会删除所有用户数据(您可能确实需要),这会导致对话重新开始。
如果有人知道更好的方法,又不删除用户数据,请分享。
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
try
{
if (activity.Type == ActivityTypes.Message)
{
//if the user types certain messages, quit all dialogs and start over
string msg = activity.Text.ToLower().Trim();
if (msg == "start over" || msg == "exit" || msg == "quit" || msg == "done" || msg =="start again" || msg == "restart" || msg == "leave" || msg == "reset")
{
//This is where the conversation gets reset!
activity.GetStateClient().BotState.DeleteStateForUser(activity.ChannelId, activity.From.Id);
}
//and even if we reset everything, show the welcome message again
BotUtils.SendTyping(activity); //send "typing" indicator upon each message received
await Conversation.SendAsync(activity, () => new RootDialog());
}
else
{
HandleSystemMessage(activity);
}
}
问题分解
根据我对你问题的理解,你想要实现的是重置对话框堆栈而不完全破坏机器人状态。
事实(来自我从 github 存储库中读取的内容)
- 框架保存对话框堆栈的方式如下:
BotDataStore > BotData > DialogStack
- BotFramework 正在使用 AutoFac 作为 DI 容器
- DialogModule 是他们用于对话框组件的 Autofac 模块
怎么做
从上面了解事实,我的解决方案是
- 注册依赖项以便我们可以在我们的控制器中使用:
// in Global.asax.cs
var builder = new ContainerBuilder();
builder.RegisterModule(new DialogModule());
builder.RegisterModule(new ReflectionSurrogateModule());
builder.RegisterModule(new DialogModule_MakeRoot());
var config = GlobalConfiguration.Configuration;
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterWebApiFilterProvider(config);
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
- 获取 Autofac 容器(随意放置在您的代码中您认为合适的任何位置)
private static ILifetimeScope Container
{
get
{
var config = GlobalConfiguration.Configuration;
var resolver = (AutofacWebApiDependencyResolver)config.DependencyResolver;
return resolver.Container;
}
}
- 在范围 中加载BotData
- 加载 DialogStack
- 重置 DialogStack
- 将新的 BotData 推送回 BotDataStore
using (var scope = DialogModule.BeginLifetimeScope(Container, activity))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(default(CancellationToken));
var stack = scope.Resolve<IDialogStack>();
stack.Reset();
await botData.FlushAsync(default(CancellationToken));
}
希望对您有所帮助。
更新 1 (27/08/2016)
感谢@ejadib 指出,Container 已经在对话中公开 class。
我们可以去掉上面答案中的第2步,最后的代码会是这样
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(default(CancellationToken));
var stack = scope.Resolve<IDialogStack>();
stack.Reset();
await botData.FlushAsync(default(CancellationToken));
}
我知道这有点老了,但我遇到了同样的问题,发布的解决方案不再是最佳方法。
我不确定这是哪个版本可用,但在 3.8.1 上,您可以注册 IScorable
可以在对话框中的任何位置触发的服务。
有一个示例代码展示了它是如何工作的,它确实有一个 "cancel" 全局命令处理程序:
https://github.com/Microsoft/BotBuilder-Samples/tree/master/CSharp/core-GlobalMessageHandlers
部分代码如下所示:
protected override async Task PostAsync(IActivity item, string state, CancellationToken token)
{
this.task.Reset();
}
对其他人有效的附加代码:
private async Task _reset(Activity activity)
{
await activity.GetStateClient().BotState
.DeleteStateForUserWithHttpMessagesAsync(activity.ChannelId, activity.From.Id);
var client = new ConnectorClient(new Uri(activity.ServiceUrl));
var clearMsg = activity.CreateReply();
clearMsg.Text = $"Reseting everything for conversation: {activity.Conversation.Id}";
await client.Conversations.SendToConversationAsync(clearMsg);
}
此代码由用户 mmulhearn 在此处发布:https://github.com/Microsoft/BotBuilder/issues/101#issuecomment-316170517