[Microsoft Bot Framework] Dialog를 사용하여 대화의 흐름 만들기

대화에는 맥락이라는 것이 있다.  어떤 주제에 대해서 대화들은 같은 맥락 안에서 대화가 이어진다. Bot 을 만들 때도 같은 맥락 안에서 사용자와 Bot간의 대화가 이어져 나가는 방식으로 구현이 된다. 피자를 주문하는 Bot은 피자 주문이라는 맥락에서 크기, 토핑 종류, 음료 등을 물어보고 답을 할 것이다. 

Microsoft Bot Framework에서는 Bot과 사용자 사이의 대화를 만드는 기본 모델이 Dialog 이다. Dialog를 통해서 반복되는 특정 대화를 모듈화해서 재사용성을 높이고 여러 Dialog를 체인 형식으로 연결시켜서 맥락을 이어가도록 프로그래밍 할 수 있다. 

Dialogs 에 대한 공식 문서는 docs.botframework.com 에 있다.

Bot framework builder에 이미 만들어져 있는 Dialog들이 있다. 이 Dialog들과 내가 만든 Dialog를 연결하면 좋은 구조를 만들 수 있다.

Custom Dialog 만들기

StartAsync라는 메서드가 정의되어 있는 IDialog를 구현하면 된다.

public async Task StartAsync(IDialogContext context)
{
    context.Wait(MessageReceivedAsync);
}

public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
    using (CommentService service = new CommentService(new KetBotContext()))
    {
        var activity = await argument;

        // get state 
        KetBotState state = null;
        context.ConversationData.TryGetValue("KetBotState", out state);

        var cat3 = await service.GetFormsAsync(state.Stage1Selection);

        bool checkflag = false;
        int selected;
        if (int.TryParse(activity.Text, out selected) && selected > 0 && selected <= cat3.Count)
        {
          checkflag = true;
        }

 if (checkflag == true)
 {
     // save Stage1 selection
     state.Stage2Selection = activity.Text;
     // save state
     context.ConversationData.SetValue("KetBotState", state);

     // final answers 
     var answers = await service.GetAnswerAsync(state.Stage0Selection + state.Stage1Selection + state.Stage2Selection);
     await context.PostAsync(string.Join("\n", answers.ToArray()));

     var q = await service.GetCommentAsync("RCB02");
     List<string> yesno = new List<string>() { "네, 맞아요!", "아닌데요?" };

     PromptDialog.Choice(context, AfterChoiceAsync, yesno, q, promptStyle: PromptStyle.Keyboard);
 }
 else
 {
     // Go back to stage 2
     await context.PostAsync(await service.GetCommentAsync("REB01"));
     await context.PostAsync(string.Join("\n", cat3.ToArray()));
     context.Wait(MessageReceivedAsync);
  }
 }
}

Dialog 안에서는 PINKET Bot의 Stage3Dialog.cs 코드처럼

  • context.PostAsync()로 사용자에게 메시지를 즉시 전송하거나
  • context.Wait(MessageReceivedAsync)로 사용자로 부터 메시지를 받을 때 까지 기다리거나
  • PromptDialog를 통해서 형식화된 메시지를 주고 받거나
  • context.Done(“some text)를 호출하면 체인으로 연결된 다음 Dialog가 Active 된다. 

Dialog Chain

Dialog들을 체인으로 연결시켜서 또 다른 Dialog를 만들 수 있다. 내부적으로 DialogStack에 순서를 관리하며 연결을 위한 다양한 Chain Method를 Fluent API로 지원한다.

Dialog Chain 샘플코드

EchoChainBot의 코드를 살펴보면

public static readonly IDialog<string> dialog = Chain.PostToChain()
 .Select(msg => msg.Text)
 .Switch(
 new Case<string, IDialog<string>>(text =>
 {
     var regex = new Regex("^reset");
     return regex.Match(text).Success;
 }, (context, txt) =>
 {
     return Chain.From(() => new PromptDialog.PromptConfirm("Are you sure you want to reset the count?",
     "Didn't get that!", 3, PromptStyle.Keyboard)).ContinueWith<bool, string>(async (ctx, res) =>
 {
 string reply;
 if (await res)
 {
     ctx.UserData.SetValue("count", 0);
     reply = "Reset count.";
 }
 else
 {
     reply = "Did not reset count.";
 }
 return Chain.Return(reply);
 });
 }),
     new RegexCase<IDialog<string>>(new Regex("^help", RegexOptions.IgnoreCase), (context, txt) =>
     {
   return Chain.Return("I am a simple echo dialog with a counter! Reset my counter by typing \"reset\"!");
 }),
 new DefaultCase<string, IDialog<string>>((context, txt) =>
 {
 int count;
 context.UserData.TryGetValue("count", out count);
 context.UserData.SetValue("count", ++count);
 string reply = string.Format("{0}: You said {1}", count, txt);
 return Chain.Return(reply);
 }))
 .Unwrap()
 .PostToUser();
  • Chain.PostToChain() 로 사용자의 메시지로 부터 Chain이 시작되고
  • Select(msg => msg.Text) DialogContext에서 Message 부분만 Select 하고
  • .Switch(new Case<string, IDialog<string>> …) 에서 메시지가 Match 되면 Callback 메서드가 실행된다.
  • Switch 에서  RegexCase<IDialog<string>>() 정규표현식을 사용할 수도 있고
  • 매칭이 없으면 DefaultCase가 실행된다.
  • 마지막으로 Unwarp()과 PostToUser()로 체인을 끝낸다.

이런식으로 Dialog를 연결해서 한 묶음의 대화를 완성한 예제가 PINKET Bot 이다. Dialog를 체인으로 엮어서 대화를 만드는 부분을 참조해 볼만하다.