使用分支和循环创建高级聊天流

适用于:SDK v4

可以使用对话库来创建复杂的聊天流。 本文介绍如何管理可分支和循环的复杂聊天,以及如何在对话的不同部分之间传递参数。

注意

Bot Framework JavaScript、C# 和 Python SDK 将继续受支持,但 Java SDK 即将停用,最终长期支持将于 2023 年 11 月结束。

使用 Java SDK 构建的现有机器人将继续正常运行。

要生成新的机器人,请考虑使用 Microsoft Copilot Studio 并阅读选择正确的助理解决方案

有关详细信息,请参阅机器人构建的未来

先决条件

关于此示例

本示例演示一个可以注册用户,让其针对列表中的最多两家公司发表评论的机器人。 该机器人使用 3 个组件对话来管理对话流。 每个组件对话包含一个瀑布对话,以及收集用户输入所需的任何提示。 这些对话在以下部分详述。 它使用聊天状态管理其对话,并使用用户状态来保存有关用户及其所要评论的公司的信息。

此机器人派生自活动处理程序。 与许多示例机器人一样,它会欢迎用户,使用对话处理来自用户的消息,并在该轮聊天结束之前保存用户和聊天状态。

若要使用对话,请安装 Microsoft.Bot.Builder.Dialogs NuGet 包。

C# 示例的类图。

定义用户配置文件

用户配置文件会包含通过对话收集的信息、用户的姓名、年龄,以及选择要评论的公司。

UserProfile.cs

/// <summary>Contains information about a user.</summary>
public class UserProfile
{
    public string Name { get; set; }

    public int Age { get; set; }

    // The list of companies the user wants to review.
    public List<string> CompaniesToReview { get; set; } = new List<string>();

创建对话

此机器人包含 3 个对话:

  • 主对话会启动整个进程,并汇总收集的信息。
  • 顶级对话根据用户的年龄收集用户信息并包括分支逻辑。
  • 用户可以通过“评论-选择”对话以迭代方式选择要评论的公司。 它使用循环逻辑来这样做。

主对话

主对话有 2 个步骤:

  1. 启动顶级对话。
  2. 检索并汇总顶级对话收集的用户配置文件,将该信息保存到用户状态,然后指示主对话的末尾。

Dialogs\MainDialog.cs

private async Task<DialogTurnResult> InitialStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    return await stepContext.BeginDialogAsync(nameof(TopLevelDialog), null, cancellationToken);
}

private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    var userInfo = (UserProfile)stepContext.Result;

    string status = "You are signed up to review "
        + (userInfo.CompaniesToReview.Count is 0 ? "no companies" : string.Join(" and ", userInfo.CompaniesToReview))
        + ".";

    await stepContext.Context.SendActivityAsync(status);

    var accessor = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
    await accessor.SetAsync(stepContext.Context, userInfo, cancellationToken);

    return await stepContext.EndDialogAsync(null, cancellationToken);
}

top-level 对话

顶级对话有 4 个步骤:

  1. 请求用户的姓名。
  2. 请求用户的年龄。
  3. 根据用户的年龄,启动“评论-选择”对话或转到下一步。
  4. 最后,感谢用户参与并返回收集的信息。

第一步创建一个空用户配置文件作为对话状态的一部分。 此对话从一个空的配置文件开始,在进行过程中将信息添加到配置文件。 结束时,最后一步会返回收集的信息。

在第三(开始选择)步中,会根据用户的年龄将聊天流分支。

Dialogs\TopLevelDialog.cs

            stepContext.Values[UserInfo] = new UserProfile();

            var promptOptions = new PromptOptions { Prompt = MessageFactory.Text("Please enter your name.") };

            // Ask the user to enter their name.
            return await stepContext.PromptAsync(nameof(TextPrompt), promptOptions, cancellationToken);
        }

        private async Task<DialogTurnResult> AgeStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            // Set the user's name to what they entered in response to the name prompt.
            var userProfile = (UserProfile)stepContext.Values[UserInfo];
            userProfile.Name = (string)stepContext.Result;

            var promptOptions = new PromptOptions { Prompt = MessageFactory.Text("Please enter your age.") };

            // Ask the user to enter their age.
            return await stepContext.PromptAsync(nameof(NumberPrompt<int>), promptOptions, cancellationToken);
        }

        private async Task<DialogTurnResult> StartSelectionStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            // Set the user's age to what they entered in response to the age prompt.
            var userProfile = (UserProfile)stepContext.Values[UserInfo];
            userProfile.Age = (int)stepContext.Result;

            if (userProfile.Age < 25)
            {
                // If they are too young, skip the review selection dialog, and pass an empty list to the next step.
                await stepContext.Context.SendActivityAsync(
                    MessageFactory.Text("You must be 25 or older to participate."),
                    cancellationToken);
                return await stepContext.NextAsync(new List<string>(), cancellationToken);
            }
            else
            {
                // Otherwise, start the review selection dialog.
                return await stepContext.BeginDialogAsync(nameof(ReviewSelectionDialog), null, cancellationToken);
            }
        }

        private async Task<DialogTurnResult> AcknowledgementStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            // Set the user's company selection to what they entered in the review-selection dialog.
            var userProfile = (UserProfile)stepContext.Values[UserInfo];
            userProfile.CompaniesToReview = stepContext.Result as List<string> ?? new List<string>();

            // Thank them for participating.
            await stepContext.Context.SendActivityAsync(
                MessageFactory.Text($"Thanks for participating, {((UserProfile)stepContext.Values[UserInfo]).Name}."),
                cancellationToken);

            // Exit the dialog, returning the collected user information.
            return await stepContext.EndDialogAsync(stepContext.Values[UserInfo], cancellationToken);
        }
    }
}

review-selection 对话

review-selection 对话包含两个步骤:

  1. 请求用户选择要评论的公司,或选择 done 以完成操作。
    • 如果对话是使用任何初始信息启动的,则可通过瀑布步骤上下文的 options 属性获取该信息。 “评论-选择”对话可以自行重启,并使用它来允许用户选择多个要评论的公司。
    • 如果用户已选择要评论的公司,则会从可用的选项中删除该公司。
    • 添加了 done 选项,允许用户早退出循环。
  2. 根据情况重复此对话或退出。
    • 如果用户选择了要评论的公司,请将其添加到列表中。
    • 如果用户选择了 2 个公司或选择了退出,请结束对话并返回收集的列表。
    • 否则,请重启对话,并使用列表的内容将其初始化。

Dialogs\ReviewSelectionDialog.cs

private async Task<DialogTurnResult> SelectionStepAsync(
    WaterfallStepContext stepContext,
    CancellationToken cancellationToken)
{
    // Continue using the same selection list, if any, from the previous iteration of this dialog.
    var list = stepContext.Options as List<string> ?? new List<string>();
    stepContext.Values[CompaniesSelected] = list;

    // Create a prompt message.
    string message;
    if (list.Count is 0)
    {
        message = $"Please choose a company to review, or `{DoneOption}` to finish.";
    }
    else
    {
        message = $"You have selected **{list[0]}**. You can review an additional company, " +
            $"or choose `{DoneOption}` to finish.";
    }

    // Create the list of options to choose from.
    var options = _companyOptions.ToList();
    options.Add(DoneOption);
    if (list.Count > 0)
    {
        options.Remove(list[0]);
    }

    var promptOptions = new PromptOptions
    {
        Prompt = MessageFactory.Text(message),
        RetryPrompt = MessageFactory.Text("Please choose an option from the list."),
        Choices = ChoiceFactory.ToChoices(options),
    };

    // Prompt the user for a choice.
    return await stepContext.PromptAsync(nameof(ChoicePrompt), promptOptions, cancellationToken);
}

private async Task<DialogTurnResult> LoopStepAsync(
    WaterfallStepContext stepContext,
    CancellationToken cancellationToken)
{
    // Retrieve their selection list, the choice they made, and whether they chose to finish.
    var list = stepContext.Values[CompaniesSelected] as List<string>;
    var choice = (FoundChoice)stepContext.Result;
    var done = choice.Value == DoneOption;

    if (!done)
    {
        // If they chose a company, add it to the list.
        list.Add(choice.Value);
    }

    if (done || list.Count >= 2)
    {
        // If they're done, exit and return their list.
        return await stepContext.EndDialogAsync(list, cancellationToken);
    }
    else
    {
        // Otherwise, repeat this dialog, passing in the list from this iteration.
        return await stepContext.ReplaceDialogAsync(InitialDialogId, list, cancellationToken);
    }
}

运行对话

“对话机器人” 类扩展了活动处理程序,包含用于运行对话的逻辑。 “对话和欢迎机器人” 类扩展了对话机器人,在用户加入聊天时也会欢迎用户。

机器人的轮次处理程序重复这 3 个对话定义的对话流。 当它收到来自用户的消息时:

  1. 它运行主对话。
    • 如果对话堆栈为空,这将启动主对话。
    • 否则,对话仍处于中间进程,这将继续活动对话。
  2. 它会保存状态,以持久保存对用户、聊天和对话状态所做的任何更新。

Bots\DialogBot.cs

public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
    await base.OnTurnAsync(turnContext, cancellationToken);

    // Save any state changes that might have occurred during the turn.
    await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
    await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
}

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    Logger.LogInformation("Running dialog with Message Activity.");

    // Run the Dialog with the new message Activity.
    await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}

注册机器人的服务

根据需要创建并注册服务:

  • 机器人的基本服务:适配器和机器人实现。
  • 用于管理状态的服务:存储、用户状态和聊天状态。
  • 机器人将使用的根对话。

Startup.cs

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient().AddControllers().AddNewtonsoftJson(options =>
    {
        options.SerializerSettings.MaxDepth = HttpHelper.BotMessageSerializerSettings.MaxDepth;
    });

    // Create the Bot Framework Authentication to be used with the Bot Adapter.
    services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();

    // Create the Bot Adapter with error handling enabled.
    services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();

    // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.)
    services.AddSingleton<IStorage, MemoryStorage>();

    // Create the User state. (Used in this bot's Dialog implementation.)
    services.AddSingleton<UserState>();

注意

内存存储仅用于测试,不用于生产。 请务必对生产用机器人使用持久型存储。

测试机器人

  1. 安装 Bot Framework Emulator(如果尚未安装)。

  2. 在计算机本地运行示例。

  3. 按如下所示启动 Emulator,连接到机器人,然后发送消息。

    与复杂对话机器人的对话脚本示例。

其他资源

有关如何实现对话的介绍,请参阅实现有序的对话流,其中使用了单个瀑布对话和一些提示来向用户提出一系列问题。

“对话”库包含提示的基本验证。 你也可以添加自定义验证。 有关详细信息,请参阅使用对话提示收集用户输入

若要简化对话代码并将其重复用于多个机器人,可将对话集的某些部分定义为单独的类。 有关详细信息,请参阅重复使用对话

后续步骤