實作循序對話流程Implement sequential conversation flow

適用于: SDK v4APPLIES TO: SDK v4

張貼問題來收集資訊是 Bot 與使用者互動時的其中一種主要方式。Gathering information by posing questions is one of the main ways a bot interacts with users. dialogs 程式庫提供實用的內建功能 (例如 提示 類別),可讓您輕鬆地詢問問題,以及驗證回應以確保回應符合特定資料類型或符合自訂驗證規則。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. 在簡單互動中,Bot 透過一連串固定的步驟執行,然後完成對話。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.

PrerequisitesPrerequisites

關於此範例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 數位提示,驗證只接受大於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.

Bot 會透過 UserProfileDialog 與使用者互動。The bot interacts with the user via UserProfileDialog. 建立 bot 的類別時 DialogBotUserProfileDialog 會設定為其主要對話。When creating the bot's DialogBot class, the UserProfileDialog is set as its main dialog. Bot 接著會使用 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);
}

為 Bot 註冊服務Register services for the bot

此 bot 會使用下列服務:This bot uses the following services:

  • Bot 的基本服務:認證提供者、配接器及 Bot 實作。Basic services for a bot: a credential provider, an adapter, and the bot implementation.
  • 用於管理狀態的服務:儲存體、使用者狀態及交談狀態。Services for managing state: storage, user state, and conversation state.
  • Bot 會使用的對話。The dialog the bot will use.

Startup.csStartup.cs

在中為 bot 註冊服務 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.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. 針對生產 Bot,請務必使用永續性的儲存體類型。Be sure to use a persistent type of storage for a production bot.

測試 BotTo test the bot

  1. 如果您尚未這麼做,請安裝 Bot Framework EmulatorIf you haven't done so already, install the Bot Framework Emulator.
  2. 在您的電腦本機執行範例。Run the sample locally on your machine.
  3. 啟動模擬器、連線到您的 Bot 並傳送如下所示的訊息。Start the Emulator, connect to your bot, and send messages as shown below.

多回合提示對話的執行範例

其他資訊Additional information

關於對話方塊和 Bot 狀態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. 對話狀態會追蹤使用者在對話集對話中的位置,並由對話內容更新,例如當呼叫 begin dialogcontinue 對話方塊 方法時。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. Bot 會使用此資訊來追蹤與使用者有關的資訊,而且您必須在對話方塊程式碼中明確地管理此狀態。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,但如果您想要跨 bot 重複使用對話,則無法使用。This practice can work for a simple bot, but it won't work if you want to reuse a dialog across bots.

有各種選項可將對話方塊步驟和 Bot 狀態分開。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 的回合處理常式或對話堆疊上較早的作用中對話,以及提示類別的設計方式。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. 如果 Bot 作為大型服務的前端,這可能效果良好。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