Gestire le interruzioni dell'utente

SI APPLICA A: SDK v4

La gestione delle interruzioni è un aspetto importante di un bot affidabile. Gli utenti non seguiranno sempre il flusso di conversazione definito, passo dopo passo. Possono ad esempio provare a porre una domanda durante il processo o semplicemente volerla annullare invece di completarla. Questo articolo descrive alcuni modi comuni per gestire le interruzioni degli utenti nel bot.

Nota

Gli SDK JavaScript, C# e Python di Bot Framework continueranno a essere supportati, ma Java SDK verrà ritirato con il supporto finale a lungo termine che termina a novembre 2023.

I bot esistenti creati con Java SDK continueranno a funzionare.

Per la creazione di nuovi bot, è consigliabile usare Microsoft Copilot Studio e leggere le informazioni sulla scelta della soluzione copilota appropriata.

Per altre informazioni, vedere Il futuro della compilazione di bot.

Prerequisiti

L'esempio di bot di base usa Language Understanding (LUIS) per identificare le finalità dell'utente; Tuttavia, l'identificazione della finalità dell'utente non è l'obiettivo di questo articolo. Per informazioni sull'identificazione delle finalità utente, vedere Comprensione del linguaggio naturale e Aggiungere la comprensione del linguaggio naturale al bot.

Nota

Language Understanding (LUIS) verrà ritirato il 1° ottobre 2025. A partire dal 1° aprile 2023, non sarà possibile creare nuove risorse LUIS. Una versione più recente di Language Understanding è ora disponibile come parte del linguaggio di intelligenza artificiale di Azure.

CLU (Conversational Language Understanding), una funzionalità del linguaggio di intelligenza artificiale di Azure, è la versione aggiornata di LUIS. Per altre informazioni sul supporto per la comprensione del linguaggio in Bot Framework SDK, vedere Comprensione del linguaggio naturale.

Informazioni sull'esempio

L'esempio usato in questo articolo consente di modellare un bot di prenotazione voli che usa i dialoghi per ottenere informazioni sul volo dall'utente. In qualsiasi momento durante la conversazione con il bot, l'utente può inviare il comando help o cancel per causare un'interruzione. In questo caso vengono gestiti due tipi di interruzioni:

  • Livello di turno: ignorare l'elaborazione a livello di turno, ma lasciare la finestra di dialogo sullo stack con le informazioni fornite. Al turno successivo si continuerà dal punto in cui la conversazione era stata interrotta.
  • Livello di dialogo: annullare completamente l'elaborazione, in modo che il bot possa ricominciare da capo.

Definire e implementare la logica delle interruzioni

Definire e implementare prima le interruzioni per help e cancel.

Per usare i dialoghi, installare il pacchetto NuGet Microsoft.Bot.Builder.Dialogs.

Dialogs\CancelAndHelpDialog.cs

Implementare la classe CancelAndHelpDialog per gestire le interruzioni dell'utente. I dialoghi BookingDialog annullabili e DateResolverDialog derivano da questa classe.

public class CancelAndHelpDialog : ComponentDialog

CancelAndHelpDialog Nella classe il OnContinueDialogAsync metodo chiama il InterruptAsync metodo per verificare se l'utente ha interrotto il flusso normale. Se il flusso è stato interrotto, vengono chiamati i metodi della classe di base; in caso contrario, viene restituito il valore di InterruptAsync.

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

Se l'utente digita "help", il metodo InterruptAsync invia un messaggio e quindi chiama DialogTurnResult (DialogTurnStatus.Waiting) per indicare che il dialogo in primo piano è in attesa di una risposta da parte dell'utente. In questo modo il flusso della conversazione viene interrotto solo per un turno e al turno successivo si continuerà dal punto in cui la conversazione era stata interrotta.

Se l'utente digita "cancel", chiama CancelAllDialogsAsync sul contesto interno del dialogo, che cancella lo stack di dialoghi e lo fa uscire con uno stato annullato e senza alcun valore di risultato. Per MainDialog (illustrato più avanti), sembrerà che il dialogo di prenotazione è terminato ed ha restituito Null, analogamente a quando l'utente sceglie di non confermare la prenotazione.

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

Verifica delle interruzioni a ogni turno

Dopo aver implementato la classe di gestione delle interruzioni, esaminare cosa accade quando questo bot riceve un nuovo messaggio dall'utente.

Dialogs\MainDialog.cs

Non appena arriva l'attività di nuovo messaggio, il bot esegue MainDialog. MainDialog chiede all'utente in cosa può essere utile. Avvia quindi BookingDialog nel metodo MainDialog.ActStepAsync, con una chiamata a BeginDialogAsync, come illustrato di seguito.

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

Successivamente, nel FinalStepAsync metodo della MainDialog classe, la finestra di dialogo di prenotazione è terminata e la prenotazione viene considerata completa o annullata.

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

Il codice in BookingDialog non viene visualizzato qui perché non è direttamente correlato alla gestione delle interruzioni. Viene usato per richiedere agli utenti i dettagli della prenotazione. Tale codice è disponibile in Dialogs\BookingDialogs.cs.

Gestire errori imprevisti

Il gestore degli errori dell'adapter gestisce tutte le eccezioni che non sono state rilevate nel bot.

AdapterWithErrorHandler.cs

Nell'esempio, il gestore dell'adapter OnTurnError riceve tutte le eccezioni generate dalla logica dei turni del bot. Se è stata generata un'eccezione, il gestore elimina lo stato della conversazione per la conversazione corrente per impedire che il bot si blocchi in un ciclo di errore causato da uno stato non valido.

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

Registrare i servizi

Startup.cs

In Startup.cs viene infine creato il bot temporaneo e, a ogni turno, viene creata una nuova istanza del bot.


// Register the BookingDialog.

Per riferimento, ecco le definizioni delle classi che vengono usate nella chiamata per creare il bot precedente.

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

Test del bot

  1. Se non è già stato fatto, installare Bot Framework Emulator.
  2. Eseguire l'esempio in locale nel computer.
  3. Avviare l'emulatore, connettersi al bot e inviare messaggi come mostrato di seguito.

Informazioni aggiuntive

  • L'esempio 24.bot-authentication-msgraph in C#, JavaScript, Python o Java illustra come gestire una richiesta di disconnessione. Usa un modello simile a quello illustrato di seguito per la gestione delle interruzioni.

  • È consigliabile inviare una risposta predefinita invece di non eseguire alcuna operazione lasciando l'utente a chiedersi che cosa stia accadendo. La risposta predefinita dovrà indicare all'utente i comandi che il bot può riconoscere in modo che l'utente possa riprendere il processo.

  • In qualsiasi momento nel turno la proprietà responded del contesto del turno indica se a questo turno il bot ha inviato un messaggio all'utente. Prima del termine del turno, il bot deve inviare un messaggio all'utente, anche se si tratta di un semplice riconoscimento dell'input.