Implementación de un flujo de conversación secuencialImplement sequential conversation flow

se aplica a: SDK V4APPLIES TO: SDK v4

La recopilación de información mediante la publicación de preguntas es una de las principales formas de interacción de un bot con los usuarios.Gathering information by posing questions is one of the main ways a bot interacts with users. La biblioteca de diálogos proporciona características integradas útiles, como las clases prompt que facilitan la formulación de preguntas y validan la respuesta para asegurarse de que coincide con un tipo de datos específico o cumple con las reglas de validación personalizadas.The dialogs library provides useful built-in features such as prompt classes that make it easy to ask questions and validate the response to make sure it matches a specific data type or meets custom validation rules.

Mediante la biblioteca Dialogs es posible administrar flujos de conversación simples y complejos.You can manage simple and complex conversation flows using the dialogs library. En una interacción simple, el bot ejecuta una secuencia fija de pasos y la conversación finaliza.In a simple interaction, the bot runs through a fixed sequence of steps, and the conversation finishes. Un cuadro de diálogo es útil cuando el bot necesita recopilar información del usuario.A dialog is useful when the bot needs to gather information from the user.

En este artículo se muestra cómo implementar un flujo de conversación simple mediante la creación de mensajes y su llamada desde un diálogo en cascada.This article shows how to implement simple conversation flow by creating prompts and calling them from a waterfall dialog.

Sugerencia

Para obtener ejemplos de cómo escribir sus propias preguntas sin usar la biblioteca de diálogos, vea el artículo Creación de mensajes propios para recopilar datos de entrada del usuario.For examples of how to write your own prompts without using the dialogs library, see the Create your own prompts to gather user input article.

PrerrequisitosPrerequisites

Acerca de este ejemploAbout this sample

En el ejemplo de avisos de varios turnos se usa un diálogo en cascada, algunos avisos y un diálogo de componentes para crear una interacción sencilla que hace al usuario una serie de preguntas.The multi-turn prompts sample uses a waterfall dialog, a few prompts, and a component dialog to create a simple interaction that asks the user a series of questions. El código usa un diálogo para desplazarse por estos pasos:The code uses a dialog to cycle through these steps:

PasosSteps Tipo de avisoPrompt type
Pedir al usuario su modo de transporteAsk the user for their mode of transportation Aviso de elecciónChoice prompt
Pedir al usuario su nombreAsk the user for their name Aviso de textoText prompt
Pedir al usuario si desea proporcionar su edadAsk the user if they want to provide their age Aviso de confirmaciónConfirm prompt
Si respondieron sí, pregunte por su edad.If they answered yes, ask for their age Solicitud de número, con validación para aceptar solo edades mayores que 0 y menores de 150Number prompt, with validation to only accept ages greater than 0 and less than 150
Si no usan Microsoft Teams, se les pide una imagen de perfil.If they're not using Microsoft Teams, ask them for a profile picture Solicitud de datos adjuntos, con validación para permitir que falte un archivo adjuntoAttachment prompt, with validation to allow a missing attachment
Preguntar si la información recopilada es "ok"Ask if the collected information is "ok" Reutilización del aviso de confirmaciónReuse Confirm prompt

Por último, si responde Sí, mostrar la información recopilada; de lo contrario, indicar al usuario que no se conservará su información.Finally, if they answered yes, display the collected information; otherwise, tell the user that their information will not be kept.

Creación del diálogo principalCreate the main dialog

Para usar diálogos, instale el paquete de NuGet Microsoft.Bot.Builder.Dialogs.To use dialogs, install the Microsoft.Bot.Builder.Dialogs NuGet package.

El bot interactúa con el usuario mediante UserProfileDialog.The bot interacts with the user via UserProfileDialog. Al crear la clase del DialogBot bot, UserProfileDialog se establece como su diálogo principal.When creating the bot's DialogBot class, the UserProfileDialog is set as its main dialog. El bot, a continuación, usa un método auxiliar Run para acceder al diálogo.The bot then uses a Run helper method to access the dialog.

Cuadro de diálogo de perfil de usuario de C#

Dialogs\UserProfileDialog.csDialogs\UserProfileDialog.cs

Comience por crear UserProfileDialog el que deriva de la clase y tiene ComponentDialog 7 pasos.Begin by creating the UserProfileDialog that derives from the ComponentDialog class, and has 7 steps.

En el constructor UserProfileDialog, se crean los pasos de cascada, los avisos y el diálogo en cascada y se agregan al conjunto de diálogos.In the UserProfileDialog constructor, create the waterfall steps, prompts and the waterfall dialog, and add them to the dialog set. Los avisos deben estar en el mismo conjunto de diálogos en el que se utilizan.The prompts need to be in the same dialog set in which they are used.

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

A continuación, agregue los pasos que usa el cuadro de diálogo para solicitar la entrada.Next, add the steps that the dialog uses to prompt for input. Para usar un aviso, debe llamarlo desde un paso del diálogo y recuperar el resultado del aviso en el paso siguiente con stepContext.Result.To use a prompt, call it from a step in your dialog and retrieve the prompt result in the following step using stepContext.Result. En un segundo plano, las preguntas consisten en un diálogo de dos pasos.Behind the scenes, prompts are a two-step dialog. En primer lugar, el símbolo del sistema solicita la entrada.First, the prompt asks for input. A continuación, devuelve el valor válido o comienza de nuevo desde el principio con un reprompt hasta que recibe una entrada válida.Then it returns the valid value, or starts over from the beginning with a reprompt until it receives a valid input.

Siempre debe devolver un valor no NULL de DialogTurnResult desde un paso de cascada.You should always return a non-null DialogTurnResult from a waterfall step. Si no lo hace, es posible que el cuadro de diálogo no funcione según lo diseñado.If you don't, your dialog may not work as designed. A continuación se muestra la implementación de NameStepAsync en el cuadro de diálogo de cascada.Shown below is the implementation for NameStepAsync in the waterfall dialog.

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

En , especifique un mensaje de reintento para cuando la entrada del usuario no se valide, ya sea porque está en un formato que el símbolo del sistema no puede analizar o la entrada produce un error en un criterio de AgeStepAsync validación.In AgeStepAsync, specify a retry prompt for when the user's input fails to validate, either because it's in a format that the prompt can't parse, or the input fails a validation criteria. En este caso, si no se ha proporcionado un aviso de reintento, el aviso utilizará el texto del aviso inicial para volver a pedir la entrada al usuario.In this case, if no retry prompt was provided, the prompt will use the initial prompt text to re-prompt the user for input.

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.csUserProfile.cs

El modo de transporte, el nombre y la edad del usuario se guardan en una instancia de la clase UserProfile.The user's mode of transportation, name, and age are saved in an instance of the UserProfile class.

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.csDialogs\UserProfileDialog.cs

En el último paso, compruebe el stepContext.Result devuelto por el diálogo llamado en el paso de cascada anterior.In the last step, check the stepContext.Result returned by the dialog called in the previous waterfall step. Si el valor devuelto es true, el accessor de perfil de usuario obtiene y actualiza el perfil de usuario.If the return value is true, the user profile accessor gets and updates the user profile. Para obtener el perfil de usuario, llame a y establezca los valores de las propiedades GetAsync userProfile.Transport , y userProfile.Name userProfile.Age userProfile.Picture .To get the user profile, call GetAsync and then set the values of the userProfile.Transport, userProfile.Name, userProfile.Age and userProfile.Picture properties. Por último, resuma la información del usuario antes de llamar EndDialogAsync a , que finaliza el cuadro de diálogo.Finally, summarize the information for the user before calling EndDialogAsync, which ends the dialog. La finalización del diálogo lo extrae de la pila de diálogos y devuelve un resultado opcional al elemento primario del diálogo.Ending the dialog pops it off the dialog stack and returns an optional result to the dialog's parent. El elemento primario es el diálogo o método que inició el diálogo que acaba de terminar.The parent is the dialog or method that started the dialog that just ended.

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

Ejecución del diálogoRun the dialog

Bots\DialogBot.csBots\DialogBot.cs

El controlador OnMessageActivityAsync utiliza el método RunAsync para iniciar o continuar el diálogo.The OnMessageActivityAsync handler uses the RunAsync method to start or continue the dialog. OnTurnAsync usa los objetos de administración de estado del bot para conservar los cambios de estado en el almacenamiento.OnTurnAsync uses the bot's state management objects to persist any state changes to storage. El método ActivityHandler.OnTurnAsync llama a los diversos métodos del controlador de actividades, como OnMessageActivityAsync.The ActivityHandler.OnTurnAsync method calls the various activity handler methods, such as OnMessageActivityAsync. De esta manera, el estado se guarda una vez completado el controlador de mensajes, pero antes de que se complete el propio turno.In this way, the state is saved after the message handler completes but before the turn itself completes.

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

Registro de los servicios del botRegister services for the bot

Este bot usa los siguientes servicios:This bot uses the following services:

  • Servicios básicos de un bot: un proveedor de credenciales, un adaptador y la implementación del bot.Basic services for a bot: a credential provider, an adapter, and the bot implementation.
  • Servicios para administrar el estado: almacenamiento, estado del usuario y estado de la conversación.Services for managing state: storage, user state, and conversation state.
  • El diálogo que va a usar el bot.The dialog the bot will use.

Startup.csStartup.cs

Registre los servicios para el bot en Startup .Register services for the bot in Startup. Estos servicios están disponibles para otras partes del código mediante la inserción de dependencias.These services are available to other parts of the code through dependency injection.

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

Nota

El almacenamiento en memoria se usa solo con fines de prueba y no está pensado para su uso en producción.Memory storage is used for testing purposes only and is not intended for production use. Asegúrese de usar un tipo de almacenamiento persistente para un bot de producción.Be sure to use a persistent type of storage for a production bot.

Prueba del botTo test the bot

  1. Si aún no lo ha hecho, instale el Bot Framework Emulator.If you haven't done so already, install the Bot Framework Emulator.
  2. Ejecute el ejemplo localmente en la máquina.Run the sample locally on your machine.
  3. Inicie el emulador, conéctese al bot y envíe mensajes como se muestra a continuación.Start the Emulator, connect to your bot, and send messages as shown below.

Ejecución de ejemplo del diálogo de aviso de varios turnos

Información adicionalAdditional information

Acerca del estado del diálogo y el botAbout dialog and bot state

En este bot, se definen dos accessors de propiedad de estado:In this bot, two state property accessors are defined:

  • Uno se crea en el estado de la conversación de la propiedad de estado del diálogo.One created within conversation state for the dialog state property. El estado del diálogo realiza un seguimiento de dónde se encuentra el usuario en los diálogos de un conjunto de diálogos y se actualiza mediante el contexto del diálogo, como cuando se llama a los métodos begin dialog o continue dialog.The dialog state tracks where the user is within the dialogs of a dialog set, and it's updated by the dialog context, such as when the begin dialog or continue dialog methods are called.
  • Uno creado en el estado de usuario de la propiedad de perfil de usuario.One created within user state for the user profile property. El bot lo usa para realizar un seguimiento de la información que tiene sobre el usuario y debe administrar explícitamente este estado en el código del cuadro de diálogo.The bot uses this to track information it has about the user, and you must explicitly manage this state in the dialog code.

Los métodos get y set del descriptor de acceso de propiedad de estado obtienen y establecen el valor de la propiedad en la memoria caché del objeto de administración de estado.The get and set methods of a state property accessor get and set the value of the property in the state management object's cache. La memoria caché se rellena la primera vez que se solicita el valor de una propiedad de estado en un turno, pero se debe guardar explícitamente.The cache is populated the first time the value of a state property is requested in a turn, but it must be persisted explicitly. Para conservar los cambios en ambas propiedades de estado, se realiza una llamada al método save changes del objeto de administración de estado correspondiente.In order to persist changes to both of these state properties, a call to the save changes method, of the corresponding state management object, is performed.

En este ejemplo se actualiza el estado del perfil de usuario desde el diálogo.This sample updates the user profile state from within the dialog. Esta práctica puede funcionar para un bot simple, pero no funcionará si desea reutilizar un diálogo entre bots.This practice can work for a simple bot, but it won't work if you want to reuse a dialog across bots.

Hay varias opciones para mantener independientes los pasos del diálogo y el estado del bot.There are various options for keeping dialog steps and bot state separate. Por ejemplo, una vez que el diálogo recopila toda la información, puede:For example, once your dialog gathers complete information, you can:

  • Use el método de cuadro de diálogo final para proporcionar los datos recopilados como valor devuelto al contexto primario.Use the end dialog method to provide the collected data as return value back to the parent context. Puede ser el controlador de turnos del bot o un diálogo activo anterior en la pila de diálogos y es cómo se diseñan las clases de aviso.This can be the bot's turn handler or an earlier active dialog on the dialog stack and it's how the prompt classes are designed.
  • Generar una solicitud a un servicio adecuado.Generate a request to an appropriate service. Esto podría funcionar bien si el bot actúa como un front-end de un servicio de mayor tamaño.This might work well if your bot acts as a front end to a larger service.

Definición de un método validador de avisosDefinition of a prompt validator method

UserProfileDialog.csUserProfileDialog.cs

A continuación se muestra un ejemplo de código de validador para la AgePromptValidatorAsync definición del método.Below is a validator code example for the AgePromptValidatorAsync method definition. promptContext.Recognized.Value contiene el valor analizado, que es un entero aquí para el aviso del número.promptContext.Recognized.Value contains the parsed value, which is an integer here for the number prompt. promptContext.Recognized.Succeeded indica si el aviso pudo analizar la entrada del usuario o no.promptContext.Recognized.Succeeded indicates whether the prompt was able to parse the user's input or not. El validador debería devolver False para indicar que no se aceptó el valor y el diálogo del aviso debe volver a preguntar al usuario; de lo contrario, devuelve True para aceptar la entrada y volver del diálogo del aviso.The validator should return false to indicate that the value was not accepted and the prompt dialog should reprompt the user; otherwise, return true to accept the input and return from the prompt dialog. Tenga en cuenta que puede cambiar el valor en el validador según su escenario.Note that you can change the value in the validator per your scenario.

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

Pasos siguientesNext steps