ユーザー入力を収集するために独自のプロンプトを作成する

適用対象: SDK v4

多くの場合、ボットとユーザー間の会話では、ユーザーに情報の入力を求め、ユーザーの応答を解析し、その情報に基づいてアクションを実行する必要があります。 お使いのボットは会話のコンテキストを追跡する必要があります。これにより、自身の動作を管理し、以前の質問に対する回答を記憶することができます。 ボットの "状態" は、受信メッセージに適切に応答するためにボットが追跡する情報です。

ヒント

ダイアログ ライブラリには、ユーザーが使用できるより多くの機能を提供する組み込みのプロンプトが用意されています。 これらのプロンプトの例については、「連続して行われる会話フローの実装」を参照してください。

注意

Bot Framework Python と Java SDK は、2023 年 11 月に終了する最終的な長期サポートで廃止される予定です。 このリポジトリ内の重要なセキュリティとバグの修正のみが行われます。 これらの SDK で構築された既存のボットは引き続き機能します。

新しいボット開発の場合は、 Power Virtual Agents の使用を検討してください。 詳細については、「 ボット構築の未来」を参照してください。

前提条件

サンプル コードについて

サンプル ボットでは、ユーザーに対して一連の質問を行い、その回答の一部を検証して、入力を保存します。 次の図は、ボット、ユーザー プロファイル、および会話フロー クラスの間の関係を示しています。

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

  • ボットによって収集されるユーザー情報を表す UserProfile クラス。
  • ユーザー情報を収集しているときに、会話状態を制御する ConversationFlow クラス。
  • 会話内のどこにいるかを追跡するための内部 ConversationFlow.Question 列挙。

ユーザーの状態はユーザーの名前、年齢、選択した日付を追跡し、会話の状態はユーザーに最後に尋ねた内容を追跡します。 このボットをデプロイする予定がないため、 メモリ ストレージを使用するようにユーザーと会話の状態を構成します。

ボットのメッセージ ターン ハンドラーとユーザーと会話の状態プロパティを使用して、会話のフローと入力のコレクションを管理します。 ボットでは、メッセージ ターン ハンドラーの各イテレーション中に受信した状態プロパティ情報を記録します。

会話およびユーザー オブジェクトを作成する

ユーザーおよび会話状態オブジェクトをスタートアップ時に作成し、ボット コンストラクターで依存関係挿入によりそれらを使用します。

Startup.cs

// 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.
services.AddSingleton<UserState>();

// Create the Conversation state.
services.AddSingleton<ConversationState>();

Bots/CustomPromptBot.cs

private readonly BotState _userState;
private readonly BotState _conversationState;

public CustomPromptBot(ConversationState conversationState, UserState userState)
{
    _conversationState = conversationState;
    _userState = userState;
}

プロパティ アクセサーを作成する

ユーザー プロファイルと会話フローのプロパティのプロパティ アクセサーを作成し、GetAsync を呼び出して、状態からプロパティ値を取得します。

Bots/CustomPromptBot.cs

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    var conversationStateAccessors = _conversationState.CreateProperty<ConversationFlow>(nameof(ConversationFlow));
    var flow = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationFlow(), cancellationToken);

    var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
    var profile = await userStateAccessors.GetAsync(turnContext, () => new UserProfile(), cancellationToken);

ターンを終了する前に、SaveChangesAsync を呼び出して、状態の変更をストレージに書き込みます。

    await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
    await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
}

メッセージ ターン ハンドラー

メッセージ アクティビティを処理する場合、メッセージ ハンドラーではヘルパー メソッドを使用して会話を管理し、ユーザーにメッセージを表示します。 ヘルパー メソッドについては、次のセクションで説明します。

Bots/CustomPromptBot.cs

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    var conversationStateAccessors = _conversationState.CreateProperty<ConversationFlow>(nameof(ConversationFlow));
    var flow = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationFlow(), cancellationToken);

    var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
    var profile = await userStateAccessors.GetAsync(turnContext, () => new UserProfile(), cancellationToken);

    await FillOutUserProfileAsync(flow, profile, turnContext, cancellationToken);

    // Save changes.
    await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
    await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
}

ユーザー プロファイルを入力する

ボットは、ボットが前のターンで尋ねた質問 (存在する場合) に基づいて、ユーザーに情報の入力を求めます。 入力は検証メソッドを使用して解析されます。

各検証メソッドは、次のような同様の設計に従います。

  • 戻り値は、入力が、この質問に対して有効な回答であるかどうか示しています。
  • 検証に合格すると、解析および正規化された値が生成され、保存されます。
  • 検証に失敗した場合はメッセージが生成され、ボットはこれを使用して情報を再度求めることができます。

検証メソッドについては、以下のセクションで説明します。

Bots/CustomPromptBot.cs

{
    var input = turnContext.Activity.Text?.Trim();
    string message;

    switch (flow.LastQuestionAsked)
    {
        case ConversationFlow.Question.None:
            await turnContext.SendActivityAsync("Let's get started. What is your name?", null, null, cancellationToken);
            flow.LastQuestionAsked = ConversationFlow.Question.Name;
            break;
        case ConversationFlow.Question.Name:
            if (ValidateName(input, out var name, out message))
            {
                profile.Name = name;
                await turnContext.SendActivityAsync($"Hi {profile.Name}.", null, null, cancellationToken);
                await turnContext.SendActivityAsync("How old are you?", null, null, cancellationToken);
                flow.LastQuestionAsked = ConversationFlow.Question.Age;
                break;
            }
            else
            {
                await turnContext.SendActivityAsync(message ?? "I'm sorry, I didn't understand that.", null, null, cancellationToken);
                break;
            }

        case ConversationFlow.Question.Age:
            if (ValidateAge(input, out var age, out message))
            {
                profile.Age = age;
                await turnContext.SendActivityAsync($"I have your age as {profile.Age}.", null, null, cancellationToken);
                await turnContext.SendActivityAsync("When is your flight?", null, null, cancellationToken);
                flow.LastQuestionAsked = ConversationFlow.Question.Date;
                break;
            }
            else
            {
                await turnContext.SendActivityAsync(message ?? "I'm sorry, I didn't understand that.", null, null, cancellationToken);
                break;
            }

        case ConversationFlow.Question.Date:
            if (ValidateDate(input, out var date, out message))
            {
                profile.Date = date;
                await turnContext.SendActivityAsync($"Your cab ride to the airport is scheduled for {profile.Date}.");
                await turnContext.SendActivityAsync($"Thanks for completing the booking {profile.Name}.");
                await turnContext.SendActivityAsync($"Type anything to run the bot again.");
                flow.LastQuestionAsked = ConversationFlow.Question.None;
                profile = new UserProfile();
                break;
            }
            else
            {
                await turnContext.SendActivityAsync(message ?? "I'm sorry, I didn't understand that.", null, null, cancellationToken);
                break;
            }
    }
}

入力を解析して検証する

ボットでは、次の条件を使用して入力が検証されます。

  • name は、空でない文字列にする必要があります。 空白文字を削除することで正規化されます。
  • age は、18 から 120 の値にする必要があります。 整数を返すことで正規化されます。
  • date は、1 時間以上未来の日付または時刻にする必要があります。 解析された入力の日付部分のみを返すことで正規化されます。

Note

年齢と日付の入力では、サンプルでは Microsoft/Recognizers-Text ライブラリを使用して初期解析を実行します。 これは、入力を解析する 1 つの方法にすぎません。 これらのライブラリの詳細については、プロジェクトの README を参照してください。

Bots/CustomPromptBot.cs

private static bool ValidateName(string input, out string name, out string message)
{
    name = null;
    message = null;

    if (string.IsNullOrWhiteSpace(input))
    {
        message = "Please enter a name that contains at least one character.";
    }
    else
    {
        name = input.Trim();
    }

    return message is null;
}

private static bool ValidateAge(string input, out int age, out string message)
{
    age = 0;
    message = null;

    // Try to recognize the input as a number. This works for responses such as "twelve" as well as "12".
    try
    {
        // Attempt to convert the Recognizer result to an integer. This works for "a dozen", "twelve", "12", and so on.
        // The recognizer returns a list of potential recognition results, if any.

        var results = NumberRecognizer.RecognizeNumber(input, Culture.English);

        foreach (var result in results)
        {
            // The result resolution is a dictionary, where the "value" entry contains the processed string.
            if (result.Resolution.TryGetValue("value", out var value))
            {
                age = Convert.ToInt32(value);
                if (age >= 18 && age <= 120)
                {
                    return true;
                }
            }
        }

        message = "Please enter an age between 18 and 120.";
    }
    catch
    {
        message = "I'm sorry, I could not interpret that as an age. Please enter an age between 18 and 120.";
    }

    return message is null;
}

private static bool ValidateDate(string input, out string date, out string message)
{
    date = null;
    message = null;

    // Try to recognize the input as a date-time. This works for responses such as "11/14/2018", "9pm", "tomorrow", "Sunday at 5pm", and so on.
    // The recognizer returns a list of potential recognition results, if any.
    try
    {
        var results = DateTimeRecognizer.RecognizeDateTime(input, Culture.English);

        // Check whether any of the recognized date-times are appropriate,
        // and if so, return the first appropriate date-time. We're checking for a value at least an hour in the future.
        var earliest = DateTime.Now.AddHours(1.0);

        foreach (var result in results)
        {
            // The result resolution is a dictionary, where the "values" entry contains the processed input.
            var resolutions = result.Resolution["values"] as List<Dictionary<string, string>>;

            foreach (var resolution in resolutions)
            {
                // The processed input contains a "value" entry if it is a date-time value, or "start" and
                // "end" entries if it is a date-time range.
                if (resolution.TryGetValue("value", out var dateString)
                    || resolution.TryGetValue("start", out dateString))
                {
                    if (DateTime.TryParse(dateString, out var candidate)
                        && earliest < candidate)
                    {
                        date = candidate.ToShortDateString();
                        return true;
                    }
                }
            }
        }

        message = "I'm sorry, please enter a date at least an hour out.";
    }
    catch
    {
        message = "I'm sorry, I could not interpret that as an appropriate date. Please enter a date at least an hour out.";
    }

    return false;
}

ボットをローカルでテストする

ボットをローカルでテストするための Bot Framework Emulator をダウンロードし、インス―ルします。

  1. ご自身のマシンを使ってローカルでサンプルを実行します。 手順が必要な場合は、C# サンプルJS サンプル、または Python サンプルの ファイルを参照してくださいREADME
  2. エミュレーターを使ってテストします。

その他のリソース

ダイアログ ライブラリには、会話の管理に関するさまざまな側面を自動化するクラスが用意されています。

次のステップ