Создание сложной последовательной беседы с использованием ветвей и циклов

ОБЛАСТЬ ПРИМЕНЕНИЯ: ПАКЕТ SDK версии 4

С помощью библиотеки диалогов можно создавать сложные потоки беседы. В этой статье объясняется, как управлять сложными беседами с ответвлениями и циклами, а также как передавать аргументы между разными частями диалога.

Примечание.

Пакеты SDK для JavaScript, C# и Python для Bot Framework по-прежнему будут поддерживаться, однако пакет SDK java отменяется с окончательной долгосрочной поддержкой, заканчивающейся в ноябре 2023 года.

Существующие боты, созданные с помощью пакета SDK для Java, будут продолжать функционировать.

Для создания нового бота рекомендуется использовать Power Virtual Agent и ознакомиться с выбором подходящего решения чат-бота.

Дополнительные сведения см. в статье "Будущее создания бота".

Необходимые компоненты

Об этом примере

Этот пример содержит бот, который может выполнять регистрацию пользователей для оценки одной или двух компаний из заданного списка. Бот использует три диалоговых окна компонента для управления потоком беседы. Каждый диалог-компонент содержит каскадный диалог и все запросы, необходимые для сбора данных, вводимых пользователем. Эти диалоги подробно описаны в следующих разделах. Бот использует состояние беседы для управления диалогами и состояние пользователя для сохранения сведений о нем и тех компаниях, которые пользователь хочет оценить.

Бот разработан на основе обработчика действий. Как и во многих примерах ботов, этот бот отправляет пользователю приветствие, использует диалоги для обработки сообщений от пользователя, а также сохраняет состояние пользователя и диалога до окончания реплики.

Чтобы использовать диалоги, установите пакет NuGet Microsoft.Bot.Builder.Dialogs.

Пример схемы классов для C#.

Определение профиля пользователя

Профиль пользователя будет содержать такие собранные в диалогах сведения, как имя пользователя, возраст и выбранные для оценки компании.

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

Создание диалогов

Этот бот содержит три диалоговых окна:

  • В основном диалоге запускается общий процесс общения и создается сводка собранных сведений.
  • В диалоге верхнего уровня происходит сбор сведений пользователя. Здесь включена логика ветвления на основе возраста пользователя.
  • В диалоге оценки и выбора пользователь может последовательно выбрать компании для оценки. Для этого используется логика цикла.

Основной диалог

Основное диалоговое окно состоит из двух шагов:

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

Диалог верхнего уровня

Диалоговое окно верхнего уровня состоит из четырех шагов:

  1. запрос имени пользователя;
  2. запрос возраста пользователя;
  3. Запуск диалога оценки и выбора или переход к следующему шагу в зависимости от возраста пользователя.
  4. отправка пользователю благодарности за участие и возвращение собранных сведений.

На первом шаге в рамках сохранения состояния диалога создается пустой профиль пользователя. В начале диалога создается пустой профиль, который заполняется в ходе ведения беседы. В конце диалога возвращаются собранные сведения.

На третьем шаге (начало выбора) в потоке беседы создается ветвь на основе возраста пользователя.

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

Диалог выбора элементов для обзора

Диалог выбора элементов для обзора состоит из двух шагов:

  1. Предложение пользователю выбрать компанию для оценки или ввести done, чтобы завершить процесс.
    • Если в начале диалога были известны какие-либо сведения, их можно получить с помощью свойства options контекста каскадного шага. Диалог оценки и выбора поддерживает автоматический перезапуск. Благодаря этому пользователь может выбрать несколько компаний для оценки.
    • Если пользователь уже выбрал компанию для оценки, ее название удаляется из доступных вариантов.
    • Вариант done добавлен, чтобы пользователь мог завершить цикл на раннем этапе.
  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 позволяет расширить возможности чат-бота, а также добавить приветствие пользователя, когда он присоединяется к беседе.

Обработчик поворота бота повторяет поток беседы, определенный тремя диалогами. При получении сообщения от пользователя происходит следующее:

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

Примечание.

Хранилище памяти используется только для тестирования и не предназначено для использования в рабочей среде. Для ботов в рабочей среде обязательно используйте постоянное хранилище любого типа.

Тестирование бота

  1. Если это еще не сделано, установите эмулятор Bot Framework.

  2. Выполните этот пример на локальном компьютере.

  3. Запустите эмулятор, подключитесь к боту и отправьте несколько сообщений, как показано ниже.

    Пример расшифровки беседы со сложным диалоговым ботом.

Дополнительные ресурсы

Общие сведения о реализации диалогового окна см . в разделе "Реализация последовательного потока беседы", который использует один каскадный диалог и несколько запросов, чтобы задать пользователю ряд вопросов.

Библиотека диалогов выполняет простую проверку запросов. Вы также можете добавить любую собственную проверку. Дополнительные сведения о сборе данных от пользователя с помощью запросов диалога см. в этой статье.

Чтобы упростить код диалога и использовать его повторно в нескольких ботах, следует определить компоненты набора диалогов в отдельном классе. Подробнее об этом см. в статье о повторном использовании диалогов.

Следующие шаги