Lidar com interrupções é um aspeto importante de um bot robusto. Os usuários nem sempre seguirão seu fluxo de conversa definido, passo a passo. Eles podem tentar fazer uma pergunta no meio do processo, ou simplesmente querer cancelá-la em vez de concluí-la. Este artigo descreve algumas maneiras comuns de lidar com interrupções de usuários em seu bot.
O exemplo de bot principal usa Language Understanding (LUIS) para identificar as intenções do usuário; no entanto, identificar a intenção do usuário não é o foco deste artigo.
Para obter informações sobre como identificar as intenções do usuário, consulte Compreensão de linguagem natural e Adicionar compreensão de linguagem natural ao seu bot.
O exemplo usado neste artigo modela um bot de reserva de voo que usa caixas de diálogo para obter informações de voo do usuário. A qualquer momento durante a conversa com o bot, o usuário pode emitir comandos de ajuda ou cancelar para causar uma interrupção. Existem dois tipos de interrupções tratadas:
Para usar caixas de diálogo, instale o pacote NuGet Microsoft.Bot.Builder.Dialogs .
Caixas de diálogo\CancelAndHelpDialog.cs
Implemente a classe para lidar com interrupções do CancelAndHelpDialog
usuário. As caixas de diálogo BookingDialog
canceláveis e DateResolverDialog
derivam dessa classe.
public class CancelAndHelpDialog : ComponentDialog
CancelAndHelpDialog
Na classe, o OnContinueDialogAsync
método chama o InterruptAsync
método para verificar se o usuário interrompeu o fluxo normal. Se o fluxo for interrompido, os métodos de classe base serão chamados; caso contrário, o valor de retorno do InterruptAsync
é retornado.
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
var result = await InterruptAsync(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
Se o usuário digitar "ajuda", o InterruptAsync
método envia uma mensagem e, em seguida, chama DialogTurnResult (DialogTurnStatus.Waiting)
para indicar que a caixa de diálogo na parte superior está aguardando uma resposta do usuário. Desta forma, o fluxo de conversa é interrompido apenas por um turno, e o próximo turno continua de onde a conversa parou.
Se o usuário digitar "cancelar", ele chamará CancelAllDialogsAsync
seu contexto de diálogo interno, o que limpa sua pilha de diálogo e faz com que ele saia com um status cancelado e nenhum valor de resultado. Para o MainDialog
(mostrado mais tarde), aparecerá que a caixa de diálogo de reserva terminou e retornou nulo, semelhante a quando o usuário opta por não confirmar sua 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 caixas de diálogo, instale o pacote npm botbuilder-dialogs .
diálogos/cancelAndHelpDialog.js
Implemente a classe para lidar com interrupções do CancelAndHelpDialog
usuário. As caixas de diálogo BookingDialog
canceláveis e DateResolverDialog
estender esta classe.
class CancelAndHelpDialog extends ComponentDialog {
CancelAndHelpDialog
Na classe, o onContinueDialog
método chama o interrupt
método para verificar se o usuário interrompeu o fluxo normal. Se o fluxo for interrompido, os métodos de classe base serão chamados; caso contrário, o valor de retorno do interrupt
é retornado.
async onContinueDialog(innerDc) {
const result = await this.interrupt(innerDc);
if (result) {
return result;
}
return await super.onContinueDialog(innerDc);
}
Se o usuário digitar "help", o interrupt
método envia uma mensagem e, em seguida, retorna um { status: DialogTurnStatus.waiting }
objeto para indicar que a caixa de diálogo na parte superior está aguardando uma resposta do usuário. Desta forma, o fluxo de conversa é interrompido apenas por um turno, e o próximo turno continua de onde a conversa parou.
Se o usuário digitar "cancelar", ele chamará cancelAllDialogs
seu contexto de diálogo interno, o que limpa sua pilha de diálogo e faz com que ele saia com um status cancelado e nenhum valor de resultado. Para o MainDialog
(mostrado mais tarde), aparecerá que a caixa de diálogo de reserva terminou e retornou nulo, semelhante a quando o usuário opta por não confirmar sua 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 a classe para lidar com interrupções do CancelAndHelpDialog
usuário. As caixas de diálogo BookingDialog
canceláveis e DateResolverDialog
derivam dessa classe.
public class CancelAndHelpDialog extends ComponentDialog {
CancelAndHelpDialog
Na classe, o onContinueDialog
método chama o interrupt
método para verificar se o usuário interrompeu o fluxo normal. Se o fluxo for interrompido, os métodos de classe base serão chamados; caso contrário, o valor de retorno do interrupt
é retornado.
@Override
protected CompletableFuture<DialogTurnResult> onContinueDialog(DialogContext innerDc) {
return interrupt(innerDc).thenCompose(result -> {
if (result != null) {
return CompletableFuture.completedFuture(result);
}
return super.onContinueDialog(innerDc);
});
}
Se o usuário digitar "ajuda", o interrupt
método envia uma mensagem e, em seguida, chama DialogTurnResult(DialogTurnStatus.WAITING)
para indicar que a caixa de diálogo na parte superior está aguardando uma resposta do usuário. Desta forma, o fluxo de conversa é interrompido apenas por um turno, e o próximo turno continua de onde a conversa parou.
Se o usuário digitar "cancelar", ele chamará cancelAllDialogs
seu contexto de diálogo interno, o que limpa sua pilha de diálogo e faz com que ele saia com um status cancelado e nenhum valor de resultado. Para o MainDialog
(mostrado mais tarde), aparecerá que a caixa de diálogo de reserva terminou e retornou nulo, semelhante a quando o usuário opta por não confirmar sua 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 caixas de diálogo, instale o botbuilder-dialogs
pacote e certifique-se de que o arquivo de exemplo requirements.txt
contém a referência adequada, como botbuilder-dialogs>=4.5.0
.
Para obter mais informações sobre como instalar os pacotes, consulte o arquivo README do repositório de exemplos.
Nota
A execução pip install botbuilder-dialogs
também instalará botbuilder-core
, botbuilder-connector
e botbuilder-schema
.
diálogos/cancel-and-help-dialog.py
Implemente a classe para lidar com interrupções do CancelAndHelpDialog
usuário. As caixas de diálogo BookingDialog
canceláveis e DateResolverDialog
derivam dessa classe.
class CancelAndHelpDialog(ComponentDialog):
CancelAndHelpDialog
Na classe, o on_continue_dialog
método chama o interrupt
método para verificar se o usuário interrompeu o fluxo normal. Se o fluxo for interrompido, os métodos de classe base serão chamados; caso contrário, o valor de retorno do interrupt
é retornado.
async def on_continue_dialog(self, inner_dc: DialogContext) -> DialogTurnResult:
result = await self.interrupt(inner_dc)
if result is not None:
return result
return await super(CancelAndHelpDialog, self).on_continue_dialog(inner_dc)
Se o usuário digitar "help" ou "?", o interrupt
método envia uma mensagem e, em seguida, chama DialogTurnResult(DialogTurnStatus.Waiting)
para indicar que a caixa de diálogo na parte superior da pilha está aguardando uma resposta do usuário. Desta forma, o fluxo de conversa é interrompido apenas por um turno, e o próximo turno continua de onde a conversa parou.
Se o usuário digitar "cancelar" ou "sair", ele chamará cancel_all_dialogs()
seu contexto de diálogo interno, o que limpa sua pilha de diálogo e faz com que ele saia com um status cancelado e nenhum valor de resultado. Para o MainDialog
, mostrado mais tarde, aparecerá que a caixa de diálogo de reserva terminou e retornou nulo, semelhante a quando o usuário opta por não confirmar sua 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
Depois que a classe de manipulação de interrupção for implementada, revise o que acontece quando esse bot recebe uma nova mensagem do usuário.
Caixas de diálogo\MainDialog.cs
À medida que a nova atividade de mensagem chega, o bot executa o MainDialog
. O MainDialog
solicita ao usuário o que ele pode ajudar. E então ele começa o BookingDialog
MainDialog.ActStepAsync
no método, com uma chamada para BeginDialogAsync
como mostrado abaixo.
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);
}
Em seguida, no FinalStepAsync
método da aula, o diálogo de MainDialog
reserva terminou e a reserva é considerada completa ou 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);
}
O código não BookingDialog
é mostrado aqui, pois não está diretamente relacionado ao tratamento de interrupções. Ele é usado para solicitar aos usuários detalhes da reserva. Você pode encontrar esse código em Dialogs\BookingDialogs.cs.
diálogos/mainDialog.js
À medida que a nova atividade de mensagem chega, o bot executa o MainDialog
. O MainDialog
solicita ao usuário o que ele pode ajudar. E então ele começa o bookingDialog
MainDialog.actStep
no método, com uma chamada para beginDialog
como mostrado abaixo.
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();
}
Em seguida, no finalStep
método da aula, o diálogo de MainDialog
reserva terminou e a reserva é considerada completa ou 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?' });
}
O código não BookingDialog
é mostrado aqui, pois não está diretamente relacionado ao tratamento de interrupções. Ele é usado para solicitar aos usuários detalhes da reserva. Você pode encontrar esse código em diálogos/bookingDialogs.js.
MainDialog.java
À medida que a nova atividade de mensagem chega, o bot executa o MainDialog
. O MainDialog
solicita ao usuário o que ele pode ajudar. E então, ele começa o BookingDialog
no MainDialog.actStep
método, com uma chamada para beginDialog
como mostrado abaixo.
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));
}
});
}
Em seguida, no finalStep
método da aula, o diálogo de MainDialog
reserva terminou e a reserva é considerada completa ou 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));
}
O código não BookingDialog
é mostrado aqui, pois não está diretamente relacionado ao tratamento de interrupções. Ele é usado para solicitar aos usuários detalhes da reserva. Você pode encontrar esse código em BookingDialogs.java.
diálogos/main_dialog.py
À medida que a nova atividade de mensagem chega, o bot executa o MainDialog
. O MainDialog
solicita ao usuário o que ele pode ajudar. E então ele começa o bookingDialog
act_step
no método, com uma chamada para begin_dialog
como mostrado abaixo.
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)
Em seguida, no final_step
método da aula, o diálogo de MainDialog
reserva terminou e a reserva é considerada completa ou 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)
O manipulador de erros do adaptador lida com todas as exceções que não foram capturadas no bot.
AdapterWithErrorHandler.cs
No exemplo, o manipulador do OnTurnError
adaptador recebe todas as exceções lançadas pela lógica de turno do bot. Se houver uma exceção lançada, o manipulador excluirá o estado da conversa atual para evitar que o bot fique preso em um loop de erro causado por estar em um estado incorreto.
{
// 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
No exemplo, o manipulador do onTurnError
adaptador recebe todas as exceções lançadas pela lógica de turno do bot. Se houver uma exceção lançada, o manipulador excluirá o estado da conversa atual para evitar que o bot fique preso em um loop de erro causado por estar em um estado incorreto.
// 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.
Ao registrar um AdapterWithErrorHandler
com a estrutura Spring em Application.java para o neste exemplo, o manipulador do adaptador recebe todas as onTurnError
exceções lançadas pela lógica de turno BotFrameworkHttpAdapter
do seu bot. Se houver uma exceção lançada, o manipulador excluirá o estado da conversa atual para evitar que o bot fique preso em um loop de erro causado por estar em um estado incorreto. No Java SDK, o AdapterWithErrorHandler
é implementado como parte do SDK e está incluído no pacote com.microsoft.bot.integration . Consulte o código-fonte do Java SDK para obter detalhes sobre a implementação deste adaptador.
adapter_with_error_handler.py
No exemplo, o manipulador do on_error
adaptador recebe todas as exceções lançadas pela lógica de turno do bot. Se houver uma exceção lançada, o manipulador excluirá o estado da conversa atual para evitar que o bot fique preso em um loop de erro causado por estar em um estado incorreto.
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
Finalmente, no Startup.cs
, o bot é criado como um transitório e, a cada turno, uma nova instância do bot é criada.
// Register the BookingDialog.
Para referência, aqui estão as definições de classe que são usadas na chamada para criar o bot acima.
public class DialogAndWelcomeBot<T> : DialogBot<T>
public class DialogBot<T> : ActivityHandler
where T : Dialog
public class MainDialog : ComponentDialog
index.js
Finalmente, no index.js
, o bot é criado.
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');
Para referência, aqui estão as definições de classe que são usadas na chamada para criar o bot acima.
class MainDialog extends ComponentDialog {
class DialogAndWelcomeBot extends DialogBot {
class DialogBot extends ActivityHandler {
Application.java
Finalmente, no Application.java
, o bot é criado.
@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);
}
Para referência, aqui estão as definições de classe que são usadas na chamada para criar o bot acima.
public class DialogAndWelcomeBot<T extends Dialog> extends DialogBot {
public class DialogBot<T extends Dialog> extends ActivityHandler {
public class MainDialog extends ComponentDialog {
app.py Finalmente, no app.py
, o bot é criado.
# Create dialogs and Bot
RECOGNIZER = FlightBookingRecognizer(CONFIG)
BOOKING_DIALOG = BookingDialog()
DIALOG = MainDialog(RECOGNIZER, BOOKING_DIALOG)
BOT = DialogAndWelcomeBot(CONVERSATION_STATE, USER_STATE, DIALOG)
Para referência, aqui estão as definições de classe que são usadas na chamada para criar o bot.
class MainDialog(ComponentDialog):
class DialogAndWelcomeBot(DialogBot):
class DialogBot(ActivityHandler):