Créer vos propres invites pour collecter des entrées utilisateur

S'APPLIQUE À : SDK v4

Une conversation entre un bot et un utilisateur implique souvent une demande (invite) d’informations à l’utilisateur, l’analyse de sa réponse et l’action en fonction des informations obtenues. Votre bot doit suivre le contexte d’une conversation pour pouvoir gérer son comportement et se rappeler des réponses aux questions précédentes. L’état d’un bot représente des informations qu’il suit pour répondre correctement aux messages entrants.

Conseil

La bibliothèque de dialogues propose des invites prédéfinies fournissant de nouvelles fonctionnalités que les utilisateurs peuvent utiliser. Vous trouverez des exemples de ces invites dans l’article Implémenter des flux de conversation séquentiels.

Remarque

Les kits 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.

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 Microsoft Copilot Studio et lisez-en plus sur le choix de la solution copilote appropriée.

Pour plus d’informations, consultez Les futures versions de bot.

Prérequis

Au sujet de l’exemple de code

L’exemple de bot pose à l’utilisateur une série de questions, valide certaines de ses réponses et enregistre son entrée. Le diagramme suivant montre la relation entre le bot, le profil utilisateur et les classes de flux de conversation.

Diagramme de classes pour l’exemple C#.

  • Une classe UserProfile pour les informations utilisateur qui seront collectées par le bot.
  • Une classe ConversationFlow pour contrôler l’état de notre conversation pendant la collecte des informations utilisateur.
  • Une énumération ConversationFlow.Question interne pour le suivi d'où vous en êtes dans la conversation.

L'état de l'utilisateur permet de connaître son nom, son âge et la date choisie, tandis que l'état de la conversation effectue le suivi de la dernière question posée à l'utilisateur. Étant donné que vous ne prévoyez de déployer ce bot, vous allez configurer un état d'utilisateur et de conversation pour utiliser le stockage en mémoire.

Vous utilisez le gestionnaire de tour de messages du bot ainsi que les propriétés d'état utilisateur et de conversion pour gérer le flux de la conversation et la collection d'entrée. Dans notre bot, nous allons enregistrer les informations de propriétés d'état reçues pendant chaque itération du gestionnaire de tour de messages.

Créer des objets utilisateur et de conversation

Créez les objets d’état utilisateur et de conversation au démarrage et consommez-les via l’injection de dépendance du constructeur de bot.

Startup.cs

// 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.
services.AddSingleton<UserState>();

// Create the Conversation state.
services.AddSingleton<ConversationState>();

Bots/CustomPromptBot.cs

private readonly BotState _userState;
private readonly BotState _conversationState;

public CustomPromptBot(ConversationState conversationState, UserState userState)
{
    _conversationState = conversationState;
    _userState = userState;
}

Créer des accesseurs de propriété

Créez des accesseurs de propriété pour les propriétés du profil utilisateur et du flux de conversation, puis appelez GetAsync pour récupérer la valeur de la propriété à partir de l’état.

Bots/CustomPromptBot.cs

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    var conversationStateAccessors = _conversationState.CreateProperty<ConversationFlow>(nameof(ConversationFlow));
    var flow = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationFlow(), cancellationToken);

    var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
    var profile = await userStateAccessors.GetAsync(turnContext, () => new UserProfile(), cancellationToken);

Avant la fin du tour, appelez SaveChangesAsync pour écrire les modifications d’état dans le stockage.

    await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
    await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
}

Gestionnaire de messages

Lors de la gestion des activités de message, le gestionnaire de messages utilise une méthode d’assistance pour gérer la conversation et afficher des invites pour l’utilisateur. La méthode d’assistance est décrite dans la section suivante.

Bots/CustomPromptBot.cs

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    var conversationStateAccessors = _conversationState.CreateProperty<ConversationFlow>(nameof(ConversationFlow));
    var flow = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationFlow(), cancellationToken);

    var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
    var profile = await userStateAccessors.GetAsync(turnContext, () => new UserProfile(), cancellationToken);

    await FillOutUserProfileAsync(flow, profile, turnContext, cancellationToken);

    // Save changes.
    await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
    await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
}

Remplissage du profil utilisateur

Le bot invite l’utilisateur à fournir des informations, en fonction de la question que le bot a posée lors du tour précédent (le cas échéant). L’entrée est analysée à l’aide d’une méthode de validation.

Chaque méthode de validation fonctionne plus ou moins de la même manière :

  • La valeur de retour indique si l’entrée est une réponse valide pour cette question.
  • Si la validation réussit, elle génère une valeur analysée et normalisée pour l’enregistrer.
  • Si la validation échoue, elle génère un message avec lequel le bot peut redemander les informations.

Les méthodes de validation sont décrites dans la section suivante.

Bots/CustomPromptBot.cs

{
    var input = turnContext.Activity.Text?.Trim();
    string message;

    switch (flow.LastQuestionAsked)
    {
        case ConversationFlow.Question.None:
            await turnContext.SendActivityAsync("Let's get started. What is your name?", null, null, cancellationToken);
            flow.LastQuestionAsked = ConversationFlow.Question.Name;
            break;
        case ConversationFlow.Question.Name:
            if (ValidateName(input, out var name, out message))
            {
                profile.Name = name;
                await turnContext.SendActivityAsync($"Hi {profile.Name}.", null, null, cancellationToken);
                await turnContext.SendActivityAsync("How old are you?", null, null, cancellationToken);
                flow.LastQuestionAsked = ConversationFlow.Question.Age;
                break;
            }
            else
            {
                await turnContext.SendActivityAsync(message ?? "I'm sorry, I didn't understand that.", null, null, cancellationToken);
                break;
            }

        case ConversationFlow.Question.Age:
            if (ValidateAge(input, out var age, out message))
            {
                profile.Age = age;
                await turnContext.SendActivityAsync($"I have your age as {profile.Age}.", null, null, cancellationToken);
                await turnContext.SendActivityAsync("When is your flight?", null, null, cancellationToken);
                flow.LastQuestionAsked = ConversationFlow.Question.Date;
                break;
            }
            else
            {
                await turnContext.SendActivityAsync(message ?? "I'm sorry, I didn't understand that.", null, null, cancellationToken);
                break;
            }

        case ConversationFlow.Question.Date:
            if (ValidateDate(input, out var date, out message))
            {
                profile.Date = date;
                await turnContext.SendActivityAsync($"Your cab ride to the airport is scheduled for {profile.Date}.");
                await turnContext.SendActivityAsync($"Thanks for completing the booking {profile.Name}.");
                await turnContext.SendActivityAsync($"Type anything to run the bot again.");
                flow.LastQuestionAsked = ConversationFlow.Question.None;
                profile = new UserProfile();
                break;
            }
            else
            {
                await turnContext.SendActivityAsync(message ?? "I'm sorry, I didn't understand that.", null, null, cancellationToken);
                break;
            }
    }
}

Analyser et valider l’entrée

Le bot utilise les critères suivants pour valider l’entrée.

  • Le nom doit être une chaîne non vide. Il est normalisé quand les espaces blancs sont supprimés.
  • L’âge doit être une valeur comprise entre 18 et 120. Il est normalisé quand un nombre entier est retourné.
  • La date doit être de n’importe quelle date ou heure située au moins une heure dans le futur. Elle est normalisée quand seule la partie concernant la date de l’entrée analysée est retournée.

Remarque

Pour l'âge et la date d'entrée, l'échantillon d'utilisation fait appel aux bibliothèques Microsoft/Recognizers-Text pour effectuer l'analyse initiale. Il s'agit simplement d'une façon d'analyser l'entrée. Pour plus d'informations sur ces bibliothèques, consultez le fichier LISEZMOI du projet.

Bots/CustomPromptBot.cs

private static bool ValidateName(string input, out string name, out string message)
{
    name = null;
    message = null;

    if (string.IsNullOrWhiteSpace(input))
    {
        message = "Please enter a name that contains at least one character.";
    }
    else
    {
        name = input.Trim();
    }

    return message is null;
}

private static bool ValidateAge(string input, out int age, out string message)
{
    age = 0;
    message = null;

    // Try to recognize the input as a number. This works for responses such as "twelve" as well as "12".
    try
    {
        // Attempt to convert the Recognizer result to an integer. This works for "a dozen", "twelve", "12", and so on.
        // The recognizer returns a list of potential recognition results, if any.

        var results = NumberRecognizer.RecognizeNumber(input, Culture.English);

        foreach (var result in results)
        {
            // The result resolution is a dictionary, where the "value" entry contains the processed string.
            if (result.Resolution.TryGetValue("value", out var value))
            {
                age = Convert.ToInt32(value);
                if (age >= 18 && age <= 120)
                {
                    return true;
                }
            }
        }

        message = "Please enter an age between 18 and 120.";
    }
    catch
    {
        message = "I'm sorry, I could not interpret that as an age. Please enter an age between 18 and 120.";
    }

    return message is null;
}

private static bool ValidateDate(string input, out string date, out string message)
{
    date = null;
    message = null;

    // Try to recognize the input as a date-time. This works for responses such as "11/14/2018", "9pm", "tomorrow", "Sunday at 5pm", and so on.
    // The recognizer returns a list of potential recognition results, if any.
    try
    {
        var results = DateTimeRecognizer.RecognizeDateTime(input, Culture.English);

        // Check whether any of the recognized date-times are appropriate,
        // and if so, return the first appropriate date-time. We're checking for a value at least an hour in the future.
        var earliest = DateTime.Now.AddHours(1.0);

        foreach (var result in results)
        {
            // The result resolution is a dictionary, where the "values" entry contains the processed input.
            var resolutions = result.Resolution["values"] as List<Dictionary<string, string>>;

            foreach (var resolution in resolutions)
            {
                // The processed input contains a "value" entry if it is a date-time value, or "start" and
                // "end" entries if it is a date-time range.
                if (resolution.TryGetValue("value", out var dateString)
                    || resolution.TryGetValue("start", out dateString))
                {
                    if (DateTime.TryParse(dateString, out var candidate)
                        && earliest < candidate)
                    {
                        date = candidate.ToShortDateString();
                        return true;
                    }
                }
            }
        }

        message = "I'm sorry, please enter a date at least an hour out.";
    }
    catch
    {
        message = "I'm sorry, I could not interpret that as an appropriate date. Please enter a date at least an hour out.";
    }

    return false;
}

Testez le bot localement

Téléchargez et installez Bot Framework Emulator pour tester le bot localement.

  1. Exécutez l’exemple en local sur votre machine. Pour obtenir plus d'instructions, reportez-vous au fichier README de l'échantillon C#, de l'échantillon JS ou de l'échantillon Python.
  2. Testez-le à l’aide de l’émulateur.

Ressources supplémentaires

La bibliothèque de boîtes de dialogue fournit des classes qui automatisent de nombreux aspects de la gestion des conversations.

Étape suivante