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.
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.
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 :
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;
}
Pour utiliser des dialogues, installez le package npm botbuilder-dialogs.
dialogs/cancelAndHelpDialog.js
Implémentez la classe CancelAndHelpDialog
pour gérer les interruptions par l’utilisateur. Les dialogues annulables BookingDialog
et DateResolverDialog
étendent cette classe.
class CancelAndHelpDialog extends ComponentDialog {
Dans la classe CancelAndHelpDialog
, la méthode onContinueDialog
appelle la méthode interrupt
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 interrupt
est retourné.
async onContinueDialog(innerDc) {
const result = await this.interrupt(innerDc);
if (result) {
return result;
}
return await super.onContinueDialog(innerDc);
}
Si l’utilisateur tape « aide », la méthode interrupt
envoie un message, puis retourne un objet { status: 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 cancelAllDialogs
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.
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
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 extends ComponentDialog {
Dans la classe CancelAndHelpDialog
, la méthode onContinueDialog
appelle la méthode interrupt
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 interrupt
est retourné.
@Override
protected CompletableFuture<DialogTurnResult> onContinueDialog(DialogContext innerDc) {
return interrupt(innerDc).thenCompose(result -> {
if (result != null) {
return CompletableFuture.completedFuture(result);
}
return super.onContinueDialog(innerDc);
});
}
Si l’utilisateur tape « aide », la méthode interrupt
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 cancelAllDialogs
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 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);
}
Pour utiliser les dialogues, installez le package botbuilder-dialogs
, puis vérifiez que l’exemple de fichier requirements.txt
contient la référence appropriée, notamment botbuilder-dialogs>=4.5.0
.
Pour plus d’informations sur l’installation des packages, consultez le fichier README relatif au dépôt d’exemples.
Remarque
L'exécution de pip install botbuilder-dialogs
installera aussi botbuilder-core
, botbuilder-connector
et botbuilder-schema
.
dialogs/cancel-and-help-dialog.py
Implémentez la classe CancelAndHelpDialog
pour gérer les interruptions par l’utilisateur. Les dialogues annulables BookingDialog
et DateResolverDialog
découlent de cette classe.
class CancelAndHelpDialog(ComponentDialog):
Dans la classe CancelAndHelpDialog
, la méthode on_continue_dialog
appelle la méthode interrupt
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 interrupt
est retourné.
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)
Si l'utilisateur tape « aide » ou « ? », la méthode interrupt
envoie un message, puis appelle DialogTurnResult(DialogTurnStatus.Waiting)
pour indiquer que le dialogue en haut de la pile attend une réponse de la part 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 cancel (annuler) ou quit (quitter), cela appelle cancel_all_dialogs()
dans 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 loin, il apparaît que le dialogue de réservation a pris fin et qu’il a retourné une valeur null, comme au moment où l’utilisateur choisit de ne pas confirmer sa réservation.
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
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.
dialogs/mainDialog.js
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.actStep
, avec un appel à beginDialog
comme indiqué ci-dessous.
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();
}
Par la suite, dans la méthode finalStep
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.
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?' });
}
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.js.
MainDialog.java
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
dans la méthode MainDialog.actStep
est démarré, avec un appel à beginDialog
comme indiqué ci-dessous.
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));
}
});
}
Par la suite, dans la méthode finalStep
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 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));
}
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 BookingDialogs.java.
dialogs/main_dialog.py
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 act_step
, avec un appel à begin_dialog
comme indiqué ci-dessous.
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)
Par la suite, dans la méthode final_step
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.
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)
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");
};
}
index.js
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.
// 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.
Lorsque vous enregistrez un AdapterWithErrorHandler
avec le cadre Spring dans Application.java pour le BotFrameworkHttpAdapter
de cet exemple, le gestionnaire onTurnError
de l'adaptateur reçoit toutes les exceptions lancé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. Dans le kit de développement logiciel (SDK) Java, AdapterWithErrorHandler
est implémenté dans le cadre du kit de développement logiciel (SDK) et est inclus dans le package com.microsoft.bot.integration. Pour plus d'informations sur l'implémentation de cet adaptateur, consultez le code source du kit de développement logiciel (SDK) Java.
adapter_with_error_handler.py
Dans l'échantillon, le gestionnaire on_error
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.
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
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
index.js
Enfin, dans index.js
, le bot est créé.
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');
À titre de référence, voici les définitions de classe utilisées dans l’appel pour créer le bot ci-dessus.
class MainDialog extends ComponentDialog {
class DialogAndWelcomeBot extends DialogBot {
class DialogBot extends ActivityHandler {
Application.java
Enfin, dans Application.java
, le bot est créé.
@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);
}
À 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 extends Dialog> extends DialogBot {
public class DialogBot<T extends Dialog> extends ActivityHandler {
public class MainDialog extends ComponentDialog {
app.py Enfin, dans app.py
, le bot est créé.
# Create dialogs and Bot
RECOGNIZER = FlightBookingRecognizer(CONFIG)
BOOKING_DIALOG = BookingDialog()
DIALOG = MainDialog(RECOGNIZER, BOOKING_DIALOG)
BOT = DialogAndWelcomeBot(CONVERSATION_STATE, USER_STATE, DIALOG)
Pour référence, voici les définitions de classe utilisées dans l’appel pour créer le bot.
class MainDialog(ComponentDialog):
class DialogAndWelcomeBot(DialogBot):
class DialogBot(ActivityHandler):