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

适用于:SDK v4

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

重要

Bot Framework SDK 和 Bot Framework Emulator 已在 GitHub 上存档。 项目不再更新或维护。 自 2025 年 12 月 31 日起,Bot Framework SDK 的支持票证将不再提供服务。

若要使用所选的 AI 服务、业务流程和知识生成代理,请考虑使用 Microsoft 365 代理 SDK。 代理 SDK 对 C#、JavaScript 或 Python 具有语言支持。 可以在 aka.ms/agents 了解有关代理 SDK 的详细信息。 如果现有的机器人是使用 Bot Framework SDK 生成的,则可以将机器人更新到代理 SDK。 查看 Bot Framework SDK 到代理 SDK 迁移指南的核心更改和更新。

如果要构建设计为在 Microsoft Teams 中工作的协作代理,请考虑使用 Teams SDK。 它为在 Teams 环境中运行的代理提供 Teams 特定的 API、自适应卡支持和内置 AI 协同调度功能。 可以在 Teams SDK(Teams AI 库)中了解详细信息。

如果要查找基于 SaaS 的代理平台,请考虑 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);
}

顶层对话框

顶级对话有 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);
        }
    }
}

审阅选择对话框

“审核选择”对话框分为两个步骤:

  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);
    }
}

运行对话

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

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

  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,连接到机器人,然后发送消息。

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

其他资源

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

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

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

后续步骤