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


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

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

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

Примечание.

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

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

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

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

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

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

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

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

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

  • В 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 для синтаксического анализа и проверки пользовательского ввода. Дополнительные сведения см. на странице Использование Azure DNS для частных доменов.

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

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