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 o 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ários turnos 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 faz ao usuário uma série de perguntas. 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, o UserProfileDialog é definido como seu diálogo principal. O bot, em seguida, usa um método auxiliar Run para acessar o diálogo.

Caixa de diálogo Perfil do usuário em C#

Dialogs\UserProfileDialog.cs

Comece criando o UserProfileDialog que deriva da classe e tem ComponentDialog 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, ele retorna o valor válido ou começa de novo desde o início com uma nova reprodução até receber uma entrada válida.

Você sempre deve ter um retorno de DialogTurnResult não nulo em uma etapa de cascata. Caso não tenha feito isso, a caixa de diálogo pode não funcionar conforme projetado. Veja abaixo a implementação de na NameStepAsync caixa de 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);
}

No , especifique um prompt de nova tentativa para quando a entrada do usuário não for validada, porque ela está em um formato que o prompt não pode analisar ou a entrada falha em um critério de AgeStepAsync validação. 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 em cascata anterior. Se o valor de retorno for true, o acessador de perfil do usuário obtém e atualiza o perfil do usuário. Para obter o perfil do usuário, chame GetAsync e de definidos os valores das userProfile.Transport propriedades , e userProfile.Name userProfile.Age userProfile.Picture . Por fim, resumir as informações para o usuário antes de chamar EndDialogAsync , 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 manter qualquer alteração 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 ativação 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

Registre os serviços para o bot no 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 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>();

    // Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
    services.AddTransient<IBot, DialogBot<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 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.

Execução de exemplo do diálogo de prompt de vários turnos

Informações adicionais

Sobre o estado do diálogo e do bot

Nesse 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 do diálogo rastreia onde o usuário está dentro dos diálogos de um conjunto de diálogos e é atualizado pelo contexto do 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 acompanhar 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 alterações em ambas as propriedades de estado, uma chamada para o método save changes, 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 end dialog para fornecer os dados coletados como valor de retorno de volta para o contexto pai. Esse 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 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

Veja abaixo um exemplo de código do validador para a AgePromptValidatorAsync definição do 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