Salvar dados de usuário e de conversa

APLICA-SE A: SDK v4

Um bot é inerentemente sem estado. Após a implantação do seu bot, talvez ele não seja executado no mesmo processo ou no mesmo computador de um turno para outro. Porém, talvez seu bot precise controlar o contexto de uma conversa, para que ele possa gerenciar seu comportamento e lembrar-se das respostas às perguntas anteriores. Os recursos de armazenamento e estado do SDK do Bot Framework permitem que você adicione um estado ao seu bot. Os bots usam o gerenciamento de estado e os objetos de armazenamento para gerenciar e manter o estado. O gerenciador de estado fornece uma camada de abstração que permite acessar propriedades de estado usando acessadores de propriedades, independentemente do tipo de armazenamento subjacente.

Observação

Os SDKs do Bot Framework para JavaScript, C# e Python continuarão a ter suporte, no entanto, o SDK para Java está sendo desativado. Seu suporte final de longo prazo será encerrado em novembro de 2023. Somente correções críticas de segurança e de bugs serão realizadas neste repositório.

Os bots existentes criados com o SDK para Java continuarão a funcionar.

Para a criação de novos bots, considere usar o Power Virtual Agents e leia sobre como escolher a solução de chatbot mais adequada.

Para obter mais informações, confira O futuro da criação de bots.

Pré-requisitos

Sobre este exemplo

Ao receber entrada do usuário, este exemplo verifica o estado da conversa armazenada para ver se o nome foi solicitado a esse usuário anteriormente. Se não foi, o nome do usuário será solicitado e essa entrada será armazenada no estado do usuário. Se foi, o nome armazenado no estado do usuário será usado para conversar com ele, e os respectivos dados de entrada, bem como a hora de recebimento e a ID do canal de entrada, serão retornados ao usuário. Os valores de hora e ID do canal são recuperados dos dados da conversa do usuário e, em seguida, salvos no estado da conversa. O diagrama a seguir mostra a relação entre o bot, o perfil do usuário e as classes de dados da conversa.

Definir classes

A primeira etapa na configuração de gerenciamento de estado é definir as classes que contêm as informações a serem gerenciadas no estado do usuário e da conversa. O exemplo usado neste artigo define as seguintes classes:

  • Em UserProfile.cs, você define uma classe UserProfile para as informações do usuário que o bot coletará.
  • Em ConversationData.cs, você define uma classe ConversationData para controlar o estado da nossa conversa durante a coleta de informações do usuário.

Os exemplos de código a seguir mostram as definições para as classes UserProfile e 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;
}

Criar objetos de estado da conversa e do usuário

Em seguida, você registra o MemoryStorage, que é usado para criar os objetos UserState e ConversationState. Os objetos de estado da conversa e do usuário são criados em Startup e a dependência é injetada no construtor de bot. Outros serviços de um bot que são registrados: um provedor de credenciais, um adaptador e a implementação do bot.

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

Adicionar acessadores de propriedade do estado

Agora você pode criar acessadores de propriedade usando o método CreateProperty, que fornece um identificador ao objeto BotState. Cada acessador de propriedade de estado permite que você obtenha ou defina o valor da propriedade de estado associada. Antes de usar as propriedades de estado, use cada acessador para carregar a propriedade do armazenamento e obtê-la no cache de estado. Para obter a chave de escopo corretamente associada à propriedade do estado, você pode chamar o método GetAsync.

Bots/StateManagementBot.cs

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

Estado de acesso do seu bot

As seções anteriores abordam as etapas de tempo de inicialização para adição dos acessadores de propriedade do estado ao nosso bot. Agora, você pode usar esses acessadores em tempo de execução para ler e gravar informações de estado. O código de exemplo abaixo usa o seguinte fluxo lógico:

  • Se userProfile.Name estiver vazio e conversationData.PromptedUserForName for true, você poderá recuperar o nome de usuário fornecido e armazená-lo no estado do usuário.
  • Se userProfile.Name estiver vazio e conversationData.PromptedUserForName for false, você poderá solicitar o nome de usuário.
  • Se userProfile.Name foi armazenado anteriormente, você recupera a hora da mensagem e a ID do canal da entrada do usuário, ecoa todos os dados de volta para o usuário e armazena os dados recuperados no estado da conversa.

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

Antes de encerrar o manipulador de turno, você pode usar o método SaveChangesAsync() dos objetos de gerenciamento de estado para gravar todas as alterações no estado de volta no armazenamento.

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

Testar seu bot

  1. Baixe e instale o Bot Framework Emulator mais recente
  2. Execute o exemplo localmente em seu computador. Se você precisar de instruções, confira o arquivo LEIAME para C#, JavaScript, Java ou Python.
  3. Use o Emulator para testar seu bot de exemplo.

Informações adicionais

Este artigo descreveu como você pode adicionar estado ao seu bot. Para obter mais informações dos tópicos relacionados, confira a tabela a seguir.

Tópico Observações
Privacidade se você pretende armazenar dados pessoais do usuário, garanta a conformidade com o Regulamento Geral sobre a Proteção de Dados.
Gerenciamento de estado todas as chamadas de gerenciamento de estado são assíncronas e last-writer-wins por padrão. Na prática, você deve obter, definir e salvar o estado o mais próximo possível em seu bot. Para uma discussão sobre como implementar o bloqueio otimista, confira Implementar armazenamento personalizado para seu bot.
Dados críticos de negócios Use o estado do bot para armazenar preferências, nome de usuário ou o último pedido feito, mas não o use para armazenar dados críticos de negócios. Para dados críticos, crie seus próprios componentes de armazenamento ou grave diretamente no armazenamento.
Recognizer-Text o exemplo usa as bibliotecas Microsoft/Recognizer-Text para analisar e validar a entrada do usuário. Para saber mais, confira a página visão geral.

Próximas etapas

Saiba como fazer uma série de perguntas ao usuário, validar suas respostas e salvar suas entradas.