Реализация процесса общения

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

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

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

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

Примечание.

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

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

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

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

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

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

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

Шаги Тип запроса
Запрос к пользователю о режиме транспортировки Запрос выбора
Запрос имени пользователя Запрос текста
Запрос к пользователю, готов ли он указать свой возраст Запрос подтверждения
Если они ответили да, попросите их возраст Числовая строка с проверкой только для принятия возрастов, превышающих 0 и менее 150
Если пользователь не использует Microsoft Teams, запросите изображение профиля Запрос на вложение с проверкой, чтобы разрешить отсутствующие вложения
Спросите, является ли собранная информация "ок" Повторный запрос подтверждения

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

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

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

Бот взаимодействует с пользователем через UserProfileDialog. При создании класса UserProfileDialog бота DialogBot устанавливается в качестве основного диалогового окна. Затем бот применяет вспомогательный метод Run для доступа к этому диалогу.

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

Dialogs\UserProfileDialog.cs

Начните с создания, наследуемого UserProfileDialogComponentDialog от класса, и имеет семь шагов.

В конструкторе 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,
        SummaryStepAsync,
        ConfirmStepAsync,
    };

    // 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. Запросы данных, по сути, являются диалогами из двух этапов. Во-первых, запрос запрашивает входные данные. Затем он возвращает допустимое значение или начинается с начала с повторного выпуска, пока он не получит допустимые входные данные.

Из каскадного шага следует всегда возвращать ненулевое значение 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.AgeuserProfile.TransportuserProfile.Nameсвойств и userProfile.Picture свойств. Наконец, сводные сведения для пользователя перед вызовом EndDialogAsync, который заканчивает диалоговое окно. Завершенный диалог удаляется из стека диалогов, а его результат (если есть) возвращается в родительский диалог. Родительским считается диалог или метод, в котором был запущен только что завершившийся диалог.

    else
    {
        msg += $" Your profile will not be kept.";
    }

    await stepContext.Context.SendActivityAsync(MessageFactory.Text(msg), 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);
}

private async Task<DialogTurnResult> SummaryStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    stepContext.Values["picture"] = ((IList<Attachment>)stepContext.Result)?.FirstOrDefault();

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

Запуск диалога

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

Примечание.

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

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

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

Пример расшифровки беседы с ботом запроса с несколькими поворотами.

Дополнительная информация:

Сведения о диалоге и состоянии бота

В этом боте определены два метода доступа к свойству состояния:

  • Один из них создается в состоянии беседы для свойства состояния диалога. Состояние диалогового окна отслеживает, где пользователь находится в диалоговых окнах набора диалогов и обновляется контекстом диалога, например при вызове методов запуска илипродолжения диалога .
  • Второй создается в состоянии пользователя для свойства профиля пользователя. Бот использует это для отслеживания информации о пользователе, и необходимо явно управлять этим состоянием в коде диалога.

Методы доступа get и set для свойства состояния позволяют получить и сохранить значение этого свойства в кэше объекта управления состоянием. Кэш заполняется автоматически при первом обращении к значению свойства состояния в течение шага, но сохранять его нужно явным образом. Чтобы сохранить изменения обоих этих свойств состояния, выполняется вызов метода сохранения изменений соответствующего объекта управления состоянием.

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

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

  • Выполните метод end dialog, чтобы передать собранные данные в возвращаемом значении обратно в контекст родительского элемента. Это может быть обработчик поворота бота или более раннее активное диалоговое окно в стеке диалогов, и это способ разработки классов запросов.
  • Создайте запрос к соответствующей службе. Это может быть хорошим вариантом, если бот выступает в роли интерфейса для более крупной службы.

Определение метода для проверяющего элемента управления запроса

UserProfileDialog.cs

Ниже приведен пример кода проверяющего элемента для AgePromptValidatorAsync определения метода. promptContext.Recognized.Value содержит анализируемое значение (в нашем примере это целое число из запроса числа). promptContext.Recognized.Succeeded указывает, удалось ли в запросе выполнить синтаксический анализ введенных пользователем данных. Проверяющий элемент должен возвращать значение false, чтобы указать, что значение не было принято, и диалоговое окно запроса должно повторно проимышировать пользователя; в противном случае верните значение true, чтобы принять входные данные и вернуться из диалогового окна запроса. Значение в проверяемом элементе можно изменить в каждом сценарии.

    }

    // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
    return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = MessageFactory.Text("Is this ok?") }, cancellationToken);
}

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