다음을 통해 공유


사용자 중단 처리

적용 대상: SDK v4

중단 처리는 강력한 봇의 중요한 측면입니다. 사용자가 항상 정의된 대화 흐름을 단계별로 따르지는 않습니다. 프로세스 중간에 질문을 하거나 완료하는 대신 취소하려고 할 수 있습니다. 이 문서에서는 봇에서 사용자 중단을 처리하는 몇 가지 일반적인 방법을 설명합니다.

참고 항목

Bot Framework JavaScript, C#및 Python SDK는 계속 지원되지만 Java SDK는 2023년 11월에 종료되는 최종 장기 지원으로 사용 중지됩니다.

Java SDK를 사용하여 빌드된 기존 봇은 계속 작동합니다.

새 봇 빌드의 경우 Microsoft Copilot Studio를 사용하고 올바른 부조종사 솔루션 선택에 대해 알아봅니다.

자세한 내용은 봇 빌드의 미래를 참조 하세요.

필수 조건

핵심 봇 샘플은 LUIS(Language Understanding)를 사용하여 사용자 의도를 식별합니다. 그러나 사용자 의도를 식별하는 것은 이 문서의 초점이 아닙니다. 사용자 의도 를 식별하는 방법에 대한 자세한 내용은 자연어 이해에 자연어 이해 추가를 참조하세요.

참고 항목

LUIS(Language Understanding)는 2025년 10월 1일에 사용 중지됩니다. 2023년 4월 1일부터 새 LUIS 리소스를 만들 수 없습니다. 이제 최신 버전의 언어 이해가 Azure AI Language의 일부로 제공됩니다.

Azure AI Language의 기능인 CLU(대화형 언어 이해)는 업데이트된 LUIS 버전입니다. Bot Framework SDK의 언어 이해 지원에 대한 자세한 내용은 자연어 이해를 참조하세요.

이 샘플 정보

이 문서에 사용된 샘플은 대화 상자를 사용하여 사용자로부터 항공편 정보를 가져오는 항공편 예약 봇을 모델로 합니다. 사용자는 봇과 대화 중에 언제든지 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;
}

각 턴마다 중단 확인

인터럽트 처리 클래스가 구현되면 이 봇이 사용자로부터 새 메시지를 받을 때 발생하는 작업을 검토합니다.

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 해당 코드를 찾을 수 있습니다.

예기치 않은 오류 처리

어댑터의 오류 처리기는 봇에서 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");
    };
}

서비스 등록

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

봇 테스트

  1. 아직 설치하지 않은 경우 Bot Framework Emulator설치합니다.
  2. 머신에서 로컬로 샘플을 실행합니다.
  3. 에뮬레이터를 시작하고 봇에 연결한 다음, 아래와 같은 메시지를 보냅니다.

추가 정보

  • C#, JavaScript, Python 또는 Java의 24.bot-authentication-msgraph 샘플은 로그아웃 요청을 처리하는 방법을 보여줍니다. 중단 처리를 위해 여기에 표시된 것과 비슷한 패턴을 사용합니다.

  • 아무 작업도 수행하지 않고 사용자에게 무슨 일이 일어나고 있는지 궁금해하는 대신 기본 응답을 보내야 합니다. 기본 응답은 사용자가 다시 궤도에 복귀할 수 있도록 봇이 이해하는 명령을 사용자에게 알려야 합니다.

  • 턴의 어느 시점에서든 턴 컨텍스트의 응답 속성 은 봇이 이 턴에 사용자에게 메시지를 보냈는지 여부를 나타냅니다. 턴이 끝나기 전에 봇은 입력에 대한 간단한 승인인 경우에도 사용자에게 메시지를 보내야 합니다.