중단 처리는 강력한 봇의 중요한 측면입니다. 사용자가 항상 정의된 대화 흐름을 단계별로 따르지는 않습니다. 프로세스 중간에 질문을 하거나 완료하는 대신 취소하려고 할 수 있습니다. 이 문서에서는 봇에서 사용자 중단을 처리하는 몇 가지 일반적인 방법을 설명합니다.
핵심 봇 샘플은 LUIS(Language Understanding)를 사용하여 사용자 의도를 식별합니다. 그러나 사용자 의도를 식별하는 것은 이 문서의 초점이 아닙니다.
사용자 의도 를 식별하는 방법에 대한 자세한 내용은 자연어 이해 및 봇에 자연어 이해 추가를 참조하세요.
이 문서에 사용된 샘플은 대화 상자를 사용하여 사용자로부터 항공편 정보를 가져오는 항공편 예약 봇을 모델로 합니다. 사용자는 봇과 대화 중에 언제든지 help 또는 cancel 명령을 실행하여 대화를 중단할 수 있습니다. 여기서 처리하는 중단 유형은 두 가지가 있습니다.
대화를 사용하려면 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);
}
사용자가 "help"를 입력하면 메서드는 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
처리하는 클래스를 구현합니다. 취소할 수 있는 대화 상자이며 DateResolverDialog
이 클래스를 확장합니다BookingDialog
.
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);
}
사용자가 "help"를 입력하면 메서드는 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);
});
}
사용자가 "help"를 입력하면 메서드는 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-connector
및 botbuilder-schema
.도 설치botbuilder-core
됩니다.
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
새 메시지 작업이 도착하면 봇은 .를 실행합니다 MainDialog
. MainDialog
사용자에게 도움이 될 수 있는 내용을 묻는 메시지가 표시됩니다. 그런 다음 아래와 같이 호출을 BeginDialogAsync
사용하여 메서드에서 MainDialog.ActStepAsync
시작합니다BookingDialog
.
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
새 메시지 작업이 도착하면 봇은 .를 실행합니다 MainDialog
. MainDialog
사용자에게 도움이 될 수 있는 내용을 묻는 메시지가 표시됩니다. 그런 다음 아래와 같이 호출을 beginDialog
사용하여 메서드에서 MainDialog.actStep
시작합니다bookingDialog
.
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
코드는 중단 처리와 직접 관련이 없으므로 여기에 표시되지 않습니다. 예약 세부 정보를 묻는 메시지를 사용자에게 표시하는 데 사용됩니다. 대화 상자/bookingDialogs.js 해당 코드를 찾을 수 있습니다.
MainDialog.java
새 메시지 작업이 도착하면 봇은 .를 실행합니다 MainDialog
. MainDialog
사용자에게 도움이 될 수 있는 내용을 묻는 메시지가 표시됩니다. 그런 다음 아래와 같이 호출을 beginDialog
사용하여 메서드에서 MainDialog.actStep
시작합니다BookingDialog
.
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
새 메시지 작업이 도착하면 봇은 .를 실행합니다 MainDialog
. MainDialog
사용자에게 도움이 될 수 있는 내용을 묻는 메시지가 표시됩니다. 그런 다음 아래와 같이 호출을 begin_dialog
사용하여 메서드에서 act_step
시작합니다bookingDialog
.
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)
어댑터의 오류 처리기는 봇에서 catch되지 않은 예외를 처리합니다.
AdapterWithErrorHandler.cs
샘플에서 어댑터의 OnTurnError
처리기는 봇의 턴 논리에 의해 throw된 예외를 수신합니다. 예외가 throw된 경우 처리기는 현재 대화에 대한 대화 상태를 삭제하여 봇이 잘못된 상태에 있어 발생하는 오류 루프에 갇히지 않도록 합니다.
{
// 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
처리기는 봇의 턴 논리에 의해 throw된 예외를 수신합니다. 예외가 throw된 경우 처리기는 현재 대화에 대한 대화 상태를 삭제하여 봇이 잘못된 상태에 있어 발생하는 오류 루프에 갇히지 않도록 합니다.
// 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
처리기는 봇의 턴 논리에 의해 throw된 예외를 수신합니다. 예외가 throw된 경우 처리기는 현재 대화에 대한 대화 상태를 삭제하여 봇이 잘못된 상태에 있어 발생하는 오류 루프에 갇히지 않도록 합니다. Java SDK에서 SDK AdapterWithErrorHandler
의 일부로 구현되며 com.microsoft.bot.integration 패키지에 포함됩니다. 이 어댑터의 구현에 대한 자세한 내용은 Java SDK 소스 코드를 참조하세요.
adapter_with_error_handler.py
샘플에서 어댑터의 on_error
처리기는 봇의 턴 논리에 의해 throw된 예외를 수신합니다. 예외가 throw된 경우 처리기는 현재 대화에 대한 대화 상태를 삭제하여 봇이 잘못된 상태에 있어 발생하는 오류 루프에 갇히지 않도록 합니다.
def __init__(
self,
settings: ConfigurationBotFrameworkAuthentication,
conversation_state: ConversationState,
):
super().__init__(settings)
self._conversation_state = conversation_state
# Catch-all for errors.
async def on_error(context: TurnContext, error: Exception):
# This check writes out errors to console log
# NOTE: In production environment, you should consider logging this to Azure
# application insights.
print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
traceback.print_exc()
# Send a message to the user
await context.send_activity("The bot encountered an error or bug.")
await context.send_activity(
"To continue to run this bot, please fix the bot source code."
)
# Send a trace activity if we're talking to the Bot Framework Emulator
if context.activity.channel_id == "emulator":
# Create a trace activity that contains the error object
trace_activity = Activity(
label="TurnError",
name="on_turn_error Trace",
timestamp=datetime.utcnow(),
type=ActivityTypes.trace,
value=f"{error}",
value_type="https://www.botframework.com/schemas/error",
)
# Send a trace activity, which will be displayed in Bot Framework Emulator
await context.send_activity(trace_activity)
# Clear out state
nonlocal self
await self._conversation_state.delete(context)
self.on_turn_error = on_error
Startup.cs
마지막으로, Startup.cs
봇은 임시로 만들어지고, 매 턴마다 봇의 새 인스턴스가 만들어집니다.
// Register the BookingDialog.
참조를 위해 위의 봇을 만들기 위해 호출에 사용되는 클래스 정의는 다음과 같습니다.
public class DialogAndWelcomeBot<T> : DialogBot<T>
public class DialogBot<T> : ActivityHandler
where T : Dialog
public class MainDialog : ComponentDialog
index.js
마지막으로, index.js
봇이 만들어집니다.
const dialog = new MainDialog(luisRecognizer, bookingDialog);
const bot = new DialogAndWelcomeBot(conversationState, userState, dialog);
// Create HTTP server
const server = restify.createServer();
server.use(restify.plugins.bodyParser());
server.listen(process.env.port || process.env.PORT || 3978, function() {
console.log(`\n${ server.name } listening to ${ server.url }`);
console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator');
참조를 위해 위의 봇을 만들기 위해 호출에 사용되는 클래스 정의는 다음과 같습니다.
class MainDialog extends ComponentDialog {
class DialogAndWelcomeBot extends DialogBot {
class DialogBot extends ActivityHandler {
Application.java
마지막으로, Application.java
봇이 만들어집니다.
@Bean
public Bot getBot(
Configuration configuration,
UserState userState,
ConversationState conversationState
) {
FlightBookingRecognizer recognizer = new FlightBookingRecognizer(configuration);
MainDialog dialog = new MainDialog(recognizer, new BookingDialog());
return new DialogAndWelcomeBot<>(conversationState, userState, dialog);
}
참조를 위해 위의 봇을 만들기 위해 호출에 사용되는 클래스 정의는 다음과 같습니다.
public class DialogAndWelcomeBot<T extends Dialog> extends DialogBot {
public class DialogBot<T extends Dialog> extends ActivityHandler {
public class MainDialog extends ComponentDialog {
app.py 마지막으로, app.py
에서 봇이 생성됩니다.
# Create dialogs and Bot
RECOGNIZER = FlightBookingRecognizer(CONFIG)
BOOKING_DIALOG = BookingDialog()
DIALOG = MainDialog(RECOGNIZER, BOOKING_DIALOG)
BOT = DialogAndWelcomeBot(CONVERSATION_STATE, USER_STATE, DIALOG)
참조를 위해 봇을 만들기 위해 호출에 사용되는 클래스 정의는 다음과 같습니다.
class MainDialog(ComponentDialog):
class DialogAndWelcomeBot(DialogBot):
class DialogBot(ActivityHandler):