Behandeln von BenutzerunterbrechungenHandle user interruptions

gilt für: SDK v4APPLIES TO: SDK v4

Die Behandlung von Unterbrechungen ist ein wichtiger Aspekt eines stabilen Bots.Handling interruptions is an important aspect of a robust bot. Benutzer folgen nicht immer Schritt für Schritt Ihrem definierten Konversationsfluss.Users will not always follow your defined conversation flow, step by step. Manche versuchen ggf., während eines Prozesses eine Frage zu stellen, oder möchten den Prozess vorzeitig abbrechen.They may try to ask a question in the middle of the process, or simply want to cancel it instead of completing it. In diesem Thema werden einige häufige Verfahren zur Behandlung von Benutzerunterbrechungen in Ihrem Bot beschrieben.In this topic, this topic describes some common ways to handle user interruptions in your bot.

VoraussetzungenPrerequisites

Informationen zu diesem BeispielAbout this sample

Im Beispiel dieses Artikels wird ein Bot für Flugbuchungen modelliert, der Dialoge verwendet, um Fluginformationen vom Benutzer zu erhalten.The sample used in this article models a flight booking bot that uses dialogs to get flight information from the user. Während der Konversation mit dem Bot kann der Benutzer mithilfe der Befehle help (Hilfe) und cancel (Abbrechen) jederzeit eine Unterbrechung auslösen.At any time during the conversation with the bot, the user can issue help or cancel commands to cause an interruption. Hier werden zwei Arten von Unterbrechungen behandelt:There are two types of interruptions handled:

  • Turn-Ebene: Die Verarbeitung wird auf der Turn-Ebene umgangen, der Dialog verbleibt jedoch im Stapel, und die angegebenen Informationen bleiben erhalten.Turn level: Bypass processing at the turn level but leave the dialog on the stack with the information that was provided. Beim nächsten Turn wird an dem Punkt fortgefahren, an dem die Konversation abgebrochen wurde.In the next turn, continue from where the conversation left off.
  • Dialogebene: Die Verarbeitung wird vollständig abgebrochen, und der Bot kann ganz von vorn beginnen.Dialog level: Cancel the processing completely, so the bot can start all over again.

Definieren und Implementieren der UnterbrechungslogikDefine and implement the interruption logic

Als Erstes definieren und implementieren Sie die Unterbrechungen durch help (Hilfe) und cancel (Abbrechen).First, define and implement the help and cancel interruptions.

Installieren Sie das NuGet-Paket Microsoft.Bot.Builder.Dialogs, um Dialoge verwenden zu können.To use dialogs, install the Microsoft.Bot.Builder.Dialogs NuGet package.

Dialogs\CancelAndHelpDialog.csDialogs\CancelAndHelpDialog.cs

Implementieren Sie die CancelAndHelpDialog-Klasse für die Verarbeitung der Benutzerunterbrechungen.Implement the CancelAndHelpDialog class to handle user interruptions. Die Dialoge BookingDialog und DateResolverDialog, die abgebrochen werden können, werden von dieser Klasse abgeleitet.The cancellable dialogs, BookingDialog and DateResolverDialog derive from this class.

public class CancelAndHelpDialog : ComponentDialog

In der CancelAndHelpDialog-Klasse ruft die OnContinueDialogAsync-Methode die InterruptAsync-Methode auf, um zu überprüfen, ob der Benutzer den normalen Konversationsfluss unterbrochen hat.In the CancelAndHelpDialog class the OnContinueDialogAsync method calls the InterruptAsync method to check if the user has interrupted the normal flow. Wurde der Konversationsfluss unterbrochen, werden Basisklassenmethoden aufgerufen. Andernfalls wird der Rückgabewert von InterruptAsync zurückgegeben.If the flow is interrupted, base class methods are called; otherwise, the return value from the InterruptAsync is returned.

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

Wenn der Benutzer „help“ (Hilfe) eingibt, sendet die Methode InterruptAsync eine Nachricht und ruft dann DialogTurnResult (DialogTurnStatus.Waiting) auf, um anzugeben, dass der übergeordnete Dialog auf eine Antwort des Benutzers wartet.If the user types "help", the InterruptAsync method sends a message and then calls DialogTurnResult (DialogTurnStatus.Waiting) to indicate that the dialog on top is waiting for a response from the user. Der Konversationsfluss wird somit nur für einen einzelnen Turn unterbrochen und beim nächsten Turn an dem Punkt fortgesetzt, an dem er abgebrochen wurde.In this way, the conversation flow is interrupted for a turn only, and the next turn continues from where the conversation left off.

Wenn der Benutzer „cancel“ (Abbrechen) eingibt, wird CancelAllDialogsAsync für den inneren Dialogkontext aufgerufen. Daraufhin wird der dazugehörige Dialogstapel gelöscht, und der Vorgang wird mit einem Abbruchstatus und ohne Ergebniswert beendet.If the user types "cancel", it calls CancelAllDialogsAsync on its inner dialog context, which clears its dialog stack and causes it to exit with a cancelled status and no result value. Für MainDialog (später zu sehen) stellt es sich so dar, dass der Buchungsdialog beendet wurde und NULL zurückgegeben hat – ähnlich wie wenn der Benutzer die Buchung nicht bestätigt.To the MainDialog (shown later on), it will appear that the booking dialog ended and returned null, similar to when the user chooses not to confirm their booking.

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

Überprüfen auf Unterbrechungen bei jedem TurnCheck for interruptions each turn

Nachdem die Klasse für die Verarbeitung von Unterbrechungen implementiert wurde, sollten Sie überprüfen, was passiert, wenn dieser Bot eine neue Nachricht vom Benutzer empfängt.Once the interrupt handling class is implemented, review what happens when this bot receives a new message from the user.

Dialogs\MainDialog.csDialogs\MainDialog.cs

Bei Eingang der neuen Nachrichtenaktivität führt der Bot MainDialog aus.As the new message activity arrives, the bot runs the MainDialog. MainDialog fragt, wie dem Benutzer geholfen werden kann.The MainDialog prompts the user for what it can help with. Anschließend wird BookingDialog in der Methode MainDialog.ActStepAsync mit einem Aufruf von BeginDialogAsync gestartet, wie im Anschluss zu sehen:And then it starts the BookingDialog in the MainDialog.ActStepAsync method, with a call to BeginDialogAsync as shown below.

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

Der Buchungsdialog endet in der Methode FinalStepAsync der Klasse MainDialog, und die Buchung ist entweder abgeschlossen oder wurde abgebrochen.Next, in the FinalStepAsync method of the MainDialog class, the booking dialog ended and the booking is considered to be complete or cancelled.

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

Der Code in BookingDialog wird hier nicht gezeigt, da er nicht direkt mit der Behandlung von Unterbrechungen zusammenhängt.The code in BookingDialog is not shown here as it is not directly related to interruption handling. Er dient dazu, den Benutzer zur Eingabe von Buchungsdetails aufzufordern.It is used to prompt users for booking details. Den entsprechenden Code finden Sie unter Dialogs\BookingDialogs.cs.You can find that code in Dialogs\BookingDialogs.cs.

Behandeln unerwarteter FehlerHandle unexpected errors

Der Fehlerhandler des Adapters verarbeitet alle Ausnahmen, die im Bot nicht abgefangen wurden.The adapter's error handler handles any exceptions that were not caught in the bot.

AdapterWithErrorHandler.csAdapterWithErrorHandler.cs

Im Beispiel empfängt der Handler des OnTurnError Adapters alle Ausnahmen, die von der Turn-Logik Ihres Bots ausgelöst werden.In the sample, the adapter's OnTurnError handler receives any exceptions thrown by your bot's turn logic. Wenn eine Ausnahme ausgelöst wird, löscht der Handler den Konversationszustand für die aktuelle Konversation, um zu verhindern, dass der Bot in einer Fehlerschleife hängen bleibt, die durch einen fehlerhaften Zustand verursacht wurde.If there is an exception thrown, the handler deletes the conversation state for the current conversation to prevent the bot from getting stuck in an error-loop caused by being in a bad state.

OnTurnError = async (turnContext, exception) =>
{
    // 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");
};

Registrieren von DienstenRegister services

Startup.csStartup.cs

Der Bot wird abschließend in Startup.cs als kurzlebige Instanz erstellt, und bei jedem Turn wird eine neue Instanz des Bots erstellt.Finally, in Startup.cs, the bot is created as a transient, and on every turn, a new instance of the bot is created.

// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
services.AddTransient<IBot, DialogAndWelcomeBot<MainDialog>>();

Zu Referenzzwecken finden Sie im Anschluss die Klassendefinitionen, die im Aufruf zum Erstellen des obigen Bots verwendet werden:For reference, here are the class definitions that are used in the call to create the bot above.

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

So testen Sie den BotTo test the bot

  1. Installieren Sie Bot Framework Emulator (sofern noch nicht geschehen).If you have not done so already, install the Bot Framework Emulator.
  2. Führen Sie das Beispiel lokal auf Ihrem Computer aus.Run the sample locally on your machine.
  3. Starten Sie den Emulator, stellen Sie eine Verbindung mit Ihrem Bot her, und senden Sie Nachrichten wie unten dargestellt.Start the Emulator, connect to your bot, and send messages as shown below.

Zusätzliche InformationenAdditional information

  • Das Beispiel 24.bot-authentication-msgraph in C#, JavaScriptoder Python zeigt, wie eine Abmeldeanforderung verarbeitet wird.The 24.bot-authentication-msgraph sample in C#, JavaScript, or Python shows how to handle a logout request. Es wird ein Muster verwendet, das dem hier gezeigten ähnelt, um Unterbrechungen zu verarbeiten.It uses a pattern similar to the one shown here for handling interruptions.

  • Es empfiehlt sich, eine Standardantwort zu senden, anstatt nichts zu unternehmen und den Benutzer ratlos zurückzulassen.You should send a default response instead of doing nothing and leaving the user wondering what is going on. Die Standardantwort sollte dem Benutzer mitteilen, welche Befehle der Bot versteht, sodass der Benutzer wieder in den Prozess hineinfindet.The default response should tell the user what commands the bot understands so the user can get back on track.

  • Die Eigenschaft responded des Turn-Kontexts gibt jederzeit Aufschluss darüber, ob der Bot während des aktuellen Turns eine Nachricht an den Benutzer gesendet hat.At any point in the turn, the turn context's responded property indicates whether the bot has sent a message to the user this turn. Vor dem Ende des Turns sollte Ihr Bot eine Nachricht an den Benutzer senden, auch wenn es sich dabei nur um eine einfache Bestätigung der Eingabe handelt.Before the turn ends, your bot should send some message to the user, even if it is a simple acknowledgement of their input.