處理中斷是健全 Bot 的重要層面。 使用者不會一律遵循您定義的交談流程,逐步執行。 他們可能會嘗試在過程中提出問題,或只是想取消它,而不是完成它。 本文說明處理 Bot 中用戶中斷的一些常見方式。
核心 Bot 範例會使用 Language Understanding (LUIS) 來識別用戶意圖;不過,識別使用者意圖並不是本文的重點。
如需識別使用者意圖的相關信息,請參閱 自然語言理解 和 將自然語言理解新增至 Bot。
若要使用對話框,請安裝 Microsoft.Bot.Builder.Dialogs NuGet 套件。
Dialogs\CancelAndHelpDialog.cs
實作 類別 CancelAndHelpDialog
來處理用戶中斷。 可取消的對話框, BookingDialog
並 DateResolverDialog
衍生自這個類別。
public class CancelAndHelpDialog : ComponentDialog
在類別中CancelAndHelpDialog
OnContinueDialogAsync
,方法會呼叫 InterruptAsync
方法,以檢查使用者是否已中斷一般流程。 如果流程中斷,則會呼叫基類方法;否則,會傳回 來自的 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);
}
如果使用者輸入「說明」,則 InterruptAsync
方法會傳送訊息,然後呼叫 DialogTurnResult (DialogTurnStatus.Waiting)
以指出頂端的對話正在等候用戶的回應。 如此一來,交談流程只會中斷一個回合,而下一個回合會從交談離開的地方繼續進行。
如果使用者輸入 「cancel」,它會在其內部對話內容上呼叫 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;
}
若要使用對話框,請安裝 botbuilder-dialogs npm 套件。
dialogs/cancelAndHelpDialog.js
實作 類別 CancelAndHelpDialog
來處理用戶中斷。 可取消的對話框, BookingDialog
並 DateResolverDialog
擴充這個類別。
class CancelAndHelpDialog extends ComponentDialog {
在類別中CancelAndHelpDialog
onContinueDialog
,方法會呼叫 interrupt
方法,以檢查使用者是否已中斷一般流程。 如果流程中斷,則會呼叫基類方法;否則,會傳回 來自的 interrupt
傳回值。
async onContinueDialog(innerDc) {
const result = await this.interrupt(innerDc);
if (result) {
return result;
}
return await super.onContinueDialog(innerDc);
}
如果使用者輸入「說明」,則 interrupt
方法會傳送訊息,然後傳回 { status: DialogTurnStatus.waiting }
物件,指出頂端的對話正在等候用戶的回應。 如此一來,交談流程只會中斷一個回合,而下一個回合會從交談離開的地方繼續進行。
如果使用者輸入 「cancel」,它會在其內部對話內容上呼叫 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
onContinueDialog
,方法會呼叫 interrupt
方法,以檢查使用者是否已中斷一般流程。 如果流程中斷,則會呼叫基類方法;否則,會傳回 來自的 interrupt
傳回值。
@Override
protected CompletableFuture<DialogTurnResult> onContinueDialog(DialogContext innerDc) {
return interrupt(innerDc).thenCompose(result -> {
if (result != null) {
return CompletableFuture.completedFuture(result);
}
return super.onContinueDialog(innerDc);
});
}
如果使用者輸入「說明」,則 interrupt
方法會傳送訊息,然後呼叫 DialogTurnResult(DialogTurnStatus.WAITING)
以指出頂端的對話正在等候用戶的回應。 如此一來,交談流程只會中斷一個回合,而下一個回合會從交談離開的地方繼續進行。
如果使用者輸入 「cancel」,它會在其內部對話內容上呼叫 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
on_continue_dialog
,方法會呼叫 interrupt
方法,以檢查使用者是否已中斷一般流程。 如果流程中斷,則會呼叫基類方法;否則,會傳回 來自的 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)
如果使用者輸入 「help」 或 「?“, interrupt
方法會傳送訊息,然後呼叫 DialogTurnResult(DialogTurnStatus.Waiting)
以指出堆疊頂端的對話方塊正在等候用戶的回應。 如此一來,交談流程只會中斷一個回合,而下一個回合會從交談離開的地方繼續進行。
如果使用者輸入 「cancel」 或 「quit」 ,它會在其內部對話內容上呼叫 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
當新的訊息活動送達時,Bot 會執行 MainDialog
。 會 MainDialog
提示使用者提供其可協助的內容。 然後它會在方法中MainDialog.ActStepAsync
啟動 BookingDialog
,並呼叫 ,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);
}
接下來,在類別的 MainDialog
方法中FinalStepAsync
,預約對話框已結束,並將預約視為完成或取消。
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
當新的訊息活動送達時,Bot 會執行 MainDialog
。 會 MainDialog
提示使用者提供其可協助的內容。 然後它會在方法中MainDialog.actStep
啟動 bookingDialog
,並呼叫 ,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();
}
接下來,在類別的 MainDialog
方法中finalStep
,預約對話框已結束,並將預約視為完成或取消。
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
當新的訊息活動送達時,Bot 會執行 MainDialog
。 會 MainDialog
提示使用者提供其可協助的內容。 然後,它會在方法中MainDialog.actStep
啟動 BookingDialog
,並呼叫 ,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));
}
});
}
接下來,在類別的 MainDialog
方法中finalStep
,預約對話框已結束,並將預約視為完成或取消。
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
當新的訊息活動送達時,Bot 會執行 MainDialog
。 會 MainDialog
提示使用者提供其可協助的內容。 然後它會在方法中act_step
啟動 bookingDialog
,並呼叫 ,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)
接下來,在類別的 MainDialog
方法中final_step
,預約對話框已結束,並將預約視為完成或取消。
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
會收到 Bot 回合邏輯擲回的任何例外狀況。 如果擲回例外狀況,處理程式會刪除目前交談的交談狀態,以防止 Bot 卡在錯誤迴圈中,因為狀態不正確。
{
// 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
會收到 Bot 回合邏輯擲回的任何例外狀況。 如果擲回例外狀況,處理程式會刪除目前交談的交談狀態,以防止 Bot 卡在錯誤迴圈中,因為狀態不正確。
// 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.
藉由在此範例中向 Application.java BotFrameworkHttpAdapter
的 Spring 架構註冊 AdapterWithErrorHandler
,配接器處理程式onTurnError
會收到 Bot 回合邏輯擲回的任何例外狀況。 如果擲回例外狀況,處理程式會刪除目前交談的交談狀態,以防止 Bot 卡在錯誤迴圈中,因為狀態不正確。 在 Java SDK 中,會AdapterWithErrorHandler
實作為 SDK 的一部分,並包含在 com.microsoft.bot.integration 套件中。 如需此配接器實作的詳細資訊,請參閱 Java SDK 原始程式碼。
adapter_with_error_handler.py
在範例中,配接器處理程式 on_error
會收到 Bot 回合邏輯擲回的任何例外狀況。 如果擲回例外狀況,處理程式會刪除目前交談的交談狀態,以防止 Bot 卡在錯誤迴圈中,因為狀態不正確。
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
中,Bot 會建立為暫時性,並在每一回合建立 Bot 的新實例。
// Register the BookingDialog.
如需參考,以下是呼叫中用來建立上述 Bot 的類別定義。
public class DialogAndWelcomeBot<T> : DialogBot<T>
public class DialogBot<T> : ActivityHandler
where T : Dialog
public class MainDialog : ComponentDialog
index.js
最後,在 index.js
中,會建立 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');
如需參考,以下是呼叫中用來建立上述 Bot 的類別定義。
class MainDialog extends ComponentDialog {
class DialogAndWelcomeBot extends DialogBot {
class DialogBot extends ActivityHandler {
Application.java
最後,在 Application.java
中,會建立 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);
}
如需參考,以下是呼叫中用來建立上述 Bot 的類別定義。
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
中,會建立 Bot。
# Create dialogs and Bot
RECOGNIZER = FlightBookingRecognizer(CONFIG)
BOOKING_DIALOG = BookingDialog()
DIALOG = MainDialog(RECOGNIZER, BOOKING_DIALOG)
BOT = DialogAndWelcomeBot(CONVERSATION_STATE, USER_STATE, DIALOG)
如需參考,以下是呼叫中用來建立 Bot 的類別定義。
class MainDialog(ComponentDialog):
class DialogAndWelcomeBot(DialogBot):
class DialogBot(ActivityHandler):