Behandeln von Benutzerunterbrechungen

GILT FÜR: SDK v4

Die Behandlung von Unterbrechungen ist ein wichtiger Aspekt eines stabilen Bots. Benutzer folgen nicht immer Schritt für Schritt Ihrem definierten Konversationsfluss. Manche versuchen ggf., während eines Prozesses eine Frage zu stellen, oder möchten den Prozess vorzeitig abbrechen. Dieser Artikel beschreibt einige häufige Verfahren zur Behandlung von Benutzerunterbrechungen in Ihrem Bot.

Hinweis

Die JavaScript-, C#- und Python-SDKs für Bot Framework werden weiterhin unterstützt, das Java-SDK wird jedoch eingestellt und der langfristige Support endet im November 2023.

Bestehende Bots, die mit dem Java SDK erstellt wurden, werden weiterhin funktionieren.

Wenn Sie einen neuen Bot erstellen möchten, sollten Sie den Einsatz von Power Virtual Agents in Betracht ziehen und sich über die Auswahl der richtigen Chatbot-Lösung informieren.

Weitere Informationen finden Sie unter Die Zukunft des Bot-Design.

Voraussetzungen

Das Core-Bot-Beispiel verwendet Language Understanding (LUIS), um Benutzerabsichten zu identifizieren. Die Identifizierung von Benutzerabsichten ist jedoch nicht der Fokus dieses Artikels. Informationen zum Identifizieren von Benutzerabsichten finden Sie unter Natürliches Sprachverständnis und Hinzufügen von natürlichem Sprachverständnis zu Ihrem Bot.

Hinweis

Language Understanding (LUIS) wird am 1. Oktober 2025 eingestellt. Ab dem 1. April 2023 können Sie keine neuen LUIS-Ressourcen erstellen. Eine neuere Version von Language Understanding ist jetzt als Teil von Azure KI Language verfügbar.

Conversational Language Understanding (CLU), ein Feature von Azure KI Language, ist die aktualisierte Version von LUIS. Weitere Informationen zur Unterstützung von Language Understanding im Bot Framework SDK finden Sie unter Natürliches Sprachverständnis.

Informationen zu diesem Beispiel

Im Beispiel dieses Artikels wird ein Bot für Flugbuchungen modelliert, der Dialoge verwendet, um Fluginformationen vom Benutzer zu erhalten. Während der Konversation mit dem Bot kann der Benutzer mithilfe der Befehle help (Hilfe) und cancel (Abbrechen) jederzeit eine Unterbrechung auslösen. Hier werden zwei Arten von Unterbrechungen behandelt:

  • Turn-Ebene: Die Verarbeitung wird auf der Turn-Ebene umgangen, der Dialog verbleibt jedoch im Stapel, und die angegebenen Informationen bleiben erhalten. Beim nächsten Turn wird an dem Punkt fortgefahren, an dem die Konversation abgebrochen wurde.
  • Dialog-Ebene: Die Verarbeitung wird vollständig abgebrochen, und der Bot kann ganz von vorn beginnen.

Definieren und Implementieren der Unterbrechungslogik

Als Erstes definieren und implementieren Sie die Unterbrechungen durch help (Hilfe) und cancel (Abbrechen).

Installieren Sie das NuGet-Paket Microsoft.Bot.Builder.Dialogs, um Dialoge verwenden zu können.

Dialogs\CancelAndHelpDialog.cs

Implementieren Sie die CancelAndHelpDialog-Klasse für die Verarbeitung der Benutzerunterbrechungen. Die Dialoge BookingDialog und DateResolverDialog, die abgebrochen werden können, werden von dieser Klasse abgeleitet.

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. Wurde der Konversationsfluss unterbrochen, werden Basisklassenmethoden aufgerufen. Andernfalls wird der Rückgabewert von InterruptAsync zurückgegeben.

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

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

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

Dialogs\MainDialog.cs

Bei Eingang der neuen Nachrichtenaktivität führt der Bot MainDialog aus. MainDialog fragt, wie dem Benutzer geholfen werden kann. Anschließend wird BookingDialog in der Methode MainDialog.ActStepAsync mit einem Aufruf von BeginDialogAsync gestartet, wie im Anschluss zu sehen:

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.

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. Er dient dazu, den Benutzer zur Eingabe von Buchungsdetails aufzufordern. Den entsprechenden Code finden Sie unter Dialogs\BookingDialogs.cs.

Behandeln unerwarteter Fehler

Der Fehlerhandler des Adapters verarbeitet alle Ausnahmen, die im Bot nicht abgefangen wurden.

AdapterWithErrorHandler.cs

Im Beispiel empfängt der Handler OnTurnError des Adapters sämtliche Ausnahmen, die von der Turn-Logik Ihres Bots ausgelöst werden. Wird eine Ausnahme ausgelöst, löscht der Handler den Konversationszustand für die aktuelle Konversation, um zu verhindern, dass der Bot in einer durch einen fehlerhaften Zustand bedingten Fehlerschleife stecken bleibt.

    {
        // 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 Diensten

Startup.cs

Der Bot wird abschließend in Startup.cs als kurzlebige Instanz erstellt, und bei jedem Turn wird eine neue Instanz des Bots erstellt.


// Register the BookingDialog.

Zu Referenzzwecken finden Sie im Anschluss die Klassendefinitionen, die im Aufruf zum Erstellen des obigen Bots verwendet werden:

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

Testen des Bots

  1. Installieren Sie Bot Framework Emulator, sofern noch nicht geschehen.
  2. Führen Sie das Beispiel lokal auf Ihrem Computer aus.
  3. Starten Sie den Emulator, stellen Sie eine Verbindung mit Ihrem Bot her, und senden Sie Nachrichten wie unten dargestellt.

Weitere Informationen

  • Das Beispiel 24.bot-authentication-msgraph in C#, JavaScript, Python oder Java zeigt, wie eine Abmeldeanforderung verarbeitet wird. Es wird ein Muster verwendet, das dem hier gezeigten ähnelt, um Unterbrechungen zu verarbeiten.

  • Es empfiehlt sich, eine Standardantwort zu senden, anstatt nichts zu unternehmen und den Benutzer ratlos zurückzulassen. Die Standardantwort sollte dem Benutzer mitteilen, welche Befehle der Bot versteht, sodass der Benutzer wieder in den Prozess hineinfindet.

  • 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. 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.