Implementar fluxo de conversa sequencial

APLICA-SE A: SDK v4

A coleta de informações por meio da apresentação de perguntas é uma das principais formas de um bot interagir com os usuários. A biblioteca de diálogos fornece recursos internos úteis como classes prompt que tornam fácil fazer perguntas e validar as respostas para que elas correspondam a um tipo de dados específico ou atendam às regras de validação personalizadas.

Você pode gerenciar fluxos de conversa simples e complexos usando a biblioteca de caixas de diálogo. Em uma interação simples, o bot percorre uma sequência fixa de etapas e a conversa termina. Uma caixa de diálogo é útil quando o bot precisa coletar informações do usuário.

Este artigo mostra como implementar um fluxo de conversa simples criando prompts e chamando-os de uma caixa de diálogo em cascata.

Dica

Para obter exemplos de como escrever seus próprios prompts sem usar a biblioteca de caixas de diálogo, veja o artigo Criar seus próprios prompts para coletar entrada do usuário.

Pré-requisitos

Sobre este exemplo

O exemplo de prompts de várias voltas usa uma caixa de diálogo em cascata, alguns prompts e uma caixa de diálogo de componente para criar uma interação simples que faça uma série de perguntas ao usuário. O código usa um diálogo para percorrer estas etapas:

Etapas Tipo de prompt
Perguntar ao usuário qual é seu modo de transporte Prompt de escolha
Perguntar o nome do usuário Prompt de texto
Perguntar se o usuário deseja fornecer a idade Prompt de confirmação
Se eles responderam sim, peça sua idade Prompt de número, com validação para aceitar apenas idades maiores que 0 e menores que 150
Se ele não estiver usando o Microsoft Teams, solicite uma imagem do perfil Prompt de anexo, com validação para permitir um anexo ausente
Pergunte se as informações coletadas estão "ok" Reutilizar prompt de confirmação

Finalmente, se ele responder sim, exibir as informações coletadas; caso contrário, dizer ao usuário que as informações dele não serão mantidas.

Criar diálogo principal

Para usar as caixas de diálogo, instale o pacote do NuGet, Microsoft.Bot.Builder.Dialogs.

O bot interage com o usuário por meio de UserProfileDialog. Ao criar a classe do DialogBot bot, ela UserProfileDialog é definida como sua caixa de diálogo principal. O bot, em seguida, usa um método auxiliar Run para acessar o diálogo.

C# user profile dialog

Dialogs\UserProfileDialog.cs

Comece criando o UserProfileDialog que deriva da ComponentDialog classe e tenha 7 etapas.

No construtor UserProfileDialog, crie as etapas de cascata, os prompts e o diálogo de cascata, e adicione-os ao conjunto do diálogo. Os avisos precisam estar no mesmo conjunto do diálogo no qual eles são usados.

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,
        ConfirmStepAsync,
        SummaryStepAsync,
    };

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

Em seguida, adicione as etapas que a caixa de diálogo usa para solicitar entrada. Para usar um prompt, chame-o de uma etapa no seu diálogo e recupere o resultado do prompt na etapa seguinte usando stepContext.Result. Nos bastidores, os prompts são uma caixa de diálogo em duas etapas. Primeiro, o prompt solicita entrada. Em seguida, retorna o valor válido ou inicia desde o início com um reprompt até receber uma entrada válida.

Você sempre deve ter um retorno de DialogTurnResult não nulo em uma etapa de cascata. Se você não fizer isso, sua caixa de diálogo pode não funcionar como projetada. Mostrado abaixo está a implementação na NameStepAsync caixa de diálogo cascata.

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

In AgeStepAsync, specify a retry prompt for when the user's input fail to validate, either it's in a format that the prompt can't parse, or the input fail a validation criteria. Nesse caso, se nenhum prompt de nova tentativa foi fornecido, o prompt usará o texto de prompt inicial para voltar a solicitar a entrada ao usuário.

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

O modo de transporte, o nome e a idade do usuário são salvos em uma instância da classe 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

Na última etapa, verifique o stepContext.Result retornado pela caixa de diálogo chamada na etapa anterior da cascata. Se o valor retornado for verdadeiro, o acessador de perfil de usuário obterá e atualizará o perfil do usuário. Para obter o perfil do usuário, chame GetAsync e defina os valores do e userProfile.NameuserProfile.AgeuserProfile.Picture das userProfile.Transportpropriedades. Por fim, resumir as informações para o usuário antes de chamar EndDialogAsync, o que encerra a caixa de diálogo. O fim do diálogo o remove da pilha de diálogo e retorna um resultado opcional ao pai dele. O pai é o método ou diálogo que iniciou o diálogo recém-terminado.

private async Task<DialogTurnResult> SummaryStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    if ((bool)stepContext.Result)
    {
        // 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);
            }
        }
    }
    else
    {
        await stepContext.Context.SendActivityAsync(MessageFactory.Text("Thanks. Your profile will not be kept."), 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);
}

Executar o diálogo

Bots\DialogBot.cs

O manipulador OnMessageActivityAsync usa o método RunAsync para iniciar ou continuar o diálogo. OnTurnAsync usa os objetos de gerenciamento de estado do bot para persistir quaisquer alterações de estado no armazenamento. O método ActivityHandler.OnTurnAsync chama os vários métodos de manipulador da atividade, como OnMessageActivityAsync. Dessa forma, o estado é salvo após a conclusão do manipulador de mensagens, mas antes que a própria vez seja concluída.

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

Registrar serviços para o bot

Este bot usa os seguintes serviços:

  • Serviços básicos para bot: um provedor de credenciais, um adaptador e a implantação do bot.
  • Serviços para gerenciamento de estado: armazenamento, estado do usuário e estado da conversa.
  • A caixa de diálogo que o bot usará.

Startup.cs

Registrar serviços para o bot em Startup. Esses serviços estão disponíveis para outros blocos do código por meio da injeção de dependência.

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

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

        // The Dialog that will be run by the bot.
        services.AddSingleton<UserProfileDialog>();

Observação

O armazenamento de memória é usado somente para testes e não deve ser usado na produção. Certifique-se de usar um tipo persistente de armazenamento para um bot de produção.

Para testar o bot

  1. Se você ainda não fez isso, instale o Emulador do Bot Framework.
  2. Execute o exemplo localmente em seu computador.
  3. Inicie o Emulador, conecte-se ao seu bot e envie mensagens conforme mostrado abaixo.

Sample run of the multi-turn prompt dialog

Informações adicionais

Sobre o estado do diálogo e do bot

Neste bot, dois acessadores de propriedade de estado são definidos:

  • Um deles criado dentro do estado de conversa para a propriedade de estado do diálogo. O estado da caixa de diálogo rastreia onde o usuário está dentro das caixas de diálogo de um conjunto de diálogos e é atualizado pelo contexto da caixa de diálogo, como quando os métodos de diálogo iniciar ou continuar são chamados.
  • Um deles criado dentro do estado do usuário para a propriedade de perfil do usuário. O bot usa isso para controlar as informações que ele tem sobre o usuário e você deve gerenciar explicitamente esse estado no código da caixa de diálogo.

Os métodos get e set de um acessador de propriedade de estado obtêm e definem o valor da propriedade no cache do objeto de gerenciamento de estado. O cache é preenchido na primeira vez em que o valor de uma propriedade de estado é solicitado em um turno, mas deve ser mantido explicitamente. Para persistir as alterações em ambas as propriedades de estado, uma chamada para o método salvar alterações , do objeto de gerenciamento de estado correspondente, é executada.

Este exemplo atualiza o estado de perfil do usuário a partir do diálogo. Essa prática pode funcionar para um bot simples, mas não funcionará se você quiser reutilizar uma caixa de diálogo entre bots.

Há várias opções para manter as etapas de diálogo e o estado de bot separados. Por exemplo, após o diálogo reunir todas as informações, você pode:

  • Use o método de caixa de diálogo final para fornecer os dados coletados como valor de retorno de volta ao contexto pai. Esse pode ser o manipulador de turnos do bot ou uma caixa de diálogo ativa anterior na pilha de diálogo e é assim que as classes de prompt são projetadas.
  • Gere uma solicitação para um serviço apropriado. Isso pode funcionar bem se seu bot atuar como um front-end para um serviço maior.

Definição de um método validador de prompt

UserProfileDialog.cs

Abaixo está um exemplo de código validador para a definição do AgePromptValidatorAsync método. promptContext.Recognized.Value contém o valor analisado, que é um inteiro aqui para o prompt do número. promptContext.Recognized.Succeeded indica se o prompt foi capaz de analisar a entrada do usuário ou não. O validador deve retornar false para indicar que o valor não foi aceito e a caixa de diálogo de prompt deve perguntar novamente ao usuário; caso contrário, retorna true para aceitar a entrada e retornar da caixa de diálogo de prompt. Observe que você pode alterar o valor no validador de acordo com seu cenário.

private static Task<bool> AgePromptValidatorAsync(PromptValidatorContext<int> promptContext, CancellationToken cancellationToken)
{
    // This condition is our validation rule. You can also change the value at this point.
    return Task.FromResult(promptContext.Recognized.Succeeded && promptContext.Recognized.Value > 0 && promptContext.Recognized.Value < 150);
}

Próximas etapas