You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 06:30:15