Chatbot - 봇에 자연어 해석 추가(LUIS)


대화 및 컨텍스트를 통해 사용자가 의미하는 바를 이해하는 기능을 구현하기 어려울 수 있지만, 봇에 자연스러운 대화 느낌을 제공할 수는 있다.

LUIS(Language Understanding)는 바로 이런 역할을 해줘서 봇이 사용자 메세지의 의도를 인식하고, 사용자에게 보다 자연스러운 언어를 사용하여 대화하게 하고, 대화 흐름을 원활하게 만드는 클라우드 기반 API 서비스이다.

공식 문서에서는 항공편 예약 애플리케이션에 LUIS 를 추가해서 사용자 입력에 포함된 의도 및 엔티티를 인식하는 방법을 소개한다.

1. 필수 구성 요소

우선 시작하기 전에 LUIS 계정이 필요하다.

2. 샘플 설명

공식 문서의 예제에서는 LUIS 서비스를 사용하여 사용자 입력을 인식하고 인식된 상위 LUIS 의도를 반환한다.

LUIS에 사용되는 언어 모델에는 Book Flight, Cancel, None 이 세가지 의도가 포함된다.

LUIS는 이 의도들을 사용해서 사용자가 봇에 메세지를 보내는 의도를 파악한다.

또한 언어 모델은 LUIS가 사용자가 보내는 메세지에서 추출할 수 있는 엔터티를 정의한다.

luis-logic-flow

사용자 입력 처리가 완료될때마다 DialogBot이 UserState와 ConversationState의 현재 상태를 저장한다. 샘플에서는 필요한 모든 정보가 수집된 후 데모 항공편 예약을 만든다.

새로운 사용자가 연결되면 OnMemebersAddedAsync가 호출되고 환영 카드가 표시된다.

OnMessageActivityAsync는 수신된 각 사용자 입력에 대해 호출된다. 또 이 모듈은 Run 대화 확장 method를 통해 적절한 대화를 실행한다.

그러면 LUIS 도우미가 호출되어 가장 점수가 높은 사용자 의도를 찾는다. 만약 의도가 “BookFlight”를 반환할 때 도우미가 LUIS에서 반환된 사용자의 정보를 채운다.

그 후 BookingDialog를 시작하고, 필요할 때 사용자로부터 Origin(출발도시), TravelDate(예약 날짜), Destination(도착도시) 의 추가 정보를 받는다.

여기서 LUIS 포탈을 이용하여 LUIS 앱에 연결할 값 가져오는 부분은 자세히 보지 않겠다.

만약 방법을 알고 싶으면 LUIS 앱에 연결할 값 가져오기를 참고하면 된다.

3. LUIS 앱을 사용하도록 봇 구성

먼저 프로젝트에 Microsoft.Bot.BUilder.AI.Luis NuGet 패키지가 설치되어 있는지 확인한다.

FlightBookingRecognizer 클래스는 appsetting.json 파일의 설정이 들어 있는 코드를 포함하고 있고, RecognizeAsync 메서드를 호출해서 LUIS 서비스를 쿼리한다.

FlightBookingRecognizer.cs

public class FlightBookingRecognizer : IRecognizer
{
    private readonly LuisRecognizer _recognizer;

    public FlightBookingRecognizer(IConfiguration configuration)
    {
        var luisIsConfigured = !string.IsNullOrEmpty(configuration["LuisAppId"]) && !string.IsNullOrEmpty(configuration["LuisAPIKey"]) && !string.IsNullOrEmpty(configuration["LuisAPIHostName"]);
        if (luisIsConfigured)
        {
            var luisApplication = new LuisApplication(
                configuration["LuisAppId"],
                configuration["LuisAPIKey"],
                "https://" + configuration["LuisAPIHostName"]);
            // Set the recognizer options depending on which endpoint version you want to use.
            // More details can be found in https://docs.microsoft.com/en-gb/azure/cognitive-services/luis/luis-migration-api-v3
            var recognizerOptions = new LuisRecognizerOptionsV3(luisApplication)
            {
                PredictionOptions = new Bot.Builder.AI.LuisV3.LuisPredictionOptions
                {
                    IncludeInstanceData = true,
                }
            };

            _recognizer = new LuisRecognizer(recognizerOptions);
        }
    }

    // Returns true if luis is configured in the appsettings.json and initialized.
    public virtual bool IsConfigured => _recognizer != null;

    public virtual async Task<RecognizerResult> RecognizeAsync(ITurnContext turnContext, CancellationToken cancellationToken)
        => await _recognizer.RecognizeAsync(turnContext, cancellationToken);

    public virtual async Task<T> RecognizeAsync<T>(ITurnContext turnContext, CancellationToken cancellationToken)
        where T : IRecognizerConvert, new()
        => await _recognizer.RecognizeAsync<T>(turnContext, cancellationToken);
}

FlightBookingEx.cs는 추출 “위치”, “대상” 및 “TravelDate” 논리를 포함하고 있으며, MainDialog.cs에서 FlightBookingRecognizer.RecognizeAsync을 호출할 때 LUIS 결과를 저장하는데 사용되는 partial 클래스 FlightBooking.cs를 확장한다.

CognitiveModels/FlightBookingEx.cs

// Extends the partial FlightBooking class with methods and properties that simplify accessing entities in the luis results
public partial class FlightBooking
{
    public (string From, string Airport) FromEntities
    {
        get
        {
            var fromValue = Entities?._instance?.From?.FirstOrDefault()?.Text;
            var fromAirportValue = Entities?.From?.FirstOrDefault()?.Airport?.FirstOrDefault()?.FirstOrDefault();
            return (fromValue, fromAirportValue);
        }
    }

    public (string To, string Airport) ToEntities
    {
        get
        {
            var toValue = Entities?._instance?.To?.FirstOrDefault()?.Text;
            var toAirportValue = Entities?.To?.FirstOrDefault()?.Airport?.FirstOrDefault()?.FirstOrDefault();
            return (toValue, toAirportValue);
        }
    }

    // This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the Time part.
    // TIMEX is a format that represents DateTime expressions that include some ambiguity. e.g. missing a Year.
    public string TravelDate
        => Entities.datetime?.FirstOrDefault()?.Expressions.FirstOrDefault()?.Split('T')[0];
}

이러면 LUIS가 봇에 연결된 것이다. 바로 위에 있는 코드를 이해하기 힘들어서 좀 더 공부해 봐야겠다.