Implementar fluxo de conversação sequencial

APLICA-SE A: SDK v4

Coletar informações fazendo perguntas é uma das principais maneiras pelas quais um bot interage com os usuários. A biblioteca de caixas de diálogo fornece recursos internos úteis, como classes de prompt que facilitam fazer perguntas e validar a resposta para garantir que ela corresponda a um tipo de dados específico ou atenda às regras de validação personalizadas.

Você pode gerenciar fluxos de conversação lineares e mais complexos usando a biblioteca de diálogos. Em uma interação linear, o bot executa 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ção linear criando prompts e chamando-os a partir de uma caixa de diálogo em cascata. Para obter exemplos de como escrever seus próprios prompts sem usar a biblioteca de diálogos, consulte o artigo Criar seus próprios prompts para coletar entrada do usuário.

Nota

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

Os bots existentes construídos com o Java SDK continuarão a funcionar.

Para a criação de novos bots, considere usar o Power Virtual Agents e leia sobre como escolher a solução de chatbot certa.

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

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 linear que faz ao usuário uma série de perguntas. O código usa uma caixa de diálogo para percorrer estas etapas:

Passos Tipo de prompt
Pergunte ao utilizador o seu modo de transporte Prompt de escolha
Peça ao utilizador o seu nome Prompt de texto
Pergunte ao usuário se ele deseja fornecer sua idade Confirmar prompt
Se responderam que sim, pergunte a sua idade Prompt de número, com validação para aceitar apenas idades maiores que 0 e menores que 150
Se não estiverem a utilizar o Microsoft Teams, peça-lhes uma foto de perfil Prompt de anexo, com validação para permitir um anexo ausente
Pergunte se a informação recolhida está "ok" Reutilizar Prompt de confirmação

Por fim, se responderem sim, exiba as informações coletadas; caso contrário, diga ao usuário que suas informações não serão mantidas.

Criar a caixa de diálogo principal

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

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

Diagrama de classes para o exemplo de C#.

Caixas de diálogo\UserProfileDialog.cs

Comece criando o UserProfileDialog que deriva da ComponentDialog classe, e tem sete etapas.

UserProfileDialog No construtor, crie as etapas em cascata, os prompts e a caixa de diálogo em cascata e adicione-os ao conjunto de diálogos. Os prompts precisam estar no mesmo conjunto de diálogo em que 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,
        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 a caixa de diálogo usa para solicitar entrada. Para usar um prompt, chame-o a partir de uma etapa na caixa de 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 pede entrada. Em seguida, ele retorna o valor válido ou recomeça desde o início com um novo prompt até receber uma entrada válida.

Você sempre deve retornar um não-nulo DialogTurnResult de uma etapa de cascata. Se você não fizer isso, sua caixa de diálogo pode não funcionar como projetado. Abaixo é mostrada a implementação para NameStepAsync na 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 AgeStepAsync, especifique um prompt de nova tentativa para quando a entrada do usuário não for validada, seja porque está em um formato que o prompt não pode analisar, ou porque a entrada falha em um critério de validação. Nesse caso, se nenhum prompt de nova tentativa foi fornecido, o prompt usará o texto do prompt inicial para solicitar novamente a entrada do 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 UserProfile classe.

public class UserProfile
{
    public string Transport { get; set; }

    public string Name { get; set; }

    public int Age { get; set; }

    public Attachment Picture { get; set; }
}

Caixas de diálogo\UserProfileDialog.cs

Na última etapa, marque o stepContext.Result retorno pela caixa de diálogo chamada na etapa de cascata anterior. Se o valor de retorno for true, o acessador de perfil de usuário obtém e atualiza o perfil de usuário. Para obter o perfil de usuário, chame GetAsync e defina os valores das userProfile.Transportpropriedades , userProfile.NameuserProfile.Age e userProfile.Picture . Por fim, resuma as informações para o usuário antes de chamar EndDialogAsync, o que encerra a caixa de diálogo. Encerrar a caixa de diálogo a retira da pilha de diálogo e retorna um resultado opcional para o pai da caixa de diálogo. O pai é a caixa de diálogo ou o método que iniciou a caixa de diálogo que acabou de terminar.

    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 a caixa de diálogo

Bots\DialogBot.cs

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

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 um bot: um provedor de credenciais, um adaptador e a implementação do bot.
  • Serviços para gerenciar o 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 no Startup. Esses serviços estão disponíveis para outras partes do código por meio de 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>();

Nota

O armazenamento de memória é usado apenas 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.

Teste seu bot

  1. Se você ainda não fez isso, instale o Bot Framework Emulator.
  2. Execute a amostra localmente na sua máquina.
  3. Inicie o emulador, conecte-se ao seu bot e envie mensagens como mostrado abaixo.

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

Informações adicionais

Sobre a caixa de diálogo e o estado do bot

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

  • Um criado dentro do estado de conversação para a propriedade de estado de 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 caixas de diálogo e é atualizado pelo contexto da caixa de diálogo, como quando os métodos de caixa de diálogo inicial ou continuar são chamados.
  • Um criado dentro do estado do usuário para a propriedade de perfil de usuário. O bot usa isso para rastrear informações que tem sobre o usuário, e você deve gerenciar explicitamente esse estado no código 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 que o valor de uma propriedade de estado é solicitado em um turno, mas deve ser persistido explicitamente. Para persistir as 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 do perfil do usuário de dentro da caixa de diálogo. Essa prática pode funcionar para alguns bots, 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 do bot separados. Por exemplo, quando a caixa de diálogo reunir informações completas, você poderá:

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

Definição de um método de validação 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 de 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 reavisar o usuário; caso contrário, retorne true para aceitar a entrada e retorne da caixa de diálogo de prompt. Você pode alterar o valor no validador de acordo com 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óximos passos

Add natural language understanding to your bot (Adicionar o Language Understanding natural ao bot)