ブランチとループを使用して高度な会話フローを作成する

この記事の対象: SDK v4

ダイアログ ライブラリを使用して、複雑な会話フローを作成できます。 この記事では、分岐およびループする複雑な会話を管理する方法と、ダイアログのさまざまな部分の間で引数を渡す方法について説明します。

Note

Bot Framework JavaScript SDK、C#、Python SDK は引き続きサポートされますが、Java SDK については、最終的な長期サポートは 2023 年 11 月に終了する予定です。 このリポジトリ内の重要なセキュリティとバグの修正のみが行われます。

Java SDK を使用して構築された既存のボットは引き続き機能します。

新しいボットの構築については、Power Virtual Agents の使用を検討し、適切なチャットボット ソリューションの選択についてお読みください。

詳細については、「The future of bot building」をご覧ください。

前提条件

このサンプルについて

このボットのサンプルでは、ユーザーがサインアップし、一覧から最大 2 つの会社をレビューできます。 ボットは 3 つのコンポーネント ダイアログを使用して、会話フローを管理します。 各コンポーネント ダイアログには、ウォーターフォール ダイアログと、ユーザー入力を収集するために必要なプロンプトが含まれています。 これらのダイアログについては、以下のセクションで詳しく説明します。 会話状態を使用してダイアログを管理し、ユーザー状態を使用してユーザーおよびユーザーがレビューする会社に関する情報を保存します。

ボットはアクティビティ ハンドラーから派生します。 多くのサンプル ボットと同様に、ユーザーに歓迎の意を示し、ダイアログを使用してユーザーからのメッセージを処理し、ターンが終わる前にユーザーと会話状態を保存します。

ダイアログを使用するには、Microsoft.Bot.Builder.Dialogs NuGet パッケージをインストールします。

Class diagram for C# sample.

ユーザー プロファイルを定義する

ユーザー プロファイルには、ダイアログによって収集された情報、ユーザーの名前、年齢、およびレビュー対象として選択された会社が含まれます。

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 つのダイアログが含まれています。

  • メイン ダイアログでは、プロセス全体が開始され、収集された情報が要約されます。
  • 最上位レベルのダイアログでは、ユーザー情報が収集され、ユーザーの年齢に基づく分岐ロジックが含まれます。
  • review-selection ダイアログでは、ユーザーはレビューする会社を繰り返し選択できます。 ループ ロジックを使用してこれを行います。

メイン ダイアログ

メイン ダイアログには、次の 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. ユーザーの年齢に基づいて、review-selection ダイアログを開始するか、次のステップに進みます。
  4. 最後に、ユーザーが参加してくれたことに対してお礼を述べ、収集した情報を返します。

最初のステップでは、ダイアログ状態の一部として空のユーザー プロファイルが作成されます。 ダイアログは空のプロファイルで開始され、その進行とともにプロファイルに情報が追加されます。 終了すると、最後のステップで、収集された情報が返されます。

3 番目 (選択の開始) のステップでは、ユーザーの年齢に基づいて会話フローが分岐します。

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 ダイアログには 2 つのステップがあります。

  1. ユーザーに対して、レビューする会社を選択するか、done を選択して完了するよう求めます。
    • ダイアログが初期情報を使用して開始された場合、その情報はウォーターフォール ステップ コンテキストの options プロパティを通じて使用可能になります。 review-selection ダイアログは、自動的に再起動することができ、これを使用して、レビューする会社をユーザーが複数選択できるようにします。
    • レビューする会社をユーザーが既に選択している場合、その会社は使用可能な選択肢から削除されます。
    • ユーザーがループを早期に終了できるように 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);
    }
}

ダイアログを実行する

dialog bot クラスは、アクティビティ ハンドラーを拡張します。また、ダイアログを実行するためのロジックを含んでいます。 dialog and welcome bot クラスは、ダイアログ ボットを拡張し、ユーザーが会話に参加したときに歓迎の意も示します。

ボットのターン ハンドラーによって、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>();

Note

メモリ ストレージはテストにのみ使用され、実稼働を目的としたものではありません。 運用環境のボットでは、必ず永続タイプのストレージを使用してください。

ボットのテスト

  1. Bot Framework Emulator をインストールします (まだインストールしていない場合)。

  2. ご自身のマシンを使ってローカルでサンプルを実行します。

  3. 以下に示すように、エミュレーターを起動し、お使いのボットに接続して、メッセージを送信します。

    Example transcript from a conversation with the complex dialog bot.

その他のリソース

ダイアログの実装方法の概要については、連続して行われる会話フローの実装をご覧ください。この記事では、1 つのウォーターフォール ダイアログと、いくつかのプロンプトを使用して、ユーザーに一連の質問を作成します。

ダイアログ ライブラリには、プロンプト用の基本的な検証が含まれます。 カスタム検証を追加することもできます。 詳細については、ダイアログ プロンプトを使用したユーザー入力の収集に関するページをご覧ください。

ご自身のダイアログ コードを簡素化し、複数のボットで再利用するために、ダイアログ セットの一部を別のクラスとして定義できます。 詳細については、「ダイアログの再利用」を参照してください。

次のステップ