您现在访问的是微软AZURE全球版技术文档网站,若需要访问由世纪互联运营的MICROSOFT AZURE中国区技术文档网站,请访问 https://docs.azure.cn.

实现顺序聊天流Implement sequential conversation flow

适用于: SDK v4APPLIES TO: SDK v4

通过发布问题来收集信息是机器人与用户交互的主要方式之一。Gathering information by posing questions is one of the main ways a bot interacts with users. 对话库提供有用的内置功能(如 prompt 类),以轻松提问和验证答复,从而确保它与特定数据类型匹配或符合自定义验证规则 。The dialogs library provides useful built-in features such as prompt classes that make it easy to ask questions and validate the response to make sure it matches a specific data type or meets custom validation rules.

可以使用对话库来管理简单的和复杂的聊天流。You can manage simple and complex conversation flows using the dialogs library. 在简单的交互中,机器人将按固定的顺序运行一组步骤,直到聊天完成。In a simple interaction, the bot runs through a fixed sequence of steps, and the conversation finishes. 当 bot 需要从用户那里收集信息时,对话框很有用。A dialog is useful when the bot needs to gather information from the user.

本文说明如何通过从瀑布对话框创建并调用提示来实现简单的对话流程。This article shows how to implement simple conversation flow by creating prompts and calling them from a waterfall dialog.

提示

有关如何在不使用对话库的情况下编写自己的提示的示例,请参阅创建自己的提示来收集用户输入一文。For examples of how to write your own prompts without using the dialogs library, see the Create your own prompts to gather user input article.

先决条件Prerequisites

关于此示例About this sample

多轮提示示例使用瀑布式对话框、几个提示和组件对话框来创建一个简单的交互,该交互会询问用户一系列问题。The multi-turn prompts sample uses a waterfall dialog, a few prompts, and a component dialog to create a simple interaction that asks the user a series of questions. 代码使用对话来循环执行以下步骤:The code uses a dialog to cycle through these steps:

步骤Steps 提示类型Prompt type
请求用户提供其交通方式Ask the user for their mode of transportation 选项提示Choice prompt
请求用户输入其姓名Ask the user for their name 文本提示Text prompt
询问用户是否愿意提供其年龄Ask the user if they want to provide their age 确认提示Confirm prompt
如果他们回答 "是",请询问其年龄If they answered yes, ask for their age Number prompt,验证仅接受大于0且小于150的年龄段Number prompt, with validation to only accept ages greater than 0 and less than 150
如果他们不使用 Microsoft Teams,则请求他们提供个人资料图片If they're not using Microsoft Teams, ask them for a profile picture 附件提示,验证是否允许缺少附件Attachment prompt, with validation to allow a missing attachment
询问收集的信息是否为 "确定"Ask if the collected information is "ok" 重用确认提示Reuse Confirm prompt

最后,如果用户回答“是”,则显示收集的信息;否则,告知用户他们的信息不会保留。Finally, if they answered yes, display the collected information; otherwise, tell the user that their information will not be kept.

创建主对话Create the main dialog

若要使用对话,请安装 Microsoft.Bot.Builder.Dialogs NuGet 包。To use dialogs, install the Microsoft.Bot.Builder.Dialogs NuGet package.

机器人通过 UserProfileDialog 来与用户交互。The bot interacts with the user via UserProfileDialog. 创建机器人的类时 DialogBot ,将 UserProfileDialog 设置为其主对话框。When creating the bot's DialogBot class, the UserProfileDialog is set as its main dialog. 然后,机器人使用 Run 帮助器方法来访问该对话。The bot then uses a Run helper method to access the dialog.

C # 用户配置文件对话框

Dialogs\UserProfileDialog.csDialogs\UserProfileDialog.cs

首先 UserProfileDialog ,创建从类派生的, ComponentDialog 并有7个步骤。Begin by creating the UserProfileDialog that derives from the ComponentDialog class, and has 7 steps.

UserProfileDialog 构造函数中创建瀑布步骤、提示和瀑布对话,然后将其添加到对话集。In the UserProfileDialog constructor, create the waterfall steps, prompts and the waterfall dialog, and add them to the dialog set. 提示需要位于使用这些提示的同一对话集中。The prompts need to be in the same dialog set in which they are used.

public UserProfileDialog(UserState userState)
    : base(nameof(UserProfileDialog))
{
    _userProfileAccessor = userState.CreateProperty<UserProfile>("UserProfile");

    // This array defines how the Waterfall will execute.
    var waterfallSteps = new WaterfallStep[]
    {
        TransportStepAsync,
        NameStepAsync,
        NameConfirmStepAsync,
        AgeStepAsync,
        PictureStepAsync,
        ConfirmStepAsync,
        SummaryStepAsync,
    };

    // Add named dialogs to the DialogSet. These names are saved in the dialog state.
    AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
    AddDialog(new TextPrompt(nameof(TextPrompt)));
    AddDialog(new NumberPrompt<int>(nameof(NumberPrompt<int>), AgePromptValidatorAsync));
    AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
    AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));
    AddDialog(new AttachmentPrompt(nameof(AttachmentPrompt), PicturePromptValidatorAsync));

    // The initial child Dialog to run.
    InitialDialogId = nameof(WaterfallDialog);
}

接下来,添加对话框用来提示输入的步骤。Next, add the steps that the dialog uses to prompt for input. 若要使用提示,请从对话中的某个步骤调用它,然后使用 stepContext.Result 检索下一步骤中的提示结果。To use a prompt, call it from a step in your dialog and retrieve the prompt result in the following step using stepContext.Result. 在幕后,提示是由两个步骤组成的对话。Behind the scenes, prompts are a two-step dialog. 首先,提示符要求输入。First, the prompt asks for input. 然后,它返回有效值,或从重新提示开始,直到收到有效输入。Then it returns the valid value, or starts over from the beginning with a reprompt until it receives a valid input.

应始终从瀑布步骤返回非 null 的 DialogTurnResultYou should always return a non-null DialogTurnResult from a waterfall step. 否则,对话框可能不会按设计方式工作。If you don't, your dialog may not work as designed. 下面显示的是 NameStepAsync 瀑布对话框中的的实现。Shown below is the implementation for NameStepAsync in the waterfall dialog.

private static async Task<DialogTurnResult> NameStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    stepContext.Values["transport"] = ((FoundChoice)stepContext.Result).Value;

    return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text("Please enter your name.") }, cancellationToken);
}

在中 AgeStepAsync ,如果用户的输入无法进行验证,则为其指定重试提示,原因是它采用的是提示无法分析的格式,或者输入未通过验证条件。In AgeStepAsync, specify a retry prompt for when the user's input fails to validate, either because it's in a format that the prompt can't parse, or the input fails a validation criteria. 在这种情况下,如果未提供重试提示,则提示将使用初始提示文本来重新提示用户输入。In this case, if no retry prompt was provided, the prompt will use the initial prompt text to re-prompt the user for input.

private async Task<DialogTurnResult> AgeStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    if ((bool)stepContext.Result)
    {
        // User said "yes" so we will be prompting for the age.
        // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
        var promptOptions = new PromptOptions
        {
            Prompt = MessageFactory.Text("Please enter your age."),
            RetryPrompt = MessageFactory.Text("The value entered must be greater than 0 and less than 150."),
        };

        return await stepContext.PromptAsync(nameof(NumberPrompt<int>), promptOptions, cancellationToken);
    }
    else
    {
        // User said "no" so we will skip the next step. Give -1 as the age.
        return await stepContext.NextAsync(-1, cancellationToken);
    }
}

UserProfile.csUserProfile.cs

用户的交通方式、姓名和年龄将保存在 UserProfile 类的实例中。The user's mode of transportation, name, and age are saved in an instance of the UserProfile class.

public class UserProfile
{
    public string Transport { get; set; }

    public string Name { get; set; }

    public int Age { get; set; }

    public Attachment Picture { get; set; }
}

Dialogs\UserProfileDialog.csDialogs\UserProfileDialog.cs

在最后一步中,检查 stepContext.Result 在上一个瀑布步骤中调用的对话框返回的。In the last step, check the stepContext.Result returned by the dialog called in the previous waterfall step. 如果返回值为 true,则用户配置文件访问器获取并更新用户配置文件。If the return value is true, the user profile accessor gets and updates the user profile. 若要获取用户配置文件,请调用 GetAsync ,并设置 userProfile.TransportuserProfile.Name userProfile.Age 和属性的值 userProfile.PictureTo get the user profile, call GetAsync and then set the values of the userProfile.Transport, userProfile.Name, userProfile.Age and userProfile.Picture properties. 最后,在调用之前汇总用户的信息 EndDialogAsync ,这将结束对话框。Finally, summarize the information for the user before calling EndDialogAsync, which ends the dialog. 结束对话会从对话堆栈中弹出该对话,并将可选结果返回到该对话的父级。Ending the dialog pops it off the dialog stack and returns an optional result to the dialog's parent. 父级是启动了刚刚结束的对话的对话或方法。The parent is the dialog or method that started the dialog that just ended.

private async Task<DialogTurnResult> SummaryStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    if ((bool)stepContext.Result)
    {
        // Get the current profile object from user state.
        var userProfile = await _userProfileAccessor.GetAsync(stepContext.Context, () => new UserProfile(), cancellationToken);

        userProfile.Transport = (string)stepContext.Values["transport"];
        userProfile.Name = (string)stepContext.Values["name"];
        userProfile.Age = (int)stepContext.Values["age"];
        userProfile.Picture = (Attachment)stepContext.Values["picture"];

        var msg = $"I have your mode of transport as {userProfile.Transport} and your name as {userProfile.Name}";

        if (userProfile.Age != -1)
        {
            msg += $" and your age as {userProfile.Age}";
        }

        msg += ".";

        await stepContext.Context.SendActivityAsync(MessageFactory.Text(msg), cancellationToken);

        if (userProfile.Picture != null)
        {
            try
            {
                await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(userProfile.Picture, "This is your profile picture."), cancellationToken);
            }
            catch
            {
                await stepContext.Context.SendActivityAsync(MessageFactory.Text("A profile picture was saved but could not be displayed here."), cancellationToken);
            }
        }
    }
    else
    {
        await stepContext.Context.SendActivityAsync(MessageFactory.Text("Thanks. Your profile will not be kept."), cancellationToken);
    }

    // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is the end.
    return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}

运行对话Run the dialog

Bots\DialogBot.csBots\DialogBot.cs

OnMessageActivityAsync 处理程序使用 RunAsync 方法来启动或继续对话。The OnMessageActivityAsync handler uses the RunAsync method to start or continue the dialog. OnTurnAsync 使用 bot 的状态管理对象将所有状态更改持久保存到存储中。OnTurnAsync uses the bot's state management objects to persist any state changes to storage. ActivityHandler.OnTurnAsync 方法调用各种活动处理程序方法,例如 OnMessageActivityAsyncThe ActivityHandler.OnTurnAsync method calls the various activity handler methods, such as OnMessageActivityAsync. 通过这种方式,在消息处理程序完成后,但在转换完成之前保存状态。In this way, the state is saved after the message handler completes but before the turn itself completes.

public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
    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);
}

注册机器人的服务Register services for the bot

此机器人使用以下服务:This bot uses the following services:

  • 机器人的基本服务:凭据提供程序、适配器和机器人实现。Basic services for a bot: a credential provider, an adapter, and the bot implementation.
  • 用于管理状态的服务:存储、用户状态和聊天状态。Services for managing state: storage, user state, and conversation state.
  • 机器人使用的对话。The dialog the bot will use.

Startup.csStartup.cs

为中的机器人注册服务 StartupRegister services for the bot in Startup. 可以通过依赖项注入将这些服务用于其他代码部分。These services are available to other parts of the code through dependency injection.

// 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();

    // Create the Bot Framework 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>();

    // Create the Conversation state. (Used by the Dialog system itself.)
    services.AddSingleton<ConversationState>();

    // The Dialog that will be run by the bot.
    services.AddSingleton<UserProfileDialog>();

    // Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
    services.AddTransient<IBot, DialogBot<UserProfileDialog>>();
}

备注

内存存储仅用于测试,不用于生产。Memory storage is used for testing purposes only and is not intended for production use. 请务必对生产用机器人使用持久型存储。Be sure to use a persistent type of storage for a production bot.

测试机器人To test the bot

  1. 如果尚未这样做,请安装 机器人框架模拟器If you haven't done so already, install the Bot Framework Emulator.
  2. 在计算机本地运行示例。Run the sample locally on your machine.
  3. 按如下所示启动模拟器,连接到机器人,然后发送消息。Start the Emulator, connect to your bot, and send messages as shown below.

多轮次提示对话的示例运行

其他信息Additional information

关于对话和机器人状态About dialog and bot state

在此 bot 中,定义了两个状态属性访问器:In this bot, two state property accessors are defined:

  • 一个访问器是在对话状态属性的聊天状态中创建的。One created within conversation state for the dialog state property. 对话框状态跟踪用户在对话框集的对话框中的位置,并由对话框上下文进行更新,例如,当调用 " 开始" 对话框 或 " 继续 " 对话框方法时。The dialog state tracks where the user is within the dialogs of a dialog set, and it's updated by the dialog context, such as when the begin dialog or continue dialog methods are called.
  • 一个访问器是在用户配置文件属性的用户状态中创建的。One created within user state for the user profile property. 机器人使用它来跟踪与用户有关的信息,并且您必须在对话框代码中显式管理此状态。The bot uses this to track information it has about the user, and you must explicitly manage this state in the dialog code.

状态属性访问器的 getset 方法在状态管理对象的缓存中获取和设置属性值。The get and set methods of a state property accessor get and set the value of the property in the state management object's cache. 首次在某个轮次中请求状态属性的值时,将填充该缓存,但是,必须显式持久保存该值。The cache is populated the first time the value of a state property is requested in a turn, but it must be persisted explicitly. 为了保持对这两个状态属性的更改,将执行对相应状态管理对象的 " 保存更改 " 方法的调用。In order to persist changes to both of these state properties, a call to the save changes method, of the corresponding state management object, is performed.

此示例从对话内部更新用户配置文件状态。This sample updates the user profile state from within the dialog. 这种做法适用于简单的机器人,但如果你想要跨 bot 重复使用对话,则它将不起作用。This practice can work for a simple bot, but it won't work if you want to reuse a dialog across bots.

有多种选项可将对话步骤与机器人状态相分离。There are various options for keeping dialog steps and bot state separate. 例如,在对话收集完整信息后,你可以:For example, once your dialog gathers complete information, you can:

  • 使用 end dialog 方法可将收集的数据作为返回值提供给父上下文。Use the end dialog method to provide the collected data as return value back to the parent context. 这可以是 bot 的 turn 处理程序或对话框堆栈上的更早的活动对话框,并且是提示类的设计方式。This can be the bot's turn handler or an earlier active dialog on the dialog stack and it's how the prompt classes are designed.
  • 向相应的服务生成请求。Generate a request to an appropriate service. 如果机器人充当较大服务的前端,此选项可能很适合。This might work well if your bot acts as a front end to a larger service.

提示验证程序方法的定义Definition of a prompt validator method

UserProfileDialog.csUserProfileDialog.cs

下面是方法定义的验证程序代码示例 AgePromptValidatorAsyncBelow is a validator code example for the AgePromptValidatorAsync method definition. promptContext.Recognized.Value 包含已解析的值,此值是一个整数,用于数值提示。promptContext.Recognized.Value contains the parsed value, which is an integer here for the number prompt. promptContext.Recognized.Succeeded 指示提示是否能够解析用户的输入。promptContext.Recognized.Succeeded indicates whether the prompt was able to parse the user's input or not. 验证程序应返回 false 以指示未接受该值,且提示对话应重新提示用户;否则,返回 true 以指示接受输入并从提示对话返回。The validator should return false to indicate that the value was not accepted and the prompt dialog should reprompt the user; otherwise, return true to accept the input and return from the prompt dialog. 请注意,可以根据方案更改验证程序中的值。Note that you can change the value in the validator per your scenario.

private static Task<bool> AgePromptValidatorAsync(PromptValidatorContext<int> promptContext, CancellationToken cancellationToken)
{
    // This condition is our validation rule. You can also change the value at this point.
    return Task.FromResult(promptContext.Recognized.Succeeded && promptContext.Recognized.Value > 0 && promptContext.Recognized.Value < 150);
}

后续步骤Next steps