連続して行われる会話フローの実装

適用対象: SDK v4

質問を投稿して情報を収集することは、ボットがユーザーとやり取りする主な手段の 1 つです。 ダイアログ ライブラリでは、質問を行いやすくなるだけでなく、応答が検証され、確実に特定のデータ型と一致するように、またはカスタム検証ルールを満たすように、prompt クラスなどの便利な組み込み機能が提供されます。

ダイアログ ライブラリを使用して、線形および複雑な会話フローを管理できます。 線形対話では、ボットは固定の一連のステップを実行し、会話が終了します。 ダイアログは、ボットがユーザーから情報を収集する必要がある場合に便利です。

この記事では、プロンプトを作成し、ウォーターフォール ダイアログから呼び出すことによって、線形会話フローを実装する方法について説明します。 ダイアログ ライブラリを使用することなく、独自のプロンプトを作成する方法の例については、「ユーザー入力を収集するために独自のプロンプトを作成する」の記事を参照してください。

Note

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

これらの SDK で構築された既存のボットは引き続き機能します。

新しいボット構築の場合は、 Power Virtual Agents の 使用を検討し、 適切なチャットボット ソリューションの選択に関する記事を参照してください。

詳細については、「 ボット構築の未来」を参照してください。

前提条件

このサンプルについて

マルチターン プロンプト サンプルでは、ウォーターフォール ダイアログ、いくつかのプロンプト、コンポーネント ダイアログを使用して、ユーザーに一連の質問をする線形対話を作成します。 コードはダイアログを使用して、これらの手順を順番に切り替えます。

手順 プロンプトの種類
移動手段をユーザーに聞きます 選択プロンプト
名前をユーザーに聞きます テキスト プロンプト
年齢を指定するかどうかをユーザーに聞きます 確認プロンプト
「はい」と答えた場合は、年齢を尋ねる 0 より大きい年齢と 150 未満の年齢のみを受け入れるための検証を含む数値プロンプト
Microsoft Teams を使用していない場合は、プロファイル画像を依頼します 添付ファイルの不足を許可するための検証を含む添付ファイル プロンプト
収集された情報が "OK" かどうかを確認する 再利用確認プロンプト

最後に、彼らが「はい」と答えた場合は、収集された情報を表示します。それ以外の場合は、情報を保持しないことをユーザーに伝えます。

メイン ダイアログを作成する

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

ボットは UserProfileDialog を介してユーザーと対話します。 ボットDialogBotのクラスを作成すると、 UserProfileDialog が メイン ダイアログとして設定されます。 その後、ボットは Run ヘルパー メソッドを使用して、ダイアログにアクセスします。

C# サンプルのクラス図。

Dialogs\UserProfileDialog.cs

まず、 クラスから派生し UserProfileDialog 、7 つのステップを持つ を 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 を使用して、次のステップでプロンプトの結果を取得します。 バックグラウンドでは、プロンプトは 2 つのステップから成るダイアログです。 最初に、入力を求めるプロンプトが表示されます。 次に、有効な値を返すか、有効な入力を受け取るまで、最初から reprompt でやり直します。

ウォーターフォール ステップからは常に 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.PictureuserProfile.TransportuserProfile.NameuserProfile.Age設定します。 最後に、 を呼び出す 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 では、ボットの状態管理オブジェクトを使用して、状態の変更をストレージに保持します。 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);
}

ボット用のサービスを登録する

このボットでは、次のサービスが使用されます。

  • ボット用の基本サービス: 資格情報プロバイダー、アダプター、およびボット実装。
  • 状態を管理するためのサービス: ストレージ、ユーザー状態、および会話の状態。
  • ボットで使用されるダイアログ。

Startup.cs

でボット Startupのサービスを登録します。 これらのサービスは、依存関係の挿入を通じてコードの他の部分で使用できます。

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

Note

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

ボットをテストする

  1. まだインストールしていない場合は、Bot Framework Emulatorをインストールします。
  2. ご自身のマシンを使ってローカルでサンプルを実行します。
  3. 以下に示すように、エミュレーターを起動し、お使いのボットに接続して、メッセージを送信します。

マルチターン プロンプト ボットを使用した会話のトランスクリプトの例。

関連情報

ダイアログとボットの状態について

このボットでは、次の 2 つの状態プロパティ アクセサーが定義されています。

  • 1 つはダイアログ状態プロパティ用の会話状態内で作成されます。 ダイアログの状態は、ユーザーがダイアログ セットのダイアログ内の場所を追跡し、ダイアログの開始やダイアログの続行メソッドが呼び出されたときなど、ダイアログ コンテキストによって更新されます。
  • もう 1 つはユーザー プロファイル プロパティ用のユーザー状態内で作成されます。 ボットはこれを使用してユーザーに関する情報を追跡し、ダイアログ コードでこの状態を明示的に管理する必要があります。

状態管理オブジェクトのキャッシュのプロパティ値は、状態プロパティ アクセサーの get メソッドおよび set メソッドによって取得および設定されます。 キャッシュはターンで状態プロパティの値が最初に要求されたときに設定されますが、明示的に保持する必要があります。 これらの両方の状態プロパティに対する変更を保持するために、対応する状態管理オブジェクトの save changes メソッドの呼び出しが実行されます。

このサンプルでは、ダイアログ内からユーザー プロファイルの状態が更新されます。 この方法は一部のボットでは機能しますが、ボット間でダイアログを再利用する場合は機能しません。

ダイアログ ステップとボットの状態を別々に維持するオプションには、さまざまな種類があります。 たとえば、お使いのダイアログで完全な情報を収集すると、以下を行うことができます。

  • end dialog メソッドを使用して、収集されたデータを戻り値として親コンテキストに返します。 これは、ボットのターン ハンドラーまたはダイアログ スタック上の以前のアクティブなダイアログであり、プロンプト クラスの設計方法です。
  • 適切なサービスへの要求を生成する。 お使いのボットが大規模なサービスへのフロントエンドとして動作している場合にうまく機能することがあります。

プロンプト検証コントロールのメソッドの定義

UserProfileDialog.cs

メソッド定義の検証コード例を次に AgePromptValidatorAsync 示します。 promptContext.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);
}

次のステップ