實作循序對話流程

適用于: SDK v4

藉由提出問題來收集資訊是 Bot 與使用者互動的主要方式之一。 對話方塊程式庫提供實用的內建功能,例如 提示 類別,可讓您輕鬆地提出問題並驗證回應,以確保其符合特定資料類型或符合自訂驗證規則。

您可以使用對話方塊程式庫來管理線性和更複雜的交談流程。 線上性互動中,Bot 會透過固定的步驟循序執行,而交談會完成。 當 Bot 需要從使用者收集資訊時,對話方塊會很有用。

本文說明如何藉由建立提示,並從瀑布式對話呼叫它們,以實作線性對話流程。 如需如何在不使用對話方塊程式庫的情況下撰寫自己的提示的範例,請參閱 建立您自己的提示以收集使用者輸入 一文。

注意

Bot Framework JavaScript、C# 和 Python SDK 將會繼續受到支援,不過,JAVA SDK 即將淘汰,最終長期支援將于 2023 年 11 月結束。 只會執行此存放庫中的重要安全性和錯誤修正。

使用 JAVA SDK 建置的現有 Bot 將繼續運作。

針對新的 Bot 建置,請考慮使用 Power Virtual Agents ,並閱讀 選擇正確的聊天機器人解決方案

如需詳細資訊,請參閱 Bot 建置 的未來。

必要條件

關於此範例

多回合提示範例會使用瀑布式對話、一些提示和元件對話來建立線性互動,詢問使用者一系列問題。 程式碼會使用對話方塊來迴圈執行下列步驟:

步驟 提示類型
詢問使用者其運輸模式 選擇提示
詢問使用者其名稱 文字提示
詢問使用者是否要提供年齡 確認提示
如果他們回答是的, 要求他們的年齡 數位提示,驗證只接受大於 0 且小於 150 的年齡
如果他們未使用 Microsoft Teams,請要求他們提供個人檔案圖片 附件提示,具有允許遺漏附件的驗證
詢問收集到的資訊是否為「確定」 重複使用確認提示

最後,如果他們回答是,則顯示收集到的資訊;否則,請告知使用者不會保留其資訊。

建立主要對話方塊

若要使用對話方塊,請安裝 Microsoft.Bot.Builder.Dialogs NuGet 套件。

Bot 會透過 UserProfileDialog 與使用者互動。 建立 Bot 的 DialogBot 類別時,會 UserProfileDialog 設定為其主要對話方塊。 接著,Bot 會使用 Run 協助程式方法來存取對話方塊。

Class diagram for the C# sample.

Dialogs\UserProfileDialog.cs

首先,建立 UserProfileDialog 衍生自 類別的 ComponentDialog ,並具有七個步驟。

在建構函式中 UserProfileDialog ,建立瀑布步驟、提示和瀑布對話,並將其新增至對話集。 提示必須位於所使用的相同對話方塊集中。

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

接下來,新增對話方塊用來提示輸入的步驟。 若要使用提示,請從對話方塊中的步驟呼叫它,並使用 stepContext.Result 擷取下列步驟中的提示結果。 在幕後,提示是雙步驟對話方塊。 首先,提示會要求輸入。 然後,它會傳回有效的值,或從開頭以重新編目開始,直到它收到有效的輸入為止。

您應該一律從瀑布式步驟傳回非 Null DialogTurnResult 。 如果您未這麼做,您的對話方塊可能無法如設計般運作。 如下所示是瀑布式對話方塊中 的 NameStepAsync 實作。

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 ,指定使用者輸入無法驗證時的重試提示,可能是因為提示的格式無法剖析,否則輸入會失敗驗證準則。 在此情況下,如果未提供重試提示,提示會使用初始提示文字來重新提示使用者輸入。

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.cs

使用者的運輸、名稱和年齡模式會儲存在 類別的實例中 UserProfile

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.cs

在最後一個步驟中,檢查 stepContext.Result 上一個瀑布步驟中呼叫的對話方塊所傳回的 。 如果傳回值為 true,使用者設定檔存取子會取得並更新使用者設定檔。 若要取得使用者設定檔,請呼叫 GetAsync ,然後設定 、 userProfile.NameuserProfile.AgeuserProfile.Picture 屬性的值 userProfile.Transport 。 最後,在呼叫 EndDialogAsync 之前,摘要說明使用者的資訊,這會結束對話。 結束對話方塊會將對話方塊從對話堆疊中快顯出來,並將選擇性結果傳回給對話方塊的父代。 父代是啟動剛結束之對話方塊的對話方塊或方法。

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

執行對話方塊

Bots\DialogBot.cs

處理常式 OnMessageActivityAsync 會使用 RunAsync 方法來啟動或繼續對話。 OnTurnAsync 會使用 Bot 的狀態管理物件來保存儲存體的任何狀態變更。 方法 ActivityHandler.OnTurnAsync 會呼叫各種活動處理常式方法,例如 OnMessageActivityAsync 。 如此一來,狀態就會儲存在訊息處理常式完成之後,但在回合本身完成之前。

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 的服務

此 Bot 會使用下列服務:

  • Bot 的基本服務:認證提供者、配接器和 Bot 實作。
  • 用於管理狀態的服務:儲存體、使用者狀態和交談狀態。
  • Bot 將使用的對話方塊。

Startup.cs

在 中 Startup 註冊 Bot 的服務。 這些服務可透過相依性插入,供程式碼的其他部分使用。

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

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

注意

記憶體儲存體僅供測試之用,不適用於生產環境。 請務必針對生產 Bot 使用持續性類型的儲存體。

測試您的機器人

  1. 如果您尚未這麼做,請安裝 Bot Framework 模擬器
  2. 在本機電腦上執行範例。
  3. 啟動模擬器、連線至 Bot,並傳送訊息,如下所示。

An example transcript of a conversation with the multi-turn prompt bot.

其他資訊

關於對話方塊和 Bot 狀態

在此 Bot 中,會定義兩個狀態屬性存取子:

  • 在對話狀態屬性的交談狀態內建立的一個。 對話方塊狀態會追蹤使用者位於對話集對話內的位置,而且對話內容會更新它,例如呼叫開始對話或 繼續對話 方法時
  • 在使用者設定檔屬性的使用者狀態內建立的一個。 Bot 會使用此專案來追蹤使用者的相關資訊,而且您必須在對話程式碼中明確管理此狀態。

狀態 屬性存取子的 get 和 set 方法會 取得,並在狀態管理物件的快取中設定 屬性的值。 快取會在第一次輪要求狀態屬性的值時填入,但必須明確保存。 為了保存這兩個狀態屬性的變更,會執行對應狀態管理物件的儲存變更 方法呼叫

此範例會從對話方塊內更新使用者設定檔狀態。 這種做法適用于某些 Bot,但如果您想要跨 Bot 重複使用對話,則無法運作。

有各種選項可將對話步驟和 Bot 狀態分開。 例如,一旦對話方塊收集完整的資訊,您就可以:

  • 使用結束對話 方法,將收集的資料當做傳回值傳回父內容。 這可以是 Bot 的回合處理常式或對話方塊堆疊上先前的作用中對話方塊,以及提示類別的設計方式。
  • 產生對適當服務的要求。 如果您的 Bot 做為較大型服務的前端,這可能會正常運作。

提示驗證程式方法的定義

UserProfileDialog.cs

以下是方法定義的驗證程式程式碼範例 AgePromptValidatorAsyncpromptContext.Recognized.Value 包含剖析的值,這是數位提示的整數。 promptContext.Recognized.Succeeded 指出提示是否能夠剖析使用者的輸入。 驗證程式應該傳回 false,表示未接受該值,而且提示對話方塊應該重新提示使用者;否則,傳回 true 以接受輸入,並從提示對話方塊傳回。 您可以根據您的案例變更驗證程式中的值。

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

下一步