Обработка прерываний является важным аспектом для создания надежного бота. Пользователи не всегда будут следовать определенному потоку беседы, пошаговая. Они могут задать вопрос в середине процесса или отменить процесс вместо завершения. В этой статье описаны некоторые распространенные способы обработки прерываний пользователей в боте.
В примере основного бота используется Распознавание речи (LUIS) для выявления намерений пользователей. Однако определение намерения пользователя не является фокусом этой статьи.
Сведения о выявлении намерений пользователей см. в разделе " Распознавание естественного языка" и "Добавление распознавания естественного языка" в бот.
Пример в этой статье моделирует работу бота для бронирования авиабилетов, который использует диалоги для получения от пользователя информации о нужном рейсе. В любой момент общения с ботом пользователь может выдать команду help (помощь) или cancel (отмена), что должно вызвать прерывание. Здесь мы будем использовать два вида прерываний.
Чтобы использовать диалоги, установите пакет NuGet Microsoft.Bot.Builder.Dialogs.
Dialogs\CancelAndHelpDialog.cs
Реализуйте класс CancelAndHelpDialog
для обработки прерываний со стороны пользователя. Диалоговые окна отмены BookingDialog
и DateResolverDialog
производные от этого класса.
public class CancelAndHelpDialog : ComponentDialog
CancelAndHelpDialog
В классе метод вызывает InterruptAsync
метод, чтобы проверить, OnContinueDialogAsync
прерывает ли пользователь обычный поток. Если процесс прерывается, вызываются методы базового класса. В противном случае возвращается значение, полученное из 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);
}
Если пользователь вводит слово help, метод InterruptAsync
отправляет сообщение и вызывает DialogTurnResult (DialogTurnStatus.Waiting)
, чтобы обозначить ожидание ответа от пользователя в диалоге верхнего уровня. В этом случае поток общения прерывается только на один шаг, а на следующем шаге продолжается с того места, где остановился диалог.
Если пользователь вводит "отмену", он вызывает CancelAllDialogsAsync
его внутренний контекст диалогового окна, который очищает стек диалогов и приводит к выходу из него с отмененным состоянием и без значения результата. С точки зрения MainDialog
(см. далее) все будет выглядеть так, как будто диалог бронирования завершился и вернул значение NULL. Этот равнозначно ситуации, когда пользователь отказался подтвердить бронирование.
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;
}
Чтобы использовать диалоги, установите пакет npm botbuilder-dialogs.
dialogs/cancelAndHelpDialog.js
Реализуйте класс CancelAndHelpDialog
для обработки прерываний со стороны пользователя. Диалоговые окна отмены BookingDialog
и DateResolverDialog
расширение этого класса.
class CancelAndHelpDialog extends ComponentDialog {
CancelAndHelpDialog
В классе метод вызывает interrupt
метод, чтобы проверить, onContinueDialog
прерывает ли пользователь обычный поток. Если процесс прерывается, вызываются методы базового класса. В противном случае возвращается значение, полученное из interrupt
.
async onContinueDialog(innerDc) {
const result = await this.interrupt(innerDc);
if (result) {
return result;
}
return await super.onContinueDialog(innerDc);
}
Если пользователь вводит слово help, метод interrupt
отправляет сообщение и возвращает объект { status: DialogTurnStatus.waiting }
, чтобы обозначить ожидание ответа от пользователя в диалоге верхнего уровня. В этом случае поток общения прерывается только на один шаг, а на следующем шаге продолжается с того места, где остановился диалог.
Если пользователь вводит "отмену", он вызывает cancelAllDialogs
его внутренний контекст диалогового окна, который очищает стек диалогов и приводит к выходу из него с отмененным состоянием и без значения результата. С точки зрения MainDialog
(см. далее) все будет выглядеть так, как будто диалог бронирования завершился и вернул значение NULL. Этот равнозначно ситуации, когда пользователь отказался подтвердить бронирование.
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
Реализуйте класс CancelAndHelpDialog
для обработки прерываний со стороны пользователя. Диалоговые окна отмены BookingDialog
и DateResolverDialog
производные от этого класса.
public class CancelAndHelpDialog extends ComponentDialog {
CancelAndHelpDialog
В классе метод вызывает interrupt
метод, чтобы проверить, onContinueDialog
прерывает ли пользователь обычный поток. Если процесс прерывается, вызываются методы базового класса. В противном случае возвращается значение, полученное из interrupt
.
@Override
protected CompletableFuture<DialogTurnResult> onContinueDialog(DialogContext innerDc) {
return interrupt(innerDc).thenCompose(result -> {
if (result != null) {
return CompletableFuture.completedFuture(result);
}
return super.onContinueDialog(innerDc);
});
}
Если пользователь вводит слово help, метод interrupt
отправляет сообщение и вызывает DialogTurnResult(DialogTurnStatus.WAITING)
, чтобы обозначить ожидание ответа от пользователя в диалоге верхнего уровня. В этом случае поток общения прерывается только на один шаг, а на следующем шаге продолжается с того места, где остановился диалог.
Если пользователь вводит "отмену", он вызывает cancelAllDialogs
его внутренний контекст диалогового окна, который очищает стек диалогов и приводит к выходу из него с отмененным состоянием и без значения результата. С точки зрения MainDialog
(см. далее) все будет выглядеть так, как будто диалог бронирования завершился и вернул значение NULL. Этот равнозначно ситуации, когда пользователь отказался подтвердить бронирование.
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);
}
Чтобы использовать диалоги, установите пакет botbuilder-dialogs
и убедитесь, что пример файла requirements.txt
содержит соответствующую ссылку, например botbuilder-dialogs>=4.5.0
.
Дополнительные сведения об установке пакетов см. в файле сведений в репозитории примеров.
Примечание.
Выполнение pip install botbuilder-dialogs
также установит botbuilder-core
, botbuilder-connector
и botbuilder-schema
.
dialogs/cancel-and-help-dialog.py
Реализуйте класс CancelAndHelpDialog
для обработки прерываний со стороны пользователя. Диалоговые окна отмены BookingDialog
и DateResolverDialog
производные от этого класса.
class CancelAndHelpDialog(ComponentDialog):
CancelAndHelpDialog
В классе метод вызывает interrupt
метод, чтобы проверить, on_continue_dialog
прерывает ли пользователь обычный поток. Если процесс прерывается, вызываются методы базового класса. В противном случае возвращается значение, полученное из 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)
Если пользователь вводит "справку" или "?", interrupt
метод отправляет сообщение, а затем вызывает DialogTurnResult(DialogTurnStatus.Waiting)
, чтобы указать, что диалоговое окно в верхней части стека ожидает ответа от пользователя. В этом случае поток общения прерывается только на один шаг, а на следующем шаге продолжается с того места, где остановился диалог.
Если пользователь вводит "отмена" или "выйти", он вызывает cancel_all_dialogs()
его внутренний контекст диалогового окна, который очищает стек диалогов и приводит к выходу с отмененным состоянием и без значения результата. С точки зрения MainDialog
(см. далее) все будет выглядеть так, как будто диалог бронирования завершился и вернул значение NULL. Этот равнозначно ситуации, когда пользователь отказался подтвердить бронирование.
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
После реализации класса обработки прерываний проверьте, что происходит, когда бот получает новое сообщение от пользователя.
Dialogs\MainDialog.cs
При поступлении действия с новым сообщением бот выполняет MainDialog
. В MainDialog
у пользователя спрашивается, какая помощь тому требуется. Затем запускается BookingDialog
в методе MainDialog.ActStepAsync
с помощью вызова BeginDialogAsync
, как показано ниже.
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);
}
Затем в методе FinalStepAsync
MainDialog
класса диалоговое окно резервирования закончилось, а резервирование считается завершенным или отмененным.
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);
}
BookingDialog
Код не отображается здесь, так как он не связан напрямую с обработкой прерываний. Он используется для запроса пользователей для получения сведений о резервировании. Вы можете найти этот код в файле Dialogs\BookingDialogs.cs.
dialogs/mainDialog.js
При поступлении действия с новым сообщением бот выполняет MainDialog
. В MainDialog
у пользователя спрашивается, какая помощь тому требуется. Затем запускается bookingDialog
в методе MainDialog.actStep
с помощью вызова beginDialog
, как показано ниже.
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();
}
Затем в методе finalStep
MainDialog
класса диалоговое окно резервирования закончилось, а резервирование считается завершенным или отмененным.
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?' });
}
BookingDialog
Код не отображается здесь, так как он не связан напрямую с обработкой прерываний. Он используется для запроса пользователей для получения сведений о резервировании. Вы можете найти этот код в файле dialogs/bookingDialogs.js.
MainDialog.java
При поступлении действия с новым сообщением бот выполняет MainDialog
. В MainDialog
у пользователя спрашивается, какая помощь тому требуется. Затем он запускается BookingDialog
в MainDialog.actStep
методе с вызовом beginDialog
, как показано ниже.
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));
}
});
}
Затем в методе finalStep
MainDialog
класса диалоговое окно резервирования закончилось, а резервирование считается завершенным или отмененным.
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));
}
BookingDialog
Код не отображается здесь, так как он не связан напрямую с обработкой прерываний. Он используется для запроса пользователей для получения сведений о резервировании. Этот код можно найти в BookingDialogs.java.
dialogs/main_dialog.py
При поступлении действия с новым сообщением бот выполняет MainDialog
. В MainDialog
у пользователя спрашивается, какая помощь тому требуется. Затем запускается bookingDialog
в методе act_step
с помощью вызова begin_dialog
, как показано ниже.
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)
Затем в методе final_step
MainDialog
класса диалоговое окно резервирования закончилось, а резервирование считается завершенным или отмененным.
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)
Обработчик ошибок адаптера обрабатывает все исключения, которые не были пойманы в боте.
AdapterWithErrorHandler.cs
В примере обработчик адаптера OnTurnError
получает все исключения, создаваемые логикой поворота бота. Если возникает исключение, обработчик удаляет состояние беседы для текущей беседы, чтобы предотвратить зависание бота в цикле ошибок, вызванного плохим состоянием.
{
// 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
В примере обработчик адаптера onTurnError
получает все исключения, создаваемые логикой поворота бота. Если возникает исключение, обработчик удаляет состояние беседы для текущей беседы, чтобы предотвратить зависание бота в цикле ошибок, вызванного плохим состоянием.
// 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.
Зарегистрируя платформу AdapterWithErrorHandler
Spring в Application.java для BotFrameworkHttpAdapter
этого примера, обработчик адаптера onTurnError
получает все исключения, создаваемые логикой поворота бота. Если возникает исключение, обработчик удаляет состояние беседы для текущей беседы, чтобы предотвратить зависание бота в цикле ошибок, вызванного плохим состоянием. В пакете SDK для Java реализован пакет SDK AdapterWithErrorHandler
и входит в пакет com.microsoft.bot.integration . Дополнительные сведения о реализации этого адаптера см. в исходном коде пакета SDK для Java.
adapter_with_error_handler.py
В примере обработчик адаптера on_error
получает все исключения, создаваемые логикой поворота бота. Если возникает исключение, обработчик удаляет состояние беседы для текущей беседы, чтобы предотвратить зависание бота в цикле ошибок, вызванного плохим состоянием.
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
Наконец, в Startup.cs
создается временный бот, то есть на каждом шаге создается новый экземпляр бота.
// Register the BookingDialog.
Для справки ниже приведены определения классов, которые используются в описанном выше вызове для создания бота.
public class DialogAndWelcomeBot<T> : DialogBot<T>
public class DialogBot<T> : ActivityHandler
where T : Dialog
public class MainDialog : ComponentDialog
index.js
Наконец, в index.js
создается бот.
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');
Для справки ниже приведены определения классов, которые используются в описанном выше вызове для создания бота.
class MainDialog extends ComponentDialog {
class DialogAndWelcomeBot extends DialogBot {
class DialogBot extends ActivityHandler {
Application.java
Наконец, в Application.java
создается бот.
@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);
}
Для справки ниже приведены определения классов, которые используются в описанном выше вызове для создания бота.
public class DialogAndWelcomeBot<T extends Dialog> extends DialogBot {
public class DialogBot<T extends Dialog> extends ActivityHandler {
public class MainDialog extends ComponentDialog {
app.py Наконец, в app.py
создается бот.
# Create dialogs and Bot
RECOGNIZER = FlightBookingRecognizer(CONFIG)
BOOKING_DIALOG = BookingDialog()
DIALOG = MainDialog(RECOGNIZER, BOOKING_DIALOG)
BOT = DialogAndWelcomeBot(CONVERSATION_STATE, USER_STATE, DIALOG)
Для справки ниже приведены определения классов, которые используются в вызове для создания бота.
class MainDialog(ComponentDialog):
class DialogAndWelcomeBot(DialogBot):
class DialogBot(ActivityHandler):