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.
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.
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:
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;
}
Installieren Sie das npm-Paket botbuilder-dialogs, um Dialoge verwenden zu können.
dialogs/cancelAndHelpDialog.js
Implementieren Sie die CancelAndHelpDialog
-Klasse für die Verarbeitung der Benutzerunterbrechungen. Diese Klasse wird durch die Dialoge BookingDialog
und DateResolverDialog
erweitert, die abgebrochen werden können.
class CancelAndHelpDialog extends ComponentDialog {
In der CancelAndHelpDialog
-Klasse ruft die onContinueDialog
-Methode die interrupt
-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 interrupt
zurückgegeben.
async onContinueDialog(innerDc) {
const result = await this.interrupt(innerDc);
if (result) {
return result;
}
return await super.onContinueDialog(innerDc);
}
Wenn der Benutzer „help“ (Hilfe) eingibt, sendet die Methode interrupt
eine Nachricht und gibt dann ein Objekt vom Typ { status: DialogTurnStatus.waiting }
zurück, 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 cancelAllDialogs
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.
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
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 extends ComponentDialog {
In der CancelAndHelpDialog
-Klasse ruft die onContinueDialog
-Methode die interrupt
-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 interrupt
zurückgegeben.
@Override
protected CompletableFuture<DialogTurnResult> onContinueDialog(DialogContext innerDc) {
return interrupt(innerDc).thenCompose(result -> {
if (result != null) {
return CompletableFuture.completedFuture(result);
}
return super.onContinueDialog(innerDc);
});
}
Wenn der Benutzer „help“ (Hilfe) eingibt, sendet die Methode interrupt
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 cancelAllDialogs
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 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);
}
Um Dialoge zu verwenden, installieren Sie das Paket botbuilder-dialogs
, und stellen Sie sicher, dass die Beispieldatei requirements.txt
den richtigen Verweis enthält, z. B. botbuilder-dialogs>=4.5.0
.
Weitere Informationen zum Installieren der Pakete finden Sie in der Beispielrepository-Infodatei.
Hinweis
Beim Ausführen von pip install botbuilder-dialogs
werden auch botbuilder-core
, botbuilder-connector
und botbuilder-schema
installiert.
dialogs/cancel-and-help-dialog.py
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.
class CancelAndHelpDialog(ComponentDialog):
In der CancelAndHelpDialog
-Klasse ruft die on_continue_dialog
-Methode die interrupt
-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 interrupt
zurückgegeben.
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)
Wenn der Benutzer „help“ (Hilfe) oder „?“ eingibt, sendet die interrupt
-Methode eine Nachricht und ruft dann DialogTurnResult(DialogTurnStatus.Waiting)
auf, um anzugeben, dass der Dialog oben im Stapel 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) oder „quit“ (Beenden) eingibt, wird cancel_all_dialogs()
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 dargestellt) 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.
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
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.
dialogs/mainDialog.js
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.actStep
mit einem Aufruf von beginDialog
gestartet, wie im Anschluss zu sehen:
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();
}
Der Buchungsdialog endet in der Methode finalStep
der Klasse MainDialog
, und die Buchung ist entweder abgeschlossen oder wurde abgebrochen.
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?' });
}
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.js.
MainDialog.java
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.actStep
mit einem Aufruf von beginDialog
gestartet, wie im Anschluss zu sehen:
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));
}
});
}
Der Buchungsdialog endet in der Methode finalStep
der Klasse MainDialog
, und die Buchung ist entweder abgeschlossen oder wurde abgebrochen.
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));
}
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 BookingDialogs.java.
dialogs/main_dialog.py
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 act_step
mit einem Aufruf von begin_dialog
gestartet, wie im Anschluss zu sehen:
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)
Der Buchungsdialog endet in der Methode final_step
der Klasse MainDialog
, und die Buchung ist entweder abgeschlossen oder wurde abgebrochen.
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)
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");
};
}
index.js
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.
// 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.
Durch die Registrierung eines AdapterWithErrorHandler
mit dem Spring-Framework in Application.java für das BotFrameworkHttpAdapter
in diesem Beispiel, empfängt der onTurnError
-Handler des Adapters alle 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. Im Java SDK wird das AdapterWithErrorHandler
im SDK implementiert und ist im com.microsoft.bot.integration enthalten. Details zur Implementierung dieses Adapters finden Sie im Java-SDK-Quellcode.
adapter_with_error_handler.py
Im Beispiel empfängt der Handler on_error
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.
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
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
index.js
In index.js
wird schließlich der Bot erstellt.
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');
Zu Referenzzwecken finden Sie im Anschluss die Klassendefinitionen, die im Aufruf zum Erstellen des obigen Bots verwendet werden:
class MainDialog extends ComponentDialog {
class DialogAndWelcomeBot extends DialogBot {
class DialogBot extends ActivityHandler {
Application.java
In Application.java
wird schließlich der Bot erstellt.
@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);
}
Zu Referenzzwecken finden Sie im Anschluss die Klassendefinitionen, die im Aufruf zum Erstellen des obigen Bots verwendet werden:
public class DialogAndWelcomeBot<T extends Dialog> extends DialogBot {
public class DialogBot<T extends Dialog> extends ActivityHandler {
public class MainDialog extends ComponentDialog {
app.py In app.py
wird schließlich der Bot erstellt.
# Create dialogs and Bot
RECOGNIZER = FlightBookingRecognizer(CONFIG)
BOOKING_DIALOG = BookingDialog()
DIALOG = MainDialog(RECOGNIZER, BOOKING_DIALOG)
BOT = DialogAndWelcomeBot(CONVERSATION_STATE, USER_STATE, DIALOG)
Zu Referenzzwecken finden Sie hier die Klassendefinitionen, die im Aufruf zum Erstellen des obigen Bots verwendet werden.
class MainDialog(ComponentDialog):
class DialogAndWelcomeBot(DialogBot):
class DialogBot(ActivityHandler):