El control de interrupciones es un aspecto importante de un bot sólido. Los usuarios no siempre seguirán el flujo de conversación que ha definido paso a paso. Es posible que intenten formular una pregunta en mitad del proceso o simplemente deseen cancelarlo en lugar de completarlo. Este artículo describe algunas maneras habituales de controlar las interrupciones de usuario en el bot.
El ejemplo de bot principal usa Language Understanding (LUIS) para identificar las intenciones del usuario; sin embargo, la identificación de la intención del usuario no es el foco de este artículo.
Para obtener información sobre la identificación de intenciones de usuario, consulte Comprensión del lenguaje natural y Adición de reconocimiento del lenguaje natural al bot.
El ejemplo utilizado en este artículo modela un bot de reservas de vuelos que usa diálogos para obtener la información del vuelo del usuario. En cualquier momento durante la conversación con el bot, el usuario puede emitir los comandos help o cancel para provocar una interrupción. Se controlan dos tipos de interrupciones:
Para usar diálogos, instale el paquete de NuGet Microsoft.Bot.Builder.Dialogs.
Dialogs\CancelAndHelpDialog.cs
Implemente la clase CancelAndHelpDialog
para controlar las interrupciones de usuario. Los diálogos cancelables BookingDialog
y DateResolverDialog
derivan de esta clase.
public class CancelAndHelpDialog : ComponentDialog
En la clase CancelAndHelpDialog
, el método OnContinueDialogAsync
llama al método InterruptAsync
para comprobar si el usuario ha interrumpido el flujo normal. Si se interrumpe el flujo, se llama a los métodos de la clase base; en caso contrario, se devuelve el valor devuelto desde 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);
}
Si el usuario escribe "help", el método InterruptAsync
envía un mensaje y, a continuación, llama a DialogTurnResult (DialogTurnStatus.Waiting)
para indicar que el diálogo en la parte superior espera una respuesta del usuario. De este modo, se interrumpe el flujo de conversación solo para un turno y, en el siguiente turno, la conversación continúa donde se dejó.
Si el usuario escribe "cancel", llama a CancelAllDialogsAsync
en el contexto de diálogo interno, que borra la pila de diálogos y hace que se cierre con un estado cancelado y sin valor de resultado. Para MainDialog
(que se muestra más adelante), parece que el diálogo de reservas finalizó y devolvió NULL, de modo similar a cuando el usuario decide no confirmar su reserva.
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;
}
Para usar diálogos, instale el paquete de npm botbuilder-dialogs.
dialogs/cancelAndHelpDialog.js
Implemente la clase CancelAndHelpDialog
para controlar las interrupciones de usuario. Los diálogos cancelables BookingDialog
y DateResolverDialog
extienden esta clase.
class CancelAndHelpDialog extends ComponentDialog {
En la clase CancelAndHelpDialog
, el método onContinueDialog
llama al método interrupt
para comprobar si el usuario ha interrumpido el flujo normal. Si se interrumpe el flujo, se llama a los métodos de la clase base; en caso contrario, se devuelve el valor devuelto desde interrupt
.
async onContinueDialog(innerDc) {
const result = await this.interrupt(innerDc);
if (result) {
return result;
}
return await super.onContinueDialog(innerDc);
}
Si el usuario escribe "help", el método interrupt
envía un mensaje y, a continuación, devuelve un objeto { status: DialogTurnStatus.waiting }
para indicar que el diálogo en la parte superior espera una respuesta del usuario. De este modo, se interrumpe el flujo de conversación solo para un turno y, en el siguiente turno, la conversación continúa donde se dejó.
Si el usuario escribe "cancel", llama a cancelAllDialogs
en el contexto de diálogo interno, que borra la pila de diálogos y hace que se cierre con un estado cancelado y sin valor de resultado. Para MainDialog
(que se muestra más adelante), parece que el diálogo de reservas finalizó y devolvió NULL, de modo similar a cuando el usuario decide no confirmar su reserva.
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
Implemente la clase CancelAndHelpDialog
para controlar las interrupciones de usuario. Los diálogos cancelables BookingDialog
y DateResolverDialog
derivan de esta clase.
public class CancelAndHelpDialog extends ComponentDialog {
En la clase CancelAndHelpDialog
, el método onContinueDialog
llama al método interrupt
para comprobar si el usuario ha interrumpido el flujo normal. Si se interrumpe el flujo, se llama a los métodos de la clase base; en caso contrario, se devuelve el valor devuelto desde interrupt
.
@Override
protected CompletableFuture<DialogTurnResult> onContinueDialog(DialogContext innerDc) {
return interrupt(innerDc).thenCompose(result -> {
if (result != null) {
return CompletableFuture.completedFuture(result);
}
return super.onContinueDialog(innerDc);
});
}
Si el usuario escribe "help", el método interrupt
envía un mensaje y, a continuación, llama a DialogTurnResult(DialogTurnStatus.WAITING)
para indicar que el diálogo en la parte superior espera una respuesta del usuario. De este modo, se interrumpe el flujo de conversación solo para un turno y, en el siguiente turno, la conversación continúa donde se dejó.
Si el usuario escribe "cancel", llama a cancelAllDialogs
en el contexto de diálogo interno, que borra la pila de diálogos y hace que se cierre con un estado cancelado y sin valor de resultado. Para MainDialog
(que se muestra más adelante), parece que el diálogo de reservas finalizó y devolvió NULL, de modo similar a cuando el usuario decide no confirmar su reserva.
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);
}
Para usar diálogos, instale el paquete botbuilder-dialogs
y asegúrese de que el archivo requirements.txt
de ejemplo contiene la referencia adecuada, como botbuilder-dialogs>=4.5.0
.
Para más información sobre la instalación de los paquetes, consulte el archivo README del repositorio de ejemplos.
Nota:
Al ejecutar pip install botbuilder-dialogs
, también se instalarán botbuilder-core
, botbuilder-connector
y botbuilder-schema
.
dialogs/cancel-and-help-dialog.py
Implemente la clase CancelAndHelpDialog
para controlar las interrupciones de usuario. Los diálogos cancelables BookingDialog
y DateResolverDialog
derivan de esta clase.
class CancelAndHelpDialog(ComponentDialog):
En la clase CancelAndHelpDialog
, el método on_continue_dialog
llama al método interrupt
para comprobar si el usuario ha interrumpido el flujo normal. Si se interrumpe el flujo, se llama a los métodos de la clase base; en caso contrario, se devuelve el valor devuelto desde 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)
Si el usuario escribe "help" or "?", el método interrupt
envía un mensaje y, a continuación, llama a DialogTurnResult(DialogTurnStatus.Waiting)
para indicar que el diálogo de la parte superior de la pila espera una respuesta del usuario. De este modo, se interrumpe el flujo de conversación solo para un turno y, en el siguiente turno, la conversación continúa donde se dejó.
Si el usuario escribe "cancel" o "quit", llama a cancel_all_dialogs()
en el contexto del diálogo interno, que borra la pila de diálogos y hace que se cierre con un estado cancelado y sin valor de resultado. Para MainDialog
, que se muestra más adelante, parece que el diálogo de reservas finalizó y devolvió NULL, de modo similar a cuando el usuario decide no confirmar su reserva.
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
Una vez implementada la clase de control de interrupciones, compruebe lo que ocurre cuando este bot recibe un nuevo mensaje del usuario.
Dialogs\MainDialog.cs
Cuando llega la nueva actividad de mensaje, el bot ejecuta MainDialog
. MainDialog
pide al usuario en qué puede ayudarle. Y, a continuación, inicia BookingDialog
en el método MainDialog.ActStepAsync
, con una llamada a BeginDialogAsync
tal como se muestra a continuación.
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);
}
Seguidamente, en el método FinalStepAsync
de la clase MainDialog
, el diálogo de reservas finaliza y la reserva se considera completada o cancelada.
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);
}
El código de BookingDialog
no se muestra aquí, ya que no está directamente relacionado con el control de interrupciones. Se utiliza para pedir a los usuarios los detalles de la reserva. Puede encontrar dicho código en Dialogs\BookingDialogs.cs.
dialogs/mainDialog.js
Cuando llega la nueva actividad de mensaje, el bot ejecuta MainDialog
. MainDialog
pide al usuario en qué puede ayudarle. Y, a continuación, inicia bookingDialog
en el método MainDialog.actStep
, con una llamada a beginDialog
tal como se muestra a continuación.
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();
}
Seguidamente, en el método finalStep
de la clase MainDialog
, el diálogo de reservas finaliza y la reserva se considera completada o cancelada.
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?' });
}
El código de BookingDialog
no se muestra aquí, ya que no está directamente relacionado con el control de interrupciones. Se utiliza para pedir a los usuarios los detalles de la reserva. Puede encontrar dicho código en dialogs/bookingDialogs.js.
MainDialog.java
Cuando llega la nueva actividad de mensaje, el bot ejecuta MainDialog
. MainDialog
pide al usuario en qué puede ayudarle. Y, a continuación, inicia BookingDialog
en el método MainDialog.actStep
, con una llamada a beginDialog
tal como se muestra a continuación.
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));
}
});
}
Seguidamente, en el método finalStep
de la clase MainDialog
, el diálogo de reservas finaliza y la reserva se considera completada o cancelada.
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));
}
El código de BookingDialog
no se muestra aquí, ya que no está directamente relacionado con el control de interrupciones. Se utiliza para pedir a los usuarios los detalles de la reserva. Puede encontrar dicho código en BookingDialogs.java.
dialogs/main_dialog.py
Cuando llega la nueva actividad de mensaje, el bot ejecuta MainDialog
. MainDialog
pide al usuario en qué puede ayudarle. Y, a continuación, inicia bookingDialog
en el método act_step
, con una llamada a begin_dialog
tal como se muestra a continuación.
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)
Seguidamente, en el método final_step
de la clase MainDialog
, el diálogo de reservas finaliza y la reserva se considera completada o cancelada.
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)
El controlador de errores del adaptador controla las excepciones que no se detectaron en el bot.
AdapterWithErrorHandler.cs
En el ejemplo, el controlador OnTurnError
del adaptador recibe las excepciones producidas por la lógica de turnos del bot. Si se produce una excepción, el controlador elimina el estado de la conversación actual para impedir que el bot se bloquee en un bucle de error causado por estar en mal estado.
{
// 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
En el ejemplo, el controlador onTurnError
del adaptador recibe las excepciones producidas por la lógica de turnos del bot. Si se produce una excepción, el controlador elimina el estado de la conversación actual para impedir que el bot se bloquee en un bucle de error causado por estar en mal estado.
// 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.
Al registrar un AdapterWithErrorHandler
con el marco Spring en Application.java para BotFrameworkHttpAdapter
en este ejemplo, el controlador del adaptador onTurnError
recibe las excepciones producidas por la lógica de turnos del bot. Si se produce una excepción, el controlador elimina el estado de la conversación actual para impedir que el bot se bloquee en un bucle de error causado por estar en mal estado. En el SDK de Java, se implementa AdapterWithErrorHandler
como parte del SDK y se incluye en el paquete com.microsoft.bot.integration. Consulte el código fuente del SDK de Java para obtener más información sobre la implementación de este adaptador.
adapter_with_error_handler.py
En el ejemplo, el controlador on_error
del adaptador recibe las excepciones producidas por la lógica de turnos del bot. Si se produce una excepción, el controlador elimina el estado de la conversación actual para impedir que el bot se bloquee en un bucle de error causado por estar en mal estado.
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
Por último, en Startup.cs
, se crea el bot como transitorio y, en cada turno, se crea una nueva instancia del bot.
// Register the BookingDialog.
Como referencia, estas son las definiciones de clase que se usan en la llamada para crear el bot anterior.
public class DialogAndWelcomeBot<T> : DialogBot<T>
public class DialogBot<T> : ActivityHandler
where T : Dialog
public class MainDialog : ComponentDialog
index.js
Por último, en index.js
, se crea el bot.
const dialog = new MainDialog(luisRecognizer, bookingDialog);
const bot = new DialogAndWelcomeBot(conversationState, userState, dialog);
// Create HTTP server
const server = restify.createServer();
server.use(restify.plugins.bodyParser());
server.listen(process.env.port || process.env.PORT || 3978, function() {
console.log(`\n${ server.name } listening to ${ server.url }`);
console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator');
Como referencia, estas son las definiciones de clase que se usan en la llamada para crear el bot anterior.
class MainDialog extends ComponentDialog {
class DialogAndWelcomeBot extends DialogBot {
class DialogBot extends ActivityHandler {
Application.java
Por último, en Application.java
, se crea el bot.
@Bean
public Bot getBot(
Configuration configuration,
UserState userState,
ConversationState conversationState
) {
FlightBookingRecognizer recognizer = new FlightBookingRecognizer(configuration);
MainDialog dialog = new MainDialog(recognizer, new BookingDialog());
return new DialogAndWelcomeBot<>(conversationState, userState, dialog);
}
Como referencia, estas son las definiciones de clase que se usan en la llamada para crear el bot anterior.
public class DialogAndWelcomeBot<T extends Dialog> extends DialogBot {
public class DialogBot<T extends Dialog> extends ActivityHandler {
public class MainDialog extends ComponentDialog {
app.py Por último, en app.py
, se crea el bot.
# Create dialogs and Bot
RECOGNIZER = FlightBookingRecognizer(CONFIG)
BOOKING_DIALOG = BookingDialog()
DIALOG = MainDialog(RECOGNIZER, BOOKING_DIALOG)
BOT = DialogAndWelcomeBot(CONVERSATION_STATE, USER_STATE, DIALOG)
Como referencia, estas son las definiciones de clase que se usan en la llamada para crear el bot.
class MainDialog(ComponentDialog):
class DialogAndWelcomeBot(DialogBot):
class DialogBot(ActivityHandler):