Implementar o 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.

É possível gerenciar fluxos de conversa lineares e mais complexos usando a biblioteca de diálogos. Em uma interação linear, o bot percorre uma sequência fixa de etapas e a conversa é encerrada. Um diálogo é útil quando o bot precisa coletar informações do usuário.

Este artigo mostra como implementar um fluxo de conversa linear ao criar solicitações e ao chamá-las usando um diálogo em cascata. 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.

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

A amostra de solicitações múltiplas usa um diálogo em cascata, algumas solicitações e um diálogo do componente para criar uma interação linear que faz 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 a resposta for afirmativa, pergunte a idade Solicitação de número, com validação para aceitar somente idades superiores a 0 e inferiores a 150
Se ele não estiver usando o Microsoft Teams, solicite uma imagem do perfil Solicitação de anexo, com validação para permitir um anexo ausente
Pergunte se as informações coletadas estão "ok" Reutilizar prompt de confirmação

Por fim, se a resposta for afirmativa, exiba as informações coletadas. Caso contrário, informe ao usuário que suas informações 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 DialogBot do bot, UserProfileDialog é definido como o diálogo principal. O bot, em seguida, usa um método auxiliar Run para acessar o diálogo.

Diagrama de classes para o exemplo de C#.

Dialogs\UserProfileDialog.cs

Comece com a criação de UserProfileDialog que deriva da classe ComponentDialog e tem sete 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. As solicitações precisam estar no mesmo conjunto de diálogos em que são usadas.

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

    // 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 o diálogo usa para fazer a solicitação para a 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, a solicitação pede pela entrada. Em seguida, ela retorna o valor válido ou recomeça desde o início com uma nova solicitação até receber uma entrada válida.

Você sempre deve ter um retorno de DialogTurnResult não nulo em uma etapa de cascata. Caso contrário, o diálogo poderá não funcionar conforme planejado. Abaixo, é mostrada a implementação de NameStepAsync no diálogo em 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);
}

Em AgeStepAsync, especifique uma solicitação de nova tentativa para quando a entrada do usuário falhar na validação, seja porque está em um formato que a solicitação não pode analisar ou porque a entrada não é aprovada pelos critérios de validação. Nesse caso, se nenhuma solicitação de nova tentativa tiver sido fornecida, a solicitação usará o texto da solicitação inicial para solicitar novamente 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 pelo diálogo chamado na etapa anterior em cascata. Se o valor retornado for “true”, o acessador do perfil de usuário obtém e atualiza o perfil de usuário. Para obter o perfil de usuário, chame GetAsync e, em seguida, defina os valores das propriedades userProfile.Transport, userProfile.Name, userProfile.Age e userProfile.Picture. Por fim, resuma as informações para o usuário antes de chamar EndDialogAsync, que encerra o 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.

    else
    {
        msg += $" Your profile will not be kept.";
    }

    await stepContext.Context.SendActivityAsync(MessageFactory.Text(msg), 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);
}

private async Task<DialogTurnResult> SummaryStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    stepContext.Values["picture"] = ((IList<Attachment>)stepContext.Result)?.FirstOrDefault();

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

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 manter as alterações de estado para o 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 da conclusão do turno.

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

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

        // Create the Conversation state. (Used by the Dialog system itself.)
        services.AddSingleton<ConversationState>();

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 seu 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.

Um exemplo de transcrição de uma conversa com o bot de prompt de várias voltas.

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 de diálogo monitora onde o usuário está nos diálogos de um conjunto de diálogos e é atualizado pelo contexto de diálogo, por exemplo, quando os métodos de iniciar diálogo ou de continuar diálogo 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 acompanhar as informações que tem sobre o usuário, e você deve gerenciar explicitamente esse estado no código do 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 manter as alterações em ambas as propriedades de estado, é realizada uma chamada ao método salvar alterações, do objeto de gerenciamento de estado correspondente.

Este exemplo atualiza o estado de perfil do usuário a partir do diálogo. Esta prática pode funcionar para alguns bots, mas não funcionará se você desejar reutilizar um 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:

  • Usar o método terminar diálogo para fornecer os dados coletados como valor de retorno ao contexto-pai. Este pode ser o manipulador de turnos do bot ou um diálogo ativo anterior na pilha de diálogos e é assim que as classes de solicitação 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, é apresentado um exemplo de código validador para a definição do método AgePromptValidatorAsync. 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 o valor “false” para indicar que o valor não foi aceito e o diálogo de solicitação deve realizar uma nova tentativa de solicitação ao usuário. Caso contrário, validador deve retornar o valor “true” para aceitar a entrada e retorná-la usando o diálogo de solicitação. É possível alterar o valor no validador de acordo com o seu cenário.

    }

    // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
    return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = MessageFactory.Text("Is this ok?") }, cancellationToken);
}

Próximas etapas