Share via


Gérer les interruptions de l’utilisateur

S'APPLIQUE À : SDK v4

La gestion des interruptions représente un aspect essentiel d’un bot efficace. Les utilisateurs ne suivront pas toujours votre flux de conversation défini, étape par étape. Ils peuvent essayer de poser une question au milieu du processus ou souhaitez tout simplement l’annuler au lieu d’aller jusqu’à la fin. Cet article présente quelques méthodes courantes pour gérer les interruptions de l'utilisateur dans votre bot.

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 Power Virtual Agents et découvrez comment choisir la solution de chatbot appropriée.

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

Prérequis

L'exemple de bot principal utilise compréhension du langage (LUIS) pour identifier les intentions utilisateur ; toutefois, l'identification de l'intention de l'utilisateur n'est pas le focus de cet article. Pour plus d'informations sur l'identification des intentions utilisateur, consultez La compréhension du langage naturel et ajoutez la compréhension du langage naturel à votre bot.

Remarque

Compréhension du langage (LUIS) sera mis hors service le 1er octobre 2025. À compter du 1er avril 2023, vous ne pourrez pas créer de nouvelles ressources LUIS. Une version plus récente de Compréhension du langage est désormais disponible dans le cadre d'Azure AI Language.

Compréhension du langage courant (CLU), une fonctionnalité d’Azure AI Language, est la version mise à jour de LUIS. Pour plus d'informations sur la prise en charge de compréhension du langage dans le kit de développement logiciel (SDK) Bot Framework, consultez Compréhension du langage naturel.

À propos de cet exemple

L’exemple utilisé dans cet article modélise un bot de réservation de vol d’avion qui utilise des dialogues pour obtenir des informations sur le vol auprès de l’utilisateur. À tout moment pendant la conversation avec le bot, l’utilisateur peut émettre des commandes d’aide ou d’annulation pour provoquer une interruption. Nous gérons deux types d’interruptions :

  • Niveau du tour : ignorez le traitement au niveau du tour, mais laissez le dialogue sur la pile avec les informations qui ont été fournies. Au tour suivant, reprenez là où la conversation s’est arrêtée.
  • Niveau du dialogue : annulez complètement le traitement, afin que le bot puisse recommencer.

Définir et implémenter la logique d’interruption

Tout d’abord, définissez et implémentez les interruptions help (aide) et cancel (annulation).

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

Dialogs\CancelAndHelpDialog.cs

Implémentez la classe CancelAndHelpDialog pour gérer les interruptions par l’utilisateur. Les dialogues annulables BookingDialog et DateResolverDialog découlent de cette classe.

public class CancelAndHelpDialog : ComponentDialog

Dans la classe CancelAndHelpDialog, la méthode OnContinueDialogAsync appelle la méthode InterruptAsync pour vérifier si l'utilisateur a interrompu le flux normal. Si le flux est interrompu, les méthodes de classe de base sont appelées. Sinon, la valeur de retour de InterruptAsync est retourné.

protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
    var result = await InterruptAsync(innerDc, cancellationToken);
    if (result != null)
    {
        return result;
    }

    return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}

Si l’utilisateur tape « aide », la méthode InterruptAsync envoie un message, puis appelle DialogTurnResult (DialogTurnStatus.Waiting) pour indiquer que le dialogue en haut de la pile attend une réponse de l’utilisateur. Ainsi, le flux de conversation est interrompu pour un seul tour. Le tour suivant reprend là où la conversation s’est arrêtée.

Si l'utilisateur tape « annuler », elle appelle CancelAllDialogsAsync sur son contexte de dialogue interne, ce qui efface sa pile de dialogues et entraîne sa fermeture avec un état annulé et aucune valeur de résultat. Pour MainDialog (illustré plus tard), il apparaît que le dialogue de réservation s’est terminé et a retourné Null, comme quand l’utilisateur choisit de ne pas confirmer sa réservation.

private async Task<DialogTurnResult> InterruptAsync(DialogContext innerDc, CancellationToken cancellationToken)
{
    if (innerDc.Context.Activity.Type == ActivityTypes.Message)
    {
        var text = innerDc.Context.Activity.Text.ToLowerInvariant();

        switch (text)
        {
            case "help":
            case "?":
                var helpMessage = MessageFactory.Text(HelpMsgText, HelpMsgText, InputHints.ExpectingInput);
                await innerDc.Context.SendActivityAsync(helpMessage, cancellationToken);
                return new DialogTurnResult(DialogTurnStatus.Waiting);

            case "cancel":
            case "quit":
                var cancelMessage = MessageFactory.Text(CancelMsgText, CancelMsgText, InputHints.IgnoringInput);
                await innerDc.Context.SendActivityAsync(cancelMessage, cancellationToken);
                return await innerDc.CancelAllDialogsAsync(cancellationToken);
        }
    }

    return null;
}

Rechercher des interruptions à chaque tour

Une fois la classe de gestion des interruptions implémentée, passez en revue ce qui se passe quand ce bot reçoit un nouveau message de l’utilisateur.

Dialogs\MainDialog.cs

Au moment où la nouvelle activité de message arrive, le bot exécute MainDialog. MainDialog invite l’utilisateur à indiquer ce qu’il veut. Ensuite, BookingDialog est démarré dans la méthode MainDialog.ActStepAsync, avec un appel à BeginDialogAsync comme indiqué ci-dessous.

private async Task<DialogTurnResult> ActStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    if (!_luisRecognizer.IsConfigured)
    {
        // LUIS is not configured, we just run the BookingDialog path with an empty BookingDetailsInstance.
        return await stepContext.BeginDialogAsync(nameof(BookingDialog), new BookingDetails(), cancellationToken);
    }

    // Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.)
    var luisResult = await _luisRecognizer.RecognizeAsync<FlightBooking>(stepContext.Context, cancellationToken);
    switch (luisResult.TopIntent().intent)
    {
        case FlightBooking.Intent.BookFlight:
            await ShowWarningForUnsupportedCities(stepContext.Context, luisResult, cancellationToken);

            // Initialize BookingDetails with any entities we may have found in the response.
            var bookingDetails = new BookingDetails()
            {
                // Get destination and origin from the composite entities arrays.
                Destination = luisResult.ToEntities.Airport,
                Origin = luisResult.FromEntities.Airport,
                TravelDate = luisResult.TravelDate,
            };

            // Run the BookingDialog giving it whatever details we have from the LUIS call, it will fill out the remainder.
            return await stepContext.BeginDialogAsync(nameof(BookingDialog), bookingDetails, cancellationToken);

        case FlightBooking.Intent.GetWeather:
            // We haven't implemented the GetWeatherDialog so we just display a TODO message.
            var getWeatherMessageText = "TODO: get weather flow here";
            var getWeatherMessage = MessageFactory.Text(getWeatherMessageText, getWeatherMessageText, InputHints.IgnoringInput);
            await stepContext.Context.SendActivityAsync(getWeatherMessage, cancellationToken);
            break;

        default:
            // Catch all for unhandled intents
            var didntUnderstandMessageText = $"Sorry, I didn't get that. Please try asking in a different way (intent was {luisResult.TopIntent().intent})";
            var didntUnderstandMessage = MessageFactory.Text(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IgnoringInput);
            await stepContext.Context.SendActivityAsync(didntUnderstandMessage, cancellationToken);
            break;
    }

    return await stepContext.NextAsync(null, cancellationToken);
}

Par la suite, dans la méthode FinalStepAsync de la classe MainDialog, le dialogue de réservation se termine et la réservation est considérée comme terminée ou annulée.

private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    // If the child dialog ("BookingDialog") was cancelled, the user failed to confirm or if the intent wasn't BookFlight
    // the Result here will be null.
    if (stepContext.Result is BookingDetails result)
    {
        // Now we have all the booking details call the booking service.

        // If the call to the booking service was successful tell the user.

        var timeProperty = new TimexProperty(result.TravelDate);
        var travelDateMsg = timeProperty.ToNaturalLanguage(DateTime.Now);
        var messageText = $"I have you booked to {result.Destination} from {result.Origin} on {travelDateMsg}";
        var message = MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput);
        await stepContext.Context.SendActivityAsync(message, cancellationToken);
    }

    // Restart the main dialog with a different message the second time around
    var promptMessage = "What else can I do for you?";
    return await stepContext.ReplaceDialogAsync(InitialDialogId, promptMessage, cancellationToken);
}

Le code dans BookingDialog n'apparaît pas ici, car il n'est pas directement lié à la gestion des interruptions. Il est utilisé pour demander aux utilisateurs les détails sur la réservation. Vous trouverez ce code dans Dialogs\BookingDialogs.cs.

Gérer les erreurs inattendues

Le gestionnaire d'erreurs de l'adaptateur gère toutes les exceptions qui n'ont pas été interceptées dans le bot.

AdapterWithErrorHandler.cs

Dans l'échantillon, le gestionnaire OnTurnError de l'adaptateur reçoit toutes les exceptions levées par la logique de tour de votre bot. Si une exception est levée, le gestionnaire supprime l'état de la conversation active pour empêcher le bot de rester bloqué dans une boucle d'erreur provoquée par un état incorrect.

    {
        // Log any leaked exception from the application.
        // NOTE: In production environment, you should consider logging this to
        // Azure Application Insights. Visit https://aka.ms/bottelemetry to see how
        // to add telemetry capture to your bot.
        logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");

        // Send a message to the user
        var errorMessageText = "The bot encountered an error or bug.";
        var errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.IgnoringInput);
        await turnContext.SendActivityAsync(errorMessage);

        errorMessageText = "To continue to run this bot, please fix the bot source code.";
        errorMessage = MessageFactory.Text(errorMessageText, errorMessageText, InputHints.ExpectingInput);
        await turnContext.SendActivityAsync(errorMessage);

        if (conversationState != null)
        {
            try
            {
                // Delete the conversationState for the current conversation to prevent the
                // bot from getting stuck in a error-loop caused by being in a bad state.
                // ConversationState should be thought of as similar to "cookie-state" in a Web pages.
                await conversationState.DeleteAsync(turnContext);
            }
            catch (Exception e)
            {
                logger.LogError(e, $"Exception caught on attempting to Delete ConversationState : {e.Message}");
            }
        }

        // Send a trace activity, which will be displayed in the Bot Framework Emulator
        await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError");
    };
}

Inscrire des services

Startup.cs

Enfin, dans Startup.cs, le bot est créé en tant que bot passager, et à chaque tour, une nouvelle instance du bot est créée.


// Register the BookingDialog.

À titre de référence, voici les définitions de classe utilisées dans l’appel pour créer le bot ci-dessus.

public class DialogAndWelcomeBot<T> : DialogBot<T>
public class DialogBot<T> : ActivityHandler
    where T : Dialog
public class MainDialog : ComponentDialog

Tester le bot

  1. Si vous ne l'avez pas encore 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.

Informations supplémentaires

  • L'échantillon 24.bot-authentication-msgraph en C#, JavaScript ou PythonJava montre comment gérer une demande de déconnexion. Il utilise un modèle similaire à celui présenté ici pour gérer les interruptions.

  • Envoyez une réponse par défaut au lieu de ne rien faire et de laisser l’utilisateur dans l’interrogation. La réponse par défaut doit indiquer à l’utilisateur les commandes comprises par le bot, de sorte qu’il puisse revenir sur la bonne voie.

  • À tout moment dans le tour, la propriété responded du contexte du tour indique si le bot a envoyé un message à l’utilisateur lors de ce tour. Avant la fin du tour, votre bot doit envoyer un message à l'utilisateur, même s'il s'agit d'un simple accusé de réception de son entrée.