Поделиться через


Сохранение данных пользователя и диалога

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

Бот по своей природе не имеет состояния. Развернутый бот не обязан выполнять следующий шаг в том же процессе или на том же компьютере, что и предыдущий. Но иногда боту нужно отслеживать контекст беседы, чтобы управлять ее ходом и запоминать ответы на предыдущие вопросы. Возможности состояния и хранения, предоставляемые библиотекой SDK Bot Framework, позволяют добавить состояние в вашего бота. Боты используют объекты управления состоянием и хранилища для управления и сохранения состояния. Диспетчер состояний предоставляет уровень абстракции, позволяющий получить доступ к свойствам состояния через аксессоры свойств, независимо от типа базового хранилища.

Примечание.

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

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

Для создания нового бота рекомендуется использовать Microsoft Copilot Studio и ознакомиться с выбором подходящего решения для соведущего.

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

Предварительные условия

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

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

Определение классов

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

  • В UserProfile.cs вы определяете UserProfile класс для сведений о пользователе, собираемых ботом.
  • В ConversationData.cs вы определяете ConversationData класс для управления состоянием беседы при сборе сведений о пользователе.

В приведенных ниже примерах кода показаны определения классов UserProfile и ConversationData.

UserProfile.cs

public class UserProfile
{
    public string Name { get; set; }
}

ConversationData.cs

public class ConversationData
{
    // The time-stamp of the most recent incoming message.
    public string Timestamp { get; set; }

    // The ID of the user's channel.
    public string ChannelId { get; set; }

    // Track whether we have already asked the user's name
    public bool PromptedUserForName { get; set; } = false;
}

Создайте объекты состояния беседы и пользователя

Затем вы регистрируете MemoryStorage , которое используется для создания UserState и ConversationState объектов. Объекты состояния пользователя и беседы создаются в Startup, а зависимости вводятся в конструктор бота. Также для бота регистрируются дополнительные службы: поставщик учетных данных, адаптер и реализация бота.

Startup.cs

// {
//     TypeNameHandling = TypeNameHandling.All,
// var storage = new BlobsStorage("<blob-storage-connection-string>", "bot-state");

// With a custom JSON SERIALIZER, use this instead.
// var storage = new BlobsStorage("<blob-storage-connection-string>", "bot-state", jsonSerializer);

/* END AZURE BLOB STORAGE */

Bots/StateManagementBot.cs

private BotState _conversationState;
private BotState _userState;

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

Добавление методов доступа к свойству состояния

Теперь вы создаете акцессоры свойств с помощью метода CreateProperty, который предоставляет дескриптор объекту BotState. Каждый метод доступа к свойству состояния позволяет получить или задать значение для соответствующего свойства состояния. Перед использованием свойств состояния используйте каждый метод доступа, чтобы загрузить свойство из хранилища и извлечь его из кэша состояния. Вызовите метод GetAsync, чтобы получить ключ с правильно определенной областью действия, связанный со свойством состояния.

Bots/StateManagementBot.cs

var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));

Доступ к состоянию бота

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

  • Если userProfile.Name пусто и conversationData.PromptedUserForName равно true, вы извлекаете указанное имя пользователя и сохраняете его в состоянии пользователя.
  • Если userProfile.Name он пуст и conversationData.PromptedUserForName имеет значение false, необходимо запросить имя пользователя.
  • Если userProfile.Name был ранее сохранен, получите время сообщения и идентификатор канала из входных данных пользователя, верните все данные пользователю и сохраните полученные данные в состоянии беседы.

Bots/StateManagementBot.cs

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    // Get the state properties from the turn context.

    var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
    var conversationData = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationData());

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

    if (string.IsNullOrEmpty(userProfile.Name))
    {
        // First time around this is set to false, so we will prompt user for name.
        if (conversationData.PromptedUserForName)
        {
            // Set the name to what the user provided.
            userProfile.Name = turnContext.Activity.Text?.Trim();

            // Acknowledge that we got their name.
            await turnContext.SendActivityAsync($"Thanks {userProfile.Name}. To see conversation data, type anything.");

            // Reset the flag to allow the bot to go through the cycle again.
            conversationData.PromptedUserForName = false;
        }
        else
        {
            // Prompt the user for their name.
            await turnContext.SendActivityAsync($"What is your name?");

            // Set the flag to true, so we don't prompt in the next turn.
            conversationData.PromptedUserForName = true;
        }
    }
    else
    {
        // Add message details to the conversation data.
        // Convert saved Timestamp to local DateTimeOffset, then to string for display.
        var messageTimeOffset = (DateTimeOffset)turnContext.Activity.Timestamp;
        var localMessageTime = messageTimeOffset.ToLocalTime();
        conversationData.Timestamp = localMessageTime.ToString();
        conversationData.ChannelId = turnContext.Activity.ChannelId.ToString();

        // Display state data.
        await turnContext.SendActivityAsync($"{userProfile.Name} sent: {turnContext.Activity.Text}");
        await turnContext.SendActivityAsync($"Message received at: {conversationData.Timestamp}");
        await turnContext.SendActivityAsync($"Message received from: {conversationData.ChannelId}");
    }
}

Перед выходом из обработчика поворота используйте метод SaveChangesAsync() объектов управления состоянием, чтобы записать все изменения состояния в хранилище.

Bots/StateManagementBot.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);
}

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

  1. Скачайте и установите последнюю версию Bot Framework Emulator.
  2. Выполните этот пример на локальном компьютере. Если вам нужны инструкции, обратитесь к README для C#, JavaScript, JavaScript или Python.
  3. Используйте эмулятор для тестирования примера бота.

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

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

Тема Примечания.
Конфиденциальность Если вы собираетесь хранить персональные данные пользователя, обеспечьте соблюдение Общего регламента по защите данных.
Управление состоянием Все вызовы управления состоянием асинхронны, и по умолчанию используется правило последнего записавшего. На практике следует размещать методы get, set и save state как можно ближе друг к другу в коде бота. Сведения о реализации оптимистической блокировки см. в разделе "Реализация пользовательского хранилища для бота".
Критически важные бизнес-данные Используйте состояние бота для хранения настроек, имени пользователя или последней, которую они заказали, но не используйте его для хранения критически важных бизнес-данных. Для критически важных данных создайте собственные компоненты хранилища или записывайте их непосредственно в хранилище.
Распознаватель текста В этом примере используются библиотеки Microsoft/Recognizer-Text для синтаксического анализа и проверки пользовательского ввода. Дополнительные сведения см. на странице обзор.

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

Узнайте, как задать пользователю ряд вопросов, проверить свои ответы и сохранить входные данные.