Implémenter des flux de conversation séquentiels

S'APPLIQUE À : SDK v4

Pour interagir avec les utilisateurs, un bot collecte généralement des informations en posant des questions. La bibliothèque de dialogues fournit des fonctionnalités intégrées telles que les classes d’invite qui vous permettent de poser des questions facilement et de valider la réponse pour vous assurer qu’elle correspond à un type de données spécifique ou répond aux règles de validation personnalisées.

Vous pouvez gérer des flux de conversation linéaires et plus complexes avec la bibliothèque de dialogues. Dans une interaction linéaire, le bot s'exécute via une séquence fixe d'étapes, et la conversation se termine. Un dialogue est utile quand le bot doit recueillir des informations auprès de l'utilisateur.

Cet article explique comment implémenter un flux de conversation linéaire en créant des invites et en les appelant à partir d'un dialogue en cascade. Pour obtenir des exemples montrant comment écrire vos propres invites sans utiliser la bibliothèque de dialogues, consultez l’article Créer vos propres invites pour collecter des entrées utilisateur.

Remarque

Les kits de développement logiciel (SDK) JavaScript, C# et Python bot Framework continueront d'être pris en charge. Toutefois, le kit de développement logiciel (SDK) Java est mis hors service avec une prise en charge finale à long terme se terminant en novembre 2023. Seuls les correctifs de sécurité et de bogues critiques au sein de ce référentiel seront entrepris.

Les bots existants créés avec le kit de développement logiciel (SDK) Java continueront de fonctionner.

Pour la nouvelle génération de bots, envisagez d'utiliser Power Virtual Agents et découvrez comment choisir la solution de chatbot appropriée.

Pour plus d'informations, consultez L'avenir de la construction de bots.

Prérequis

À propos de cet exemple

L'exemple d'invites multitours utilise une boîte de dialogue en cascade, quelques invites et une boîte de dialogue de composant pour créer une interaction linéaire qui pose une série de questions à l'utilisateur. Le code utilise un dialogue pour suivre ces étapes :

Étapes Type d’invite
Demander à l’utilisateur son mode de transport Invite de choix
Demander son nom à l’utilisateur Invite de texte
Demander à l’utilisateur s’il souhaite indiquer son âge Invite de confirmation
Si ils ont répondu oui, demander leur âge Invite de nombre, avec validation pour accepter uniquement des âges compris entre 0 et 150
S’il n’utilise pas Microsoft Teams, demandez-lui un avatar Invite de pièce jointe, avec validation pour autoriser une pièce jointe manquante
Demander si les informations collectées sont correctes Invite de réutilisation de la confirmation

Enfin, si l'utilisateur a répondu oui, affichez les informations collectées ; dans le cas contraire, indiquez-lui que ses informations ne seront pas conservées.

Créer le dialogue principal

Pour utiliser les dialogues, installez le package NuGet Microsoft.Bot.Builder.Dialogs.

Le bot interagit avec l’utilisateur par le biais du UserProfileDialog. Lors de la création de la classe DialogBot du bot, le UserProfileDialog est défini comme son dialogue principal. Le bot utilise ensuite une méthode d’assistance Run pour accéder au dialogue.

Class diagram for the C# sample.

Dialogs\UserProfileDialog.cs

Créez d'abord le UserProfileDialog qui dérive de la classe ComponentDialog et comporte sept étapes.

Dans le constructeur UserProfileDialog, créez les étapes en cascade, les invites et le dialogue en cascade, puis ajoutez-les au jeu de dialogues. Les invites doivent se trouver dans le jeu de dialogues où elles sont utilisées.

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

Ensuite, ajoutez les étapes que le dialogue utilise pour demander l'entrée. Pour utiliser une invite, appelez-la à partir d’une étape dans votre dialogue et récupérez le résultat de l’invite à l’étape suivante avec stepContext.Result. Dans les coulisses, les invites constituent une boîte de dialogue en deux étapes. Tout d'abord, l'invite demande une entrée. Ensuite, elle retourne la valeur valide ou redémarre depuis le début avec une nouvelle invite jusqu'à ce qu'elle reçoive une entrée valide.

Vous devez toujours retourner une valeur DialogTurnResult non Null à partir d’une étape en cascade. À défaut, votre dialogue risque de ne pas fonctionner comme prévu. L'exemple ci-dessous illustre l'implémentation pour NameStepAsync dans le dialogue en cascade.

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

Dans AgeStepAsync, spécifiez une nouvelle invite au cas où la validation de l'entrée de l'utilisateur échouerait, soit parce que son format ne peut pas être analysé par l'invite, soit parce que l'entrée ne remplit pas un critère de validation. Dans ce cas, si aucune nouvelle invite n'a été fournie, l'invite utilise le texte d'invite initial pour redemander à l'utilisateur d'effectuer une entrée

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

Le mode de transport, le nom et l’âge de l’utilisateur sont enregistrés dans une instance de la 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

Au cours de la dernière étape, vérifiez le stepContext.Result retourné par le dialogue appelé à l'étape précédente en cascade. Si la valeur renvoyée est true, l'accesseur au profil utilisateur obtient et met à jour le profil de l'utilisateur. Pour obtenir le profil utilisateur, appelez GetAsync, puis définissez les valeurs des propriétés userProfile.Transport, userProfile.Name, userProfile.Age et userProfile.Picture. Enfin, récapitulez les informations à l'attention de l'utilisateur avant d'appeler EndDialogAsync, qui met fin au dialogue. La fin du dialogue se traduit par son retrait de la pile des dialogues et le retour d’un résultat facultatif à son parent. Le parent est le dialogue ou la méthode ayant démarré le dialogue qui vient de prendre fin.

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

Exécuter le dialogue

Bots\DialogBot.cs

Le gestionnaire OnMessageActivityAsync utilise la méthode RunAsync pour démarrer ou continuer le dialogue. OnTurnAsync utilise les objets de gestion de l'état du bot pour conserver tous les changements d'état dans le stockage. La méthode ActivityHandler.OnTurnAsync appelle les différentes méthodes de gestionnaire d’activités, par exemple OnMessageActivityAsync. De cette manière, l'état est sauvegardé une fois que le gestionnaire de message est terminé, mais avant que le tour lui-même ne se termine.

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

Inscrire les services pour le bot

Ce bot utilise les services suivants :

  • Services de base pour un bot : un fournisseur d’informations d’identification, un adaptateur et l’implémentation du bot.
  • Services pour gérer l’état : le stockage, l’état utilisateur et l’état de conversation.
  • Le dialogue que le bot va utiliser.

Startup.cs

Enregistrez les services pour le bot dans Startup. Ces services sont disponibles pour d’autres parties du code par le biais de l’injection de dépendances.

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

Remarque

Le stockage mémoire est utilisé uniquement à des fins de test et n'est pas destiné à une utilisation en production. Veillez à utiliser un type de stockage permanent pour un bot de production.

Tester votre bot

  1. Si ce n'est déjà fait, installez Bot Framework Emulator.
  2. Exécutez l’exemple en local sur votre machine.
  3. Démarrez l’émulateur, connectez-vous à votre bot et envoyez des messages, comme indiqué ci-dessous.

An example transcript of a conversation with the multi-turn prompt bot.

Informations supplémentaires

Au sujet du dialogue et de l’état du bot

Dans ce bot, deux accesseurs de propriété d'état sont définis :

  • Un créé dans l’état de conversation pour la propriété d’état de dialogue. L'état du dialogue permet de savoir où se trouve l'utilisateur dans les dialogues d'un jeu de dialogues. Il est ensuite mis à jour par le contexte du dialogue, par exemple lorsque les méthodes début de dialogue ou suite de dialogue sont appelées.
  • Un créé dans l’état de l’utilisateur de la propriété de profil utilisateur. Le bot l'utilise pour suivre les informations qu'il possède sur l'utilisateur. Vous devez donc gérer cet état de manière explicite dans le code du dialogue.

Les méthodes obtenir et définir d’un accesseur de propriété d’état obtiennent et définissent la valeur de la propriété dans le cache de l’objet de gestion d’états. Le cache est rempli la première fois que la valeur d’une propriété d’état est demandée dans un tour, mais il doit être conservé explicitement. Afin de conserver les modifications apportées à ces deux propriétés d'état, un appel à la méthode enregistrer les modifications, de l'objet de gestion d'état correspondant, est effectué.

Cet exemple met à jour l’état du profil utilisateur dans le dialogue. Cette pratique peut fonctionner pour certains bots, mais elle ne fonctionnera pas si vous souhaitez réutiliser un dialogue entre bots.

Il existe différentes options pour séparer les étapes de dialogue de l’état du bot. Par exemple, une fois que votre dialogue a collecté des informations complètes, vous pouvez effectuer les actions suivantes :

  • Utiliser la méthode terminer un dialogue pour fournir les données collectées en tant que valeurs renvoyées au contexte parent. Les classes d'invite sont conçues de la même manière que le gestionnaire de tour du bot ou un dialogue actif précédent dans la pile de dialogue.
  • Générer une demande vers un service approprié. Cela peut fonctionner correctement si votre bot agit en tant que serveur frontal vers un service plus volumineux.

Définition d'une méthode de validation d'invite

UserProfileDialog.cs

Vous trouverez ci-dessous un exemple de code de validation pour la définition de la méthode AgePromptValidatorAsync. promptContext.Recognized.Value contient la valeur analysée, qui est ici un entier pour l'invite numérique. promptContext.Recognized.Succeeded indique si l'invite a pu analyser l'entrée de l'utilisateur. Le validateur doit renvoyer false pour indiquer que la valeur n'a pas été acceptée et une nouvelle invite doit être adressée à l'utilisateur ; dans le cas contraire, renvoyez true pour accepter l'entrée et revenir à la fenêtre d'invite. Vous pouvez modifier la valeur figurant dans le validateur selon votre scénario.

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

Étapes suivantes