如何使用Microsoft Bot Service与同一用户开启多并行Messenger对话?
在Microsoft Bot Service中实现Messenger多并行主题对话
嘿,我完全明白你想要实现的效果——同一用户在Messenger上和机器人同时进行多个独立主题的对话,比如topic1、topic2、topic3各自跑自己的流程,互不干扰,而不是那种线性的瀑布流。这确实不能用常规的单对话栈来做,核心思路是为每个主题单独维护对话状态,下面是具体的实现方案:
1. 定义多主题对话状态结构
首先得在用户状态(UserState)里建一个字典,用来存每个主题对应的对话状态(DialogState)。这样每个主题的对话栈都会被独立保存,不会互相串:
// 定义用户级别的多主题对话状态容器 public class MultiTopicUserState { // Key是主题名称,Value是对应主题的对话状态 public Dictionary<string, DialogState> TopicDialogStates { get; set; } = new Dictionary<string, DialogState>(); }
2. 为每个主题创建独立对话框
针对每个主题(topic1、topic2、topic3),单独写对应的对话框就行,它们可以是瀑布流、组件对话框或者任何你需要的对话类型。举个例子:
// Topic1的对话框示例,你可以照着写Topic2、Topic3的 public class Topic1Dialog : ComponentDialog { public Topic1Dialog() : base(nameof(Topic1Dialog)) { AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { FirstStepAsync, FinalStepAsync })); InitialDialogId = nameof(WaterfallDialog); } private async Task<DialogTurnResult> FirstStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { return await stepContext.PromptAsync("textPrompt", new PromptOptions { Prompt = MessageFactory.Text("请输入topic1的第一步内容:") }, cancellationToken); } private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { await stepContext.Context.SendActivityAsync($"你在topic1里输入了:{stepContext.Result}", cancellationToken: cancellationToken); return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); } }
3. 在机器人中初始化状态与对话框
在机器人的构造函数里,注册UserState和所有主题对话框,同时创建多主题状态的访问器,方便后续读写:
private readonly UserState _userState; private readonly MultiTopicUserStateAccessor _multiTopicAccessor; private readonly DialogSet _dialogSet; public MultiTopicBot(UserState userState, Topic1Dialog topic1Dialog, Topic2Dialog topic2Dialog, Topic3Dialog topic3Dialog) { _userState = userState; _multiTopicAccessor = new MultiTopicUserStateAccessor(userState); // 初始化DialogSet,这里不用传默认DialogState,因为我们要动态加载每个主题的状态 _dialogSet = new DialogSet(); _dialogSet.Add(topic1Dialog); _dialogSet.Add(topic2Dialog); _dialogSet.Add(topic3Dialog); _dialogSet.Add(new TextPrompt("textPrompt")); }
对应的状态访问器可以这么写:
public class MultiTopicUserStateAccessor { public IStatePropertyAccessor<MultiTopicUserState> MultiTopicState { get; } public MultiTopicUserStateAccessor(UserState userState) { MultiTopicState = userState.CreateProperty<MultiTopicUserState>("MultiTopicUserState"); } }
4. 处理消息并启动/继续对应主题的对话
在OnTurnAsync方法里,根据用户的输入(比如用户发“开启topic1”或者选了主题菜单)确定要操作的主题,然后加载该主题的DialogState,继续或者启动对话:
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) { await base.OnTurnAsync(turnContext, cancellationToken); if (turnContext.Activity.Type == ActivityTypes.Message) { var messageText = turnContext.Activity.Text.ToLower(); string targetTopic = null; // 判断用户要操作的主题,这里可以根据你的需求调整触发逻辑 if (messageText.Contains("topic1")) targetTopic = nameof(Topic1Dialog); else if (messageText.Contains("topic2")) targetTopic = nameof(Topic2Dialog); else if (messageText.Contains("topic3")) targetTopic = nameof(Topic3Dialog); if (!string.IsNullOrEmpty(targetTopic)) { // 获取用户的多主题状态 var multiTopicState = await _multiTopicAccessor.MultiTopicState.GetAsync(turnContext, () => new MultiTopicUserState(), cancellationToken); // 如果该主题还没初始化对话状态,先建一个 if (!multiTopicState.TopicDialogStates.ContainsKey(targetTopic)) { multiTopicState.TopicDialogStates[targetTopic] = new DialogState(); } // 加载该主题的DialogState,创建对应的DialogContext var dc = await _dialogSet.CreateContextAsync(turnContext, multiTopicState.TopicDialogStates[targetTopic], cancellationToken); // 如果对话没在运行,就启动;否则继续当前流程 if (dc.ActiveDialog == null) { await dc.BeginDialogAsync(targetTopic, null, cancellationToken); } else { await dc.ContinueDialogAsync(cancellationToken); } // 记得保存状态,不然下次就找不到之前的对话进度了 await _userState.SaveChangesAsync(turnContext, false, cancellationToken); } else { await turnContext.SendActivityAsync("请输入topic1、topic2或topic3来开启对应主题的对话哦", cancellationToken: cancellationToken); } } }
关键注意事项
- 状态完全隔离:每个主题的对话状态都是独立存储的,用户在topic1输入的内容绝对不会影响topic2的对话流程。
- Messenger适配:Messenger的用户ID会被Bot Service自动映射到
turnContext.Activity.From.Id,所以UserState可以准确关联到每个用户的多主题状态。 - 对话ID唯一性:确保每个主题对话框的ID是唯一的(比如用类名当ID),避免启动对话时出现冲突。
- 随时切换主题:用户可以随时发送主题名称切换到对应对话,当前主题的对话会暂停,下次再触发该主题时会从上次的步骤继续。
这样就能实现同一用户在Messenger上和机器人进行多个并行主题对话的需求了。
内容的提问来源于stack exchange,提问作者Brice Peters
相关产品推荐
相关产品推荐

