Compartilhar via


Crie seus próprio prompts para coletar entradas do usuário

APLICA-SE A: SDK v4

Uma conversa entre um bot e um usuário muitas vezes envolve solicitar ao usuário algumas informações, analisar as respostas e atuar com base nessas informações. Seu bot deve controlar o contexto de uma conversa, para que ele possa gerenciar seu comportamento e lembrar-se das respostas às perguntas anteriores. Um estado do bot é uma informação que o bot controla para responder apropriadamente às mensagens recebidas.

Dica

A biblioteca de diálogos fornece prompts internos que oferecem mais funcionalidade do que os usuários podem usar. Exemplos desses prompts podem ser encontrados no artigo Implementar fluxo de conversa sequencial.

Observação

Os SDKs JavaScript, C# e Python do Bot Framework continuarão a ser compatíveis. No entanto, o SDK Java está sendo desativado, com o suporte final de longo prazo terminando em novembro de 2023.

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

Para a criação de novos bots, considere usar o Microsoft Copilot Studio e leia sobre como escolher a solução de copiloto certa.

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

Pré-requisitos

Sobre o código de exemplo

O exemplo de bot faz uma série de perguntas ao usuário, valida algumas das respostas dele e salva as entrada que ele faz. O diagrama a seguir mostra a relação entre o bot, o perfil do usuário e as classes de fluxo da conversa.

Diagrama de classes para o exemplo de C#.

  • Uma classe UserProfile para as informações do usuário que o bot coletará.
  • Uma classe ConversationFlow para controlar o estado da nossa conversa durante a coleta de informações do usuário.
  • Uma enumeração ConversationFlow.Question interna para acompanhar o ponto em que você está na conversa.

O estado do usuário acompanhará o nome do usuário, a idade e a data escolhida. O estado da conversa acompanhará o que você acabou de perguntar ao usuário. Como você não planejou implantar esse bot, você configurará os estados do usuário e da conversa para usar o armazenamento de memória.

Você usa o manipulador de turno de mensagem do bot e as propriedades do estado do usuário e da conversa para gerenciar o fluxo da conversa e a coleta de entradas. No seu bot, você registrará as informações da propriedade do estado recebidas durante cada iteração do manipulador de turno da mensagem.

Criar objetos da conversa e do usuário

Crie o usuário e os objetos de estado da conversa na inicialização e consuma-os por meio da injeção de dependência no construtor de bot.

Startup.cs

// 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.
services.AddSingleton<UserState>();

// Create the Conversation state.
services.AddSingleton<ConversationState>();

Bots/CustomPromptBot.cs

private readonly BotState _userState;
private readonly BotState _conversationState;

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

Criar acessadores de propriedade

Crie acessadores de propriedade para as propriedades de perfil de usuário e de fluxo de conversa e, em seguida, chame GetAsync para recuperar o valor da propriedade do estado.

Bots/CustomPromptBot.cs

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    var conversationStateAccessors = _conversationState.CreateProperty<ConversationFlow>(nameof(ConversationFlow));
    var flow = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationFlow(), cancellationToken);

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

Antes do fim da rodada, chame SaveChangesAsync para gravar qualquer alteração de estado no armazenamento.

    await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
    await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
}

Manipulador de turno de mensagens

Ao lidar com atividades de mensagens, o manipulador de mensagens usa um método auxiliar para gerenciar a conversa e fazer solicitações ao usuário. O método auxiliar é descrito na seção a seguir.

Bots/CustomPromptBot.cs

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    var conversationStateAccessors = _conversationState.CreateProperty<ConversationFlow>(nameof(ConversationFlow));
    var flow = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationFlow(), cancellationToken);

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

    await FillOutUserProfileAsync(flow, profile, turnContext, cancellationToken);

    // Save changes.
    await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
    await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
}

Preenchendo o perfil do usuário

O bot solicita ao usuário informações, com base na pergunta, se houver, que o bot fez na rodada anterior. A entrada é analisada por meio de um método de validação.

Cada método de validação segue um design semelhante:

  • O valor retornado indica se a entrada é uma resposta válida para a pergunta.
  • Se a validação é bem-sucedida, produz um valor analisado e normalizado para salvar.
  • Se a validação falha, produz uma mensagem com a qual o bot pode solicitar as informações novamente.

Os métodos de validação são descritos na seção a seguir.

Bots/CustomPromptBot.cs

{
    var input = turnContext.Activity.Text?.Trim();
    string message;

    switch (flow.LastQuestionAsked)
    {
        case ConversationFlow.Question.None:
            await turnContext.SendActivityAsync("Let's get started. What is your name?", null, null, cancellationToken);
            flow.LastQuestionAsked = ConversationFlow.Question.Name;
            break;
        case ConversationFlow.Question.Name:
            if (ValidateName(input, out var name, out message))
            {
                profile.Name = name;
                await turnContext.SendActivityAsync($"Hi {profile.Name}.", null, null, cancellationToken);
                await turnContext.SendActivityAsync("How old are you?", null, null, cancellationToken);
                flow.LastQuestionAsked = ConversationFlow.Question.Age;
                break;
            }
            else
            {
                await turnContext.SendActivityAsync(message ?? "I'm sorry, I didn't understand that.", null, null, cancellationToken);
                break;
            }

        case ConversationFlow.Question.Age:
            if (ValidateAge(input, out var age, out message))
            {
                profile.Age = age;
                await turnContext.SendActivityAsync($"I have your age as {profile.Age}.", null, null, cancellationToken);
                await turnContext.SendActivityAsync("When is your flight?", null, null, cancellationToken);
                flow.LastQuestionAsked = ConversationFlow.Question.Date;
                break;
            }
            else
            {
                await turnContext.SendActivityAsync(message ?? "I'm sorry, I didn't understand that.", null, null, cancellationToken);
                break;
            }

        case ConversationFlow.Question.Date:
            if (ValidateDate(input, out var date, out message))
            {
                profile.Date = date;
                await turnContext.SendActivityAsync($"Your cab ride to the airport is scheduled for {profile.Date}.");
                await turnContext.SendActivityAsync($"Thanks for completing the booking {profile.Name}.");
                await turnContext.SendActivityAsync($"Type anything to run the bot again.");
                flow.LastQuestionAsked = ConversationFlow.Question.None;
                profile = new UserProfile();
                break;
            }
            else
            {
                await turnContext.SendActivityAsync(message ?? "I'm sorry, I didn't understand that.", null, null, cancellationToken);
                break;
            }
    }
}

Analisar e validar a entrada

O bot usa os critérios a seguir para validar a entrada.

  • O nome deve ser uma cadeia de caracteres preenchida. É normalizado eliminando o espaço em branco.
  • A idade deve ser entre 18 e 120. É normalizado retornando um número inteiro.
  • A data deve ser qualquer data ou hora pelo menos uma hora no futuro. É normalizado retornando apenas a parte da data da entrada analisada.

Observação

Para as entradas da idade e da data, o exemplo usa as bibliotecas Microsoft/Reconhecedores de Texto para fazer a análise inicial. Essa é apenas uma maneira de analisar a entrada. Para obter mais informações sobre essas bibliotecas, confira o arquivo LEIAME do projeto.

Bots/CustomPromptBot.cs

private static bool ValidateName(string input, out string name, out string message)
{
    name = null;
    message = null;

    if (string.IsNullOrWhiteSpace(input))
    {
        message = "Please enter a name that contains at least one character.";
    }
    else
    {
        name = input.Trim();
    }

    return message is null;
}

private static bool ValidateAge(string input, out int age, out string message)
{
    age = 0;
    message = null;

    // Try to recognize the input as a number. This works for responses such as "twelve" as well as "12".
    try
    {
        // Attempt to convert the Recognizer result to an integer. This works for "a dozen", "twelve", "12", and so on.
        // The recognizer returns a list of potential recognition results, if any.

        var results = NumberRecognizer.RecognizeNumber(input, Culture.English);

        foreach (var result in results)
        {
            // The result resolution is a dictionary, where the "value" entry contains the processed string.
            if (result.Resolution.TryGetValue("value", out var value))
            {
                age = Convert.ToInt32(value);
                if (age >= 18 && age <= 120)
                {
                    return true;
                }
            }
        }

        message = "Please enter an age between 18 and 120.";
    }
    catch
    {
        message = "I'm sorry, I could not interpret that as an age. Please enter an age between 18 and 120.";
    }

    return message is null;
}

private static bool ValidateDate(string input, out string date, out string message)
{
    date = null;
    message = null;

    // Try to recognize the input as a date-time. This works for responses such as "11/14/2018", "9pm", "tomorrow", "Sunday at 5pm", and so on.
    // The recognizer returns a list of potential recognition results, if any.
    try
    {
        var results = DateTimeRecognizer.RecognizeDateTime(input, Culture.English);

        // Check whether any of the recognized date-times are appropriate,
        // and if so, return the first appropriate date-time. We're checking for a value at least an hour in the future.
        var earliest = DateTime.Now.AddHours(1.0);

        foreach (var result in results)
        {
            // The result resolution is a dictionary, where the "values" entry contains the processed input.
            var resolutions = result.Resolution["values"] as List<Dictionary<string, string>>;

            foreach (var resolution in resolutions)
            {
                // The processed input contains a "value" entry if it is a date-time value, or "start" and
                // "end" entries if it is a date-time range.
                if (resolution.TryGetValue("value", out var dateString)
                    || resolution.TryGetValue("start", out dateString))
                {
                    if (DateTime.TryParse(dateString, out var candidate)
                        && earliest < candidate)
                    {
                        date = candidate.ToShortDateString();
                        return true;
                    }
                }
            }
        }

        message = "I'm sorry, please enter a date at least an hour out.";
    }
    catch
    {
        message = "I'm sorry, I could not interpret that as an appropriate date. Please enter a date at least an hour out.";
    }

    return false;
}

Testar o bot localmente

Baixe e instale o Bot Framework Emulator para testar o bot localmente.

  1. Execute o exemplo localmente em seu computador. Se você precisar de instruções, veja o arquivo README do Exemplo de C#, do Exemplo de JS ou do Exemplo de Python.
  2. Teste-o usando o Emulator.

Recursos adicionais

A Biblioteca de caixas de diálogo fornece classes que automatizam muitos aspectos do gerenciamento das conversas.

Próxima etapa