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.
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.
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:
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;
}
Per usare i dialoghi, installare il pacchetto npm botbuilder-dialogs.
dialogs/cancelAndHelpDialog.js
Implementare la classe CancelAndHelpDialog
per gestire le interruzioni dell'utente. I dialoghi BookingDialog
annullabili ed DateResolverDialog
estendono questa classe.
class CancelAndHelpDialog extends ComponentDialog {
CancelAndHelpDialog
Nella classe il onContinueDialog
metodo chiama il interrupt
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 interrupt
.
async onContinueDialog(innerDc) {
const result = await this.interrupt(innerDc);
if (result) {
return result;
}
return await super.onContinueDialog(innerDc);
}
Se l'utente digita "help", il metodo interrupt
invia un messaggio e quindi restituisce un oggetto { status: 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 cancelAllDialogs
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.
async interrupt(innerDc) {
if (innerDc.context.activity.text) {
const text = innerDc.context.activity.text.toLowerCase();
switch (text) {
case 'help':
case '?': {
const helpMessageText = 'Show help here';
await innerDc.context.sendActivity(helpMessageText, helpMessageText, InputHints.ExpectingInput);
return { status: DialogTurnStatus.waiting };
}
case 'cancel':
case 'quit': {
const cancelMessageText = 'Cancelling...';
await innerDc.context.sendActivity(cancelMessageText, cancelMessageText, InputHints.IgnoringInput);
return await innerDc.cancelAllDialogs();
}
}
}
}
CancelAndHelpDialog.java
Implementare la classe CancelAndHelpDialog
per gestire le interruzioni dell'utente. I dialoghi BookingDialog
annullabili e DateResolverDialog
derivano da questa classe.
public class CancelAndHelpDialog extends ComponentDialog {
CancelAndHelpDialog
Nella classe il onContinueDialog
metodo chiama il interrupt
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 interrupt
.
@Override
protected CompletableFuture<DialogTurnResult> onContinueDialog(DialogContext innerDc) {
return interrupt(innerDc).thenCompose(result -> {
if (result != null) {
return CompletableFuture.completedFuture(result);
}
return super.onContinueDialog(innerDc);
});
}
Se l'utente digita "help", il metodo interrupt
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 cancelAllDialogs
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 CompletableFuture<DialogTurnResult> interrupt(DialogContext innerDc) {
if (innerDc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) {
String text = innerDc.getContext().getActivity().getText().toLowerCase();
switch (text) {
case "help":
case "?":
Activity helpMessage = MessageFactory
.text(helpMsgText, helpMsgText, InputHints.EXPECTING_INPUT);
return innerDc.getContext().sendActivity(helpMessage)
.thenCompose(sendResult ->
CompletableFuture
.completedFuture(new DialogTurnResult(DialogTurnStatus.WAITING)));
case "cancel":
case "quit":
Activity cancelMessage = MessageFactory
.text(cancelMsgText, cancelMsgText, InputHints.IGNORING_INPUT);
return innerDc.getContext()
.sendActivity(cancelMessage)
.thenCompose(sendResult -> innerDc.cancelAllDialogs());
default:
break;
}
}
return CompletableFuture.completedFuture(null);
}
Per usare i dialoghi, installare il pacchetto botbuilder-dialogs
e verificare che nel file requirements.txt
di esempio sia contenuto il riferimento appropriato, ad esempio botbuilder-dialogs>=4.5.0
.
Per altre informazioni sull'installazione dei pacchetti, vedere il file README nel repository di esempi.
Nota
L'esecuzione pip install botbuilder-dialogs
installerà botbuilder-core
anche , botbuilder-connector
e botbuilder-schema
.
dialogs/cancel-and-help-dialog.py
Implementare la classe CancelAndHelpDialog
per gestire le interruzioni dell'utente. I dialoghi BookingDialog
annullabili e DateResolverDialog
derivano da questa classe.
class CancelAndHelpDialog(ComponentDialog):
CancelAndHelpDialog
Nella classe il on_continue_dialog
metodo chiama il interrupt
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 interrupt
.
async def on_continue_dialog(self, inner_dc: DialogContext) -> DialogTurnResult:
result = await self.interrupt(inner_dc)
if result is not None:
return result
return await super(CancelAndHelpDialog, self).on_continue_dialog(inner_dc)
Se l'utente digita "help" o "?", il interrupt
metodo invia un messaggio e quindi chiama DialogTurnResult(DialogTurnStatus.Waiting)
per indicare che la finestra di dialogo nella parte superiore dello stack è in attesa di una risposta dall'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" o "quit", chiama cancel_all_dialogs()
il relativo contesto di dialogo interno, che cancella lo stack di dialoghi e lo fa uscire con uno stato annullato e nessun valore del risultato. Per MainDialog
, illustrato più avanti, sembrerà che il dialogo di prenotazione sia terminato e abbia restituito Null, analogamente a quando l'utente sceglie di non confermare la prenotazione.
async def interrupt(self, inner_dc: DialogContext) -> DialogTurnResult:
if inner_dc.context.activity.type == ActivityTypes.message:
text = inner_dc.context.activity.text.lower()
help_message_text = "Show Help..."
help_message = MessageFactory.text(
help_message_text, help_message_text, InputHints.expecting_input
)
if text in ("help", "?"):
await inner_dc.context.send_activity(help_message)
return DialogTurnResult(DialogTurnStatus.Waiting)
cancel_message_text = "Cancelling"
cancel_message = MessageFactory.text(
cancel_message_text, cancel_message_text, InputHints.ignoring_input
)
if text in ("cancel", "quit"):
await inner_dc.context.send_activity(cancel_message)
return await inner_dc.cancel_all_dialogs()
return None
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.
dialogs/mainDialog.js
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.actStep
, con una chiamata a beginDialog
, come illustrato di seguito.
async actStep(stepContext) {
const bookingDetails = {};
if (!this.luisRecognizer.isConfigured) {
// LUIS is not configured, we just run the BookingDialog path.
return await stepContext.beginDialog('bookingDialog', bookingDetails);
}
// Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt)
const luisResult = await this.luisRecognizer.executeLuisQuery(stepContext.context);
switch (LuisRecognizer.topIntent(luisResult)) {
case 'BookFlight': {
// Extract the values for the composite entities from the LUIS result.
const fromEntities = this.luisRecognizer.getFromEntities(luisResult);
const toEntities = this.luisRecognizer.getToEntities(luisResult);
// Show a warning for Origin and Destination if we can't resolve them.
await this.showWarningForUnsupportedCities(stepContext.context, fromEntities, toEntities);
// Initialize BookingDetails with any entities we may have found in the response.
bookingDetails.destination = toEntities.airport;
bookingDetails.origin = fromEntities.airport;
bookingDetails.travelDate = this.luisRecognizer.getTravelDate(luisResult);
console.log('LUIS extracted these booking details:', JSON.stringify(bookingDetails));
// Run the BookingDialog passing in whatever details we have from the LUIS call, it will fill out the remainder.
return await stepContext.beginDialog('bookingDialog', bookingDetails);
}
case 'GetWeather': {
// We haven't implemented the GetWeatherDialog so we just display a TODO message.
const getWeatherMessageText = 'TODO: get weather flow here';
await stepContext.context.sendActivity(getWeatherMessageText, getWeatherMessageText, InputHints.IgnoringInput);
break;
}
default: {
// Catch all for unhandled intents
const didntUnderstandMessageText = `Sorry, I didn't get that. Please try asking in a different way (intent was ${ LuisRecognizer.topIntent(luisResult) })`;
await stepContext.context.sendActivity(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IgnoringInput);
}
}
return await stepContext.next();
}
Successivamente, nel finalStep
metodo della MainDialog
classe, la finestra di dialogo di prenotazione è terminata e la prenotazione viene considerata completa o annullata.
async finalStep(stepContext) {
// If the child dialog ("bookingDialog") was cancelled or the user failed to confirm, the Result here will be null.
if (stepContext.result) {
const result = stepContext.result;
// Now we have all the booking details.
// This is where calls to the booking AOU service or database would go.
// If the call to the booking service was successful tell the user.
const timeProperty = new TimexProperty(result.travelDate);
const travelDateMsg = timeProperty.toNaturalLanguage(new Date(Date.now()));
const msg = `I have you booked to ${ result.destination } from ${ result.origin } on ${ travelDateMsg }.`;
await stepContext.context.sendActivity(msg, msg, InputHints.IgnoringInput);
}
// Restart the main dialog with a different message the second time around
return await stepContext.replaceDialog(this.initialDialogId, { restartMsg: 'What else can I do for you?' });
}
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.js.
MainDialog.java
Non appena arriva l'attività di nuovo messaggio, il bot esegue MainDialog
. MainDialog
chiede all'utente in cosa può essere utile. Viene quindi avviato BookingDialog
nel MainDialog.actStep
metodo con una chiamata a beginDialog
, come illustrato di seguito.
private CompletableFuture<DialogTurnResult> actStep(WaterfallStepContext stepContext) {
if (!luisRecognizer.isConfigured()) {
// LUIS is not configured, we just run the BookingDialog path with an empty BookingDetailsInstance.
return stepContext.beginDialog("BookingDialog", new BookingDetails());
}
// Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.)
return luisRecognizer.recognize(stepContext.getContext()).thenCompose(luisResult -> {
switch (luisResult.getTopScoringIntent().intent) {
case "BookFlight":
// Extract the values for the composite entities from the LUIS result.
ObjectNode fromEntities = luisRecognizer.getFromEntities(luisResult);
ObjectNode toEntities = luisRecognizer.getToEntities(luisResult);
// Show a warning for Origin and Destination if we can't resolve them.
return showWarningForUnsupportedCities(
stepContext.getContext(), fromEntities, toEntities)
.thenCompose(showResult -> {
// Initialize BookingDetails with any entities we may have found in the response.
BookingDetails bookingDetails = new BookingDetails();
bookingDetails.setDestination(toEntities.get("airport").asText());
bookingDetails.setOrigin(fromEntities.get("airport").asText());
bookingDetails.setTravelDate(luisRecognizer.getTravelDate(luisResult));
// Run the BookingDialog giving it whatever details we have from the LUIS call,
// it will fill out the remainder.
return stepContext.beginDialog("BookingDialog", bookingDetails);
}
);
case "GetWeather":
// We haven't implemented the GetWeatherDialog so we just display a TODO message.
String getWeatherMessageText = "TODO: get weather flow here";
Activity getWeatherMessage = MessageFactory
.text(
getWeatherMessageText, getWeatherMessageText,
InputHints.IGNORING_INPUT
);
return stepContext.getContext().sendActivity(getWeatherMessage)
.thenCompose(resourceResponse -> stepContext.next(null));
default:
// Catch all for unhandled intents
String didntUnderstandMessageText = String.format(
"Sorry, I didn't get that. Please "
+ " try asking in a different way (intent was %s)",
luisResult.getTopScoringIntent().intent
);
Activity didntUnderstandMessage = MessageFactory
.text(
didntUnderstandMessageText, didntUnderstandMessageText,
InputHints.IGNORING_INPUT
);
return stepContext.getContext().sendActivity(didntUnderstandMessage)
.thenCompose(resourceResponse -> stepContext.next(null));
}
});
}
Successivamente, nel finalStep
metodo della MainDialog
classe, la finestra di dialogo di prenotazione è terminata e la prenotazione viene considerata completa o annullata.
private CompletableFuture<DialogTurnResult> finalStep(WaterfallStepContext stepContext) {
CompletableFuture<Void> stepResult = CompletableFuture.completedFuture(null);
// 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.getResult() instanceof BookingDetails) {
// Now we have all the booking details call the booking service.
// If the call to the booking service was successful tell the user.
BookingDetails result = (BookingDetails) stepContext.getResult();
TimexProperty timeProperty = new TimexProperty(result.getTravelDate());
String travelDateMsg = timeProperty.toNaturalLanguage(LocalDateTime.now());
String messageText = String.format("I have you booked to %s from %s on %s",
result.getDestination(), result.getOrigin(), travelDateMsg
);
Activity message = MessageFactory
.text(messageText, messageText, InputHints.IGNORING_INPUT);
stepResult = stepContext.getContext().sendActivity(message).thenApply(sendResult -> null);
}
// Restart the main dialog with a different message the second time around
String promptMessage = "What else can I do for you?";
return stepResult
.thenCompose(result -> stepContext.replaceDialog(getInitialDialogId(), promptMessage));
}
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. È possibile trovare il codice in BookingDialogs.java.
dialogs/main_dialog.py
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 act_step
, con una chiamata a begin_dialog
, come illustrato di seguito.
async def act_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
if not self._luis_recognizer.is_configured:
# LUIS is not configured, we just run the BookingDialog path with an empty BookingDetailsInstance.
return await step_context.begin_dialog(
self._booking_dialog_id, BookingDetails()
)
# Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.)
intent, luis_result = await LuisHelper.execute_luis_query(
self._luis_recognizer, step_context.context
)
if intent == Intent.BOOK_FLIGHT.value and luis_result:
# Show a warning for Origin and Destination if we can't resolve them.
await MainDialog._show_warning_for_unsupported_cities(
step_context.context, luis_result
)
# Run the BookingDialog giving it whatever details we have from the LUIS call.
return await step_context.begin_dialog(self._booking_dialog_id, luis_result)
if intent == Intent.GET_WEATHER.value:
get_weather_text = "TODO: get weather flow here"
get_weather_message = MessageFactory.text(
get_weather_text, get_weather_text, InputHints.ignoring_input
)
await step_context.context.send_activity(get_weather_message)
else:
didnt_understand_text = (
"Sorry, I didn't get that. Please try asking in a different way"
)
didnt_understand_message = MessageFactory.text(
didnt_understand_text, didnt_understand_text, InputHints.ignoring_input
)
await step_context.context.send_activity(didnt_understand_message)
return await step_context.next(None)
Successivamente, nel final_step
metodo della MainDialog
classe, la finestra di dialogo di prenotazione è terminata e la prenotazione viene considerata completa o annullata.
async def final_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
# If the child dialog ("BookingDialog") was cancelled or the user failed to confirm,
# the Result here will be null.
if step_context.result is not None:
result = step_context.result
# Now we have all the booking details call the booking service.
# If the call to the booking service was successful tell the user.
# time_property = Timex(result.travel_date)
# travel_date_msg = time_property.to_natural_language(datetime.now())
msg_txt = f"I have you booked to {result.destination} from {result.origin} on {result.travel_date}"
message = MessageFactory.text(msg_txt, msg_txt, InputHints.ignoring_input)
await step_context.context.send_activity(message)
prompt_message = "What else can I do for you?"
return await step_context.replace_dialog(self.id, prompt_message)
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");
};
}
index.js
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.
// Send a trace activity, which will be displayed in Bot Framework Emulator
await context.sendTraceActivity(
'OnTurnError Trace',
`${ error }`,
'https://www.botframework.com/schemas/error',
'TurnError'
);
// Send a message to the user
let onTurnErrorMessage = 'The bot encountered an error or bug.';
await context.sendActivity(onTurnErrorMessage, onTurnErrorMessage, InputHints.ExpectingInput);
onTurnErrorMessage = 'To continue to run this bot, please fix the bot source code.';
await context.sendActivity(onTurnErrorMessage, onTurnErrorMessage, InputHints.ExpectingInput);
// Clear out state
await conversationState.delete(context);
};
// Set the onTurnError for the singleton CloudAdapter.
adapter.onTurnError = onTurnErrorHandler;
// Define a state store for your bot. See https://aka.ms/about-bot-state to learn more about using MemoryStorage.
// A bot requires a state store to persist the dialog and user state between messages.
// For local development, in-memory storage is used.
Registrando un oggetto AdapterWithErrorHandler
con Spring Framework in Application.java per BotFrameworkHttpAdapter
in questo 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. In Java SDK, viene AdapterWithErrorHandler
implementato come parte dell'SDK ed è incluso nel pacchetto com.microsoft.bot.integration . Per informazioni dettagliate sull'implementazione di questa scheda, vedere il codice sorgente di Java SDK.
adapter_with_error_handler.py
Nell'esempio, il gestore dell'adapter on_error
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.
def __init__(
self,
settings: ConfigurationBotFrameworkAuthentication,
conversation_state: ConversationState,
):
super().__init__(settings)
self._conversation_state = conversation_state
# Catch-all for errors.
async def on_error(context: TurnContext, error: Exception):
# This check writes out errors to console log
# NOTE: In production environment, you should consider logging this to Azure
# application insights.
print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
traceback.print_exc()
# Send a message to the user
await context.send_activity("The bot encountered an error or bug.")
await context.send_activity(
"To continue to run this bot, please fix the bot source code."
)
# Send a trace activity if we're talking to the Bot Framework Emulator
if context.activity.channel_id == "emulator":
# Create a trace activity that contains the error object
trace_activity = Activity(
label="TurnError",
name="on_turn_error Trace",
timestamp=datetime.utcnow(),
type=ActivityTypes.trace,
value=f"{error}",
value_type="https://www.botframework.com/schemas/error",
)
# Send a trace activity, which will be displayed in Bot Framework Emulator
await context.send_activity(trace_activity)
# Clear out state
nonlocal self
await self._conversation_state.delete(context)
self.on_turn_error = on_error
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
index.js
In index.js
viene infine creato il bot.
const dialog = new MainDialog(luisRecognizer, bookingDialog);
const bot = new DialogAndWelcomeBot(conversationState, userState, dialog);
// Create HTTP server
const server = restify.createServer();
server.use(restify.plugins.bodyParser());
server.listen(process.env.port || process.env.PORT || 3978, function() {
console.log(`\n${ server.name } listening to ${ server.url }`);
console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator');
Per riferimento, ecco le definizioni delle classi che vengono usate nella chiamata per creare il bot precedente.
class MainDialog extends ComponentDialog {
class DialogAndWelcomeBot extends DialogBot {
class DialogBot extends ActivityHandler {
Application.java
In Application.java
viene infine creato il bot.
@Bean
public Bot getBot(
Configuration configuration,
UserState userState,
ConversationState conversationState
) {
FlightBookingRecognizer recognizer = new FlightBookingRecognizer(configuration);
MainDialog dialog = new MainDialog(recognizer, new BookingDialog());
return new DialogAndWelcomeBot<>(conversationState, userState, dialog);
}
Per riferimento, ecco le definizioni delle classi che vengono usate nella chiamata per creare il bot precedente.
public class DialogAndWelcomeBot<T extends Dialog> extends DialogBot {
public class DialogBot<T extends Dialog> extends ActivityHandler {
public class MainDialog extends ComponentDialog {
app.py Infine, in app.py
, viene creato il bot.
# Create dialogs and Bot
RECOGNIZER = FlightBookingRecognizer(CONFIG)
BOOKING_DIALOG = BookingDialog()
DIALOG = MainDialog(RECOGNIZER, BOOKING_DIALOG)
BOT = DialogAndWelcomeBot(CONVERSATION_STATE, USER_STATE, DIALOG)
Per riferimento, ecco le definizioni delle classi che vengono usate nella chiamata per creare il bot.
class MainDialog(ComponentDialog):
class DialogAndWelcomeBot(DialogBot):
class DialogBot(ActivityHandler):