Criar fluxo de conversa avançado usando branches e loops

APLICA-SE A: SDK v4

Você pode criar fluxos de conversa complexos usando a biblioteca de diálogos. Este artigo aborda como gerenciar conversas complexas com ramificações e loop e como passar argumentos entre diferentes partes do diálogo.

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 Power Virtual Agents e ler sobre como escolher a solução de chatbot correta.

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

Pré-requisitos

Sobre este exemplo

Este exemplo representa um bot que pode inscrever usuários para avaliação de até duas empresas de uma lista. O bot usa três diálogos de componentes para gerenciar o fluxo da conversa. Cada diálogo componente inclui uma caixa de diálogo de cascata e os prompts necessários para coletar a entrada do usuário. Esses diálogos são descritos mais detalhadamente nas seções a seguir. Ele usa o estado da conversa para gerenciar os diálogos e usa o estado do usuário para salvar informações sobre o usuário e quais empresas eles desejam avaliar.

O bot deriva do manipulador de atividade. Como muitos dos bots de exemplo, ele dá boas vindas ao usuário, usa caixas de diálogo para manipular mensagens do usuário e salva o estado do usuário e da conversa antes do término da conversa.

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

Diagrama de classes para exemplo de C#.

Definir o perfil do usuário

O perfil do usuário conterá informações coletadas pelos diálogos: o nome do usuário, a idade e as empresas selecionadas para avaliação.

UserProfile.cs

/// <summary>Contains information about a user.</summary>
public class UserProfile
{
    public string Name { get; set; }

    public int Age { get; set; }

    // The list of companies the user wants to review.
    public List<string> CompaniesToReview { get; set; } = new List<string>();

Criar os diálogos

Este bot contém três diálogos:

  • O diálogo principal inicia o processo geral e, em seguida, resume as informações coletadas.
  • O diálogo de nível superior coleta as informações do usuário e inclui a lógica de ramificação, com base na idade do usuário.
  • O diálogo de seleção da avaliação permite que o usuário selecione iterativamente as empresas a serem avaliadas. Ele usa lógica de loop para fazer isso.

O diálogo principal

O diálogo principal tem duas etapas:

  1. Iniciar o diálogo de nível superior.
  2. Recuperar e resumir o perfil do usuário coletado pelo diálogo de nível superior, salvar essas informações no estado do usuário e, finalmente, sinalizar o final do diálogo principal.

Dialogs\MainDialog.cs

private async Task<DialogTurnResult> InitialStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    return await stepContext.BeginDialogAsync(nameof(TopLevelDialog), null, cancellationToken);
}

private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    var userInfo = (UserProfile)stepContext.Result;

    string status = "You are signed up to review "
        + (userInfo.CompaniesToReview.Count is 0 ? "no companies" : string.Join(" and ", userInfo.CompaniesToReview))
        + ".";

    await stepContext.Context.SendActivityAsync(status);

    var accessor = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
    await accessor.SetAsync(stepContext.Context, userInfo, cancellationToken);

    return await stepContext.EndDialogAsync(null, cancellationToken);
}

O diálogo de nível superior

O diálogo de nível superior tem quatro etapas:

  1. Perguntar o nome do usuário.
  2. Perguntar a idade do usuário.
  3. Iniciar o diálogo de seleção da avaliação ou prosseguir para a próxima etapa, com base na idade do usuário.
  4. Por fim, agradecer a participação do usuário e retornar as informações coletadas.

A primeira etapa cria um perfil do usuário vazio como parte do estado do diálogo. O diálogo começa com um perfil vazio e adiciona informações ao perfil conforme progride. Quando termina, a última etapa retorna as informações coletadas.

Na terceira etapa (iniciar seleção), o fluxo de conversa se ramifica, com base na idade do usuário.

Dialogs\TopLevelDialog.cs

            stepContext.Values[UserInfo] = new UserProfile();

            var promptOptions = new PromptOptions { Prompt = MessageFactory.Text("Please enter your name.") };

            // Ask the user to enter their name.
            return await stepContext.PromptAsync(nameof(TextPrompt), promptOptions, cancellationToken);
        }

        private async Task<DialogTurnResult> AgeStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            // Set the user's name to what they entered in response to the name prompt.
            var userProfile = (UserProfile)stepContext.Values[UserInfo];
            userProfile.Name = (string)stepContext.Result;

            var promptOptions = new PromptOptions { Prompt = MessageFactory.Text("Please enter your age.") };

            // Ask the user to enter their age.
            return await stepContext.PromptAsync(nameof(NumberPrompt<int>), promptOptions, cancellationToken);
        }

        private async Task<DialogTurnResult> StartSelectionStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            // Set the user's age to what they entered in response to the age prompt.
            var userProfile = (UserProfile)stepContext.Values[UserInfo];
            userProfile.Age = (int)stepContext.Result;

            if (userProfile.Age < 25)
            {
                // If they are too young, skip the review selection dialog, and pass an empty list to the next step.
                await stepContext.Context.SendActivityAsync(
                    MessageFactory.Text("You must be 25 or older to participate."),
                    cancellationToken);
                return await stepContext.NextAsync(new List<string>(), cancellationToken);
            }
            else
            {
                // Otherwise, start the review selection dialog.
                return await stepContext.BeginDialogAsync(nameof(ReviewSelectionDialog), null, cancellationToken);
            }
        }

        private async Task<DialogTurnResult> AcknowledgementStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            // Set the user's company selection to what they entered in the review-selection dialog.
            var userProfile = (UserProfile)stepContext.Values[UserInfo];
            userProfile.CompaniesToReview = stepContext.Result as List<string> ?? new List<string>();

            // Thank them for participating.
            await stepContext.Context.SendActivityAsync(
                MessageFactory.Text($"Thanks for participating, {((UserProfile)stepContext.Values[UserInfo]).Name}."),
                cancellationToken);

            // Exit the dialog, returning the collected user information.
            return await stepContext.EndDialogAsync(stepContext.Values[UserInfo], cancellationToken);
        }
    }
}

O diálogo de seleção da avaliação

O diálogo de seleção da avaliação tem duas etapas:

  1. Pedir que o usuário escolha uma empresa para avaliação ou done para concluir.
    • Se o diálogo tiver sido iniciado com informações iniciais, as informações estarão disponíveis por meio da propriedade options do contexto da etapa de cascata. O diálogo de seleção da avaliação pode ser reiniciado e usa essa possibilidade para permitir que o usuário escolha mais de uma empresa para avaliar.
    • Se o usuário já tiver selecionado uma empresa para avaliar, essa empresa será removida das opções disponíveis.
    • Uma opção done é adicionada para permitir que o usuário saia do loop com antecedência.
  2. Repetir esse diálogo ou sair, conforme apropriado.
    • Se o usuário escolher uma empresa para avaliar, adicione-a à lista.
    • Caso o usuário tenha escolhido duas empresas ou tenha optado por sair, encerre o diálogo e retorne a lista coletada.
    • Caso contrário, reinicie o diálogo, inicializando-o com o conteúdo de sua lista.

Dialogs\ReviewSelectionDialog.cs

private async Task<DialogTurnResult> SelectionStepAsync(
    WaterfallStepContext stepContext,
    CancellationToken cancellationToken)
{
    // Continue using the same selection list, if any, from the previous iteration of this dialog.
    var list = stepContext.Options as List<string> ?? new List<string>();
    stepContext.Values[CompaniesSelected] = list;

    // Create a prompt message.
    string message;
    if (list.Count is 0)
    {
        message = $"Please choose a company to review, or `{DoneOption}` to finish.";
    }
    else
    {
        message = $"You have selected **{list[0]}**. You can review an additional company, " +
            $"or choose `{DoneOption}` to finish.";
    }

    // Create the list of options to choose from.
    var options = _companyOptions.ToList();
    options.Add(DoneOption);
    if (list.Count > 0)
    {
        options.Remove(list[0]);
    }

    var promptOptions = new PromptOptions
    {
        Prompt = MessageFactory.Text(message),
        RetryPrompt = MessageFactory.Text("Please choose an option from the list."),
        Choices = ChoiceFactory.ToChoices(options),
    };

    // Prompt the user for a choice.
    return await stepContext.PromptAsync(nameof(ChoicePrompt), promptOptions, cancellationToken);
}

private async Task<DialogTurnResult> LoopStepAsync(
    WaterfallStepContext stepContext,
    CancellationToken cancellationToken)
{
    // Retrieve their selection list, the choice they made, and whether they chose to finish.
    var list = stepContext.Values[CompaniesSelected] as List<string>;
    var choice = (FoundChoice)stepContext.Result;
    var done = choice.Value == DoneOption;

    if (!done)
    {
        // If they chose a company, add it to the list.
        list.Add(choice.Value);
    }

    if (done || list.Count >= 2)
    {
        // If they're done, exit and return their list.
        return await stepContext.EndDialogAsync(list, cancellationToken);
    }
    else
    {
        // Otherwise, repeat this dialog, passing in the list from this iteration.
        return await stepContext.ReplaceDialogAsync(InitialDialogId, list, cancellationToken);
    }
}

Executar os diálogos

A classe dialog bot estende o manipulador de atividade e contém a lógica para executar os diálogos. A classe dialog and welcome bot estende o bot de diálogo para também dar boas-vindas a um usuário quando ele ingressar na conversa.

O manipulador de turnos do bot repete o fluxo de conversa definido pelos três diálogos. Quando ele recebe uma mensagem do usuário:

  1. Ele executa o diálogo principal.
    • Se a pilha de diálogo estiver vazia, será iniciado o diálogo principal.
    • Caso contrário, os diálogos ainda estarão no meio do processo e isso dará continuidade no diálogo ativo.
  2. Isso salvará o estado, para que as atualizações do usuário, da conversa e do diálogo sejam mantidas.

Bots\DialogBot.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);
}

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

Crie e registre serviços conforme necessário:

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

Startup.cs

// 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(options =>
    {
        options.SerializerSettings.MaxDepth = HttpHelper.BotMessageSerializerSettings.MaxDepth;
    });

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

Observação

O armazenamento de memória é usado somente para fins de teste e não se destina ao uso em produção. Certifique-se de usar um tipo persistente de armazenamento para um bot de produção.

Testar o bot

  1. Caso ainda não tenha feito isso, instale o Bot Framework Emulator.

  2. Execute o exemplo localmente em seu computador.

  3. Inicie o Emulador, conecte-se ao seu bot e envie mensagens conforme mostrado abaixo.

    Exemplo de transcrição de uma conversa com o bot de diálogo complexo.

Recursos adicionais

Para obter uma introdução sobre como implementar um diálogo, confira a implementação de um fluxo de conversa sequencial, que usa um único diálogo em cascata e algumas solicitações para fazer uma série de perguntas ao usuário.

A biblioteca Diálogos inclui uma validação básica de prompts. Você também pode adicionar uma validação personalizada. Para obter mais informações, confira coletar entrada do usuário usando um prompt de caixa de diálogo.

Para simplificar o código do seu diálogo e reutilizá-lo em vários bots, defina as partes de um conjunto de diálogos como uma classe separada. Para saber mais, confira reutilizar caixas de diálogos.

Próximas etapas