이번 포스트는 Bot Framework와 LINE 메신저를 Direct Line을 이용해서 연결하는 방법에 대한 내용입니다.


1. 환경 및 준비

2. 참고자료

3. Line Message API

4. 시스템 구성도

5. What you can do with the Messaging API

6. Bot Framework와 LINE Message API 연결 준비

7. 채널 설정 페이지

8. Azure Storage account 생성

9. Connection string

10. Azure App Service

11. 로컬 WebApp 서비스 디버깅

12. 이후 작업

13. 봇 테스트

14. 봇 수정

15. 최종 결과 화면


동영상

https://youtu.be/VX62Mt-sJ1A



라인 봇 채널 추가 QR Code


PDF


BotToLine.pdf



블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/


이번 포스트 주제는 Direct Line API를 이용해서 나만의 챗봇 클라이언트를 만드는 방법에 대한 내용입니다.

그리고, 일반 택스트 문자열이 아닌 Hero Card와 이미지를 어떻게 만들어서 사용하는지도 살펴 볼 수 있습니다.


1. 내용

. 환경 및 준비
. 참고 자료
. Direct Line API 3.0
. Direct Line API를 사용하기 위한 준비
. Bot key point
. Client key point
. Bot 개발
. 소스


2. 동영상

유뷰브 채널로 이동하시면 지금까지 올린 다른 강의도 보실 수 있습니다. 구독 신청 부탁드립니다.


Direct Line 동영상



3. PPT


directLine.pptx


블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/


Microsoft Bot Framework에 대해서 알아보는 첫번째 동영상으로 Hello World입니다.


2018년에는 챗봇 만들기에 도전하세요~



동영상에 포함되어 있는 내용 요약


1. 참고 자료 링크

2. Bot을 만드는 두가지 방법

3. Bot Framework Emulator

4. .Net Bot 만들기

5. 실습


유뷰브 채널로 이동하시면 감상 하실 수 있습니다.

구독 신청도 부탁드립니다.

동영상 링크


PPT


botframework.pptx


블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

대화한 내용을 찾아보기 위해서 Log나 History 기능을 검색했고, 아래와 같은 결론을 내렸습니다. 참고하세용


음..이미지는 Log 이미지입니다. 흐흐흐;;;




1. How can i get all the chat history of bot and the user ?

https://github.com/Microsoft/BotBuilder/issues/2073#issuecomment-273982401


이슈 트래킹에 따르면, 대화 내용은 private하기 때문에 대부분의 봇들이 히스토리 기능을 기본 제공을 하지 않는다고 합니다. 히스토리가 필요하면 직접 구현해야한다고..



2. 간단하게 구현하는 방법은 내부에 IList<string> 이나 IDictionary<string,string>을 이용해서 간단하게 저장했다가 사용하는 방법이 있습니다.


    public sealed class WillActivityLogger : IActivityLogger
    {
        async Task IActivityLogger.LogAsync(IActivity activity)
        {
            //log here
            var message = activity.AsMessageActivity();
            var id = string.IsNullOrEmpty(message.Id) ? "BotToUser" : message.Id;
            Debug.WriteLine($"Id : {id}, Text : {message.Text}");
        }
    }


    [BotAuthentication]
    public class MessagesController : ApiController
    {
        static MessagesController()
        {
            var builder = new ContainerBuilder();
            builder.RegisterType<WillActivityLogger>().AsImplementedInterfaces().InstancePerDependency();
            builder.Update(Conversation.Container);
        }

        ...

    }


기본 MessageController를 약간 수정한 것 입니다. 로그를 남기는 sealed class를 하나 생성하고, 기능을 구현한 후, MessagesController가 생성되는 시점에 로그를 남길 수 있도록 쑤셔 넣는 것입니다.



Id : 63ddlg3dk2cnmk847c, Text : 좋아하는 음식은 뭐에요?
Id : BotToUser, Text : 할로~ 카키봇입니다.


위와 같은 로그를 Output 창에서 볼 수 있습니다.



3. 이슈 트래킹 아래로 계속 내려가면 Azure table storage에 로그를 남기는 방법에 대해서도 간단하게 나옵니다. 봇 프레임웍이 내부적으로 남기는 로그는 1mb이내이기 때문에 그 이상의 데이터를 남겨야 하는 경우에 사용하기를 바란다는 내용이 있네요..



4. 이슈 트래킹 더~ 하단에는  LogPostToBot and LogBotUser. 것을 이용하세요라고 안내를 하기도 합니다. 아마 이 부분에 대한 요구가 많았던 것 같습니다. 이렇게 만들어 줄거면;;; 그냥 첨부터 기능 넣어주지..



그래서 최종적으로 로그를 남기고 싶다면 별도록 구현해야하며, 1mb이상은 별도 서비스를 이용하여야 한다.. 정도가 될 것 같습니다.



추가로 아래 내용도 있습니다. 뭐 비슷한 내용입니다만..

http://stackoverflow.com/questions/41309532/how-to-log-a-chat-conversation-with-bot-framework-c-sharp-bot-builder

블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

봇 프레임웍에 대한 포스트를 다시 시작합니다.

이미 기본적인 내용은 몇번 포스트를 했기 때문에, 그동안 업데이트 된 내용만 정리를 해보겠습니다.



1. 참고

Build your own Bot – Get Started with these Resources!



2. 봇 템플릿 다운로드

http://aka.ms/bf-bc-vstemplate


다운로드 받은 zip 파일을  "%USERPROFILE%\Documents\Visual Studio 2015\Templates\ProjectTemplates\Visual C#\"에 복사합니다. Visual Studio 2017을 사용하는 경우에는 2017 폴더 내부에 복사해 넣으면 됩니다.



3. 에뮬레이터 다운로드

https://emulator.botframework.com/

botframework-emulator-Setup-3.5.25.exe 이 최신 버전입니다.


아래 이미지 처럼 오른쪽이 검은색으로 표시됩니다.




4. LUIS에서 한글 서비스 추가하기

http://www.luis.ai

LUIS를 사용하기 위해서는 Microsoft Account가 필요하며, 사이트가 접근 할 수 있도록 허용하시면 가입 됩니다.


My apps에 가시면 New App 버튼이 있습니다.



한글을 선택할 수 있습니다. 다른 내용을 입력하고 Create 버튼을 클릭합니다.



서비스가 생성되면 Dashboard 화면으로 이동합니다. Azure 포털을 보는 듯한 느낌이네요

Create an Intent 버튼을 클릭해서 Intent를 추가해 보겠습니다.



기본으로 None이라는 Intent가 1개 추가되어있습니다. 여기에 hello.bot를 추가합니다.

그리고, 다음과 같은 문장들을 입력 완료 후 save를 눌러서 저장합니다.



위와 같은 방식으로 몇개의 intent를 추가합니다.

여기서는 call.bot, hello.bot, question.bot, question.botframework를 추가했습니다.




Train & Test를 클릭하고 Train Application 버튼을 클릭해서 연습을 시킵니다.


문장을 입력하면서 어떤 결과가 반환되는지 확인을 합니다.







간단하게 한글용 서비스를 추가하고 테스트를 진행했습니다. 이 방법은 LUIS를 사용하는 아주 기초 중에 기초이며, 포스트를 진행하면서 더 세부적인 기능들에 대해서 소개를 하도록 하겠습니다.



5. 챗봇 개발에 관심이있는 분들은...

https://www.facebook.com/groups/botkorea/

에 가입하시면 지속적으로 정보를 공유하도록 하겠습니다.



블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

30도가 넘는 불타는 금요일 저녁 봇과의 전쟁을 시작하려고 합니다. 이전 V1 버전 포스팅을 했을 때 다루지 않았던 LUIS를 좀더 심층적으로 다루어 보려고 합니다. 비록 아직 한글지원은 요원한 일이지만, 어떻게든 짧은 영어로 비벼 보겠습니다.

 

 

0. 관련 페이지

https://docs.botframework.com/en-us/csharp/builder/sdkreference/dialogs.html#Overview

 

다이얼로그 관련 페이지로, 중간에 핵심 내용 중 하나는 LUIS를 이용해서 자언어를 이해하고 그에 맞는 결과를 반화는 것입니다.

 

Language Understanding Intelligent Service(LUIS)

https://docs.botframework.com/en-us/node/builder/guides/understanding-natural-language/

 

이 곳에 내용이 오늘의 핵심 내용입니다.

 

 

1. LUIS

 

마이크로소프트의 LUIS는 어플리케이션이 빠르고 효과적으로 언어를 이해할 수 있도록 지원하는 서비스 입니다. LUIS를 이용하기 위해서는 모델이 필요한데, 이미 Bind이나 Cortana에서 만들어 놓은 모델 중 당신의 목적에 맞는 모델을 사용해서 빠르게 만들 수 있습니다.

 

LUIS는 대화형 개념 학습(PICL) 서비스이며, 마이크로소프트 리서치의 플랫폼을 기반으로 하며, 마이크로소프트 프로젝트 옥스포드의 일부분이기도 합니다.

 

Bot Builder를 사용하면 LuisDialog 클래스를 통해 서비스에 자동으로 접근 할 수 있으며, 자연 언어 이해를 통한 처리를 bot에 추가할 수 있습니다. 당신은 공개 언어 모델을 참조하는 LuisDialog의 인스턴스를 추가하고, 사용자의 대화에 응답하는 처리기를 만들 수 있습니다.

 

LUIS Tutorial & Pre-built application v2

https://www.luis.ai/Help/Index#PreBuiltApp

 

비디오와 설명 자료와 이미 만들어져있는 LUIS 모델을 사용할 수 있도록 해주는 참고 문서 입니다. 이 참고 문서를 이용해서 Alarm Bot을 만들 수 있습니다.

 

 

2. Intents, Entities, and Model Training (의지, 실재, 모델 학습)

 

인간과 컴퓨터간 상호 작용에서 중요한 문제 중 하나는 사용자가 원하는 것을 이해하고, 사용자의 의도에 맞는 정보를 찾을 수 있느냐 하는 것입니다. LUIS는 응용 프로그램과 관련된 객체의 집합을 쉽고 빠르게 디자인을 할 수 있어서 원하는 정보를 찾고, 사용하는데 용이 합니다.

 

또한, 당신의 어플리케이션이 배포되어 데이터가 발생하여 시스템에 흐르게 되면, LUIS를 이용해서 학습을 진행하여 더 좋은 결과를 만들 수도 있습니다. 학습 진행 과정에서 LUIS는 비교적 확실한 상호 작용을 식별하여 intent와 entities를 구분하도록 당신에게 요청을 할 수 있습니다. LUIS는 불안정한 요소를 파악해서 당신에게 시스템의 성능을 향상 시킬 수 있는 방법을 제공하고 도움을 줄 것입니다. 그리고, 중요한 경우에 대해 초점을 맞춤으로써, LUIS는 가능한 빨리 학습하여 당신의 소중한 시간을 낭비하지 않을 것 입니다.

 

 

3. Create Your Model

 

당신의 bot에 자언 언어 지원을 추가하는 첫번째 단계는 LUIS Model을 생성하는 것 입니다. LUIS에 로그인을 하여 Bot을 위한 새로운 LUIS 응용프로그램을 만들면 됩니다.

 

https://www.luis.ai/

 

사이트에 가입하기 위해서는 Microsoft Account가 필요하며, 현재 LUIS는 beta 버전으로 사용료는 무료입니다.

 

 

로그인을 하고 들어가면 아래와 비슷한 화면이 나옵니다. 저는 이전에 MSHealthBot이라는 어플리케이션을 만들어 놓은 것이 있어서 해당 내용이 화면에 표시 됩니다.

 

 

처음 사용하기 위해서는 응용프로그램을 만든 후 LUIS가 당신의 bot 모델을 훈련하는데 사용할 Intents와 Entities를 추가 한 후에 문장을 입력해서 학습을 시킬 수 있습니다. 하지만, 시간이 오래 걸릴 수 있겠죠? 그래서 미리 만들어진  모델을 사용하는 하나의 추가 적인 옵션이 있습니다. 이는 튜토리얼을 위해 만들어진 것으로 Cortana pre-built apps를 클릭하면 5개의 언어에 대한 지원을 선택하 실 수 있습니다. 영어, 중국어, 프랑스어, 스페인어, 이탈리어 입니다.

 

화면에 출력되는 팝업에 표시된 URL에 포함된 id와 subscription-key를 복사해서 LuisDialog class에 사용하실 수 있습니다. 또한 URL은 당신이 만든 LUIS bot app을 가리 키는 것입니다. 그래서, 당신은 이 기본 LUIS 모델을 훈련시켜서 원하는 방향으로 성장시켜 사용할 수 있습니다.

 

이번에는 기존에 제공되는 모델을 이용해서 만들어 본 후 좀더 익숙해지면 진짜 LUIS 모델을 만들어서 이용해 보도록 하겠습니다.

 

 

 

 

URL을 복사해서 보관한 후 Alarm Bot 만들기로 이동합니다.(Dialog의 중간..)

 

 

4. Alarm Bot

 

프로젝트 탐색기에서 클래스를 추가하고 이름을 SimpleAlarmDialog라고 입력합니다. 아래 내용을 입력합니다.

이렇게 만들어진 클래스는 자체적으로 LUIS 서비스와 통신을 수행 합니다.

 

    [LuisModel("c413b2ef-382c-45bd-8ff0-f76d60e2a821", "6d0966209c6e4f6b835ce34492f3e6d9")]
    [Serializable]
    public class SimpleAlarmDialog : LuisDialog<object>
    {
    }

 

이제 내부에 메소드를 하나씩 만들어 나갑니다. 사용자가 입력한 내용이 LUIS의 intent로 구분이 가능하다면, LuisIntent라는 속성으로 연결된 메소드가 실행이 됩니다.

 

        [LuisIntent("builtin.intent.alarm.turn_off_alarm")]
        public async Task TurnOffAlarm(IDialogContext context, LuisResult result)
        {
            if (TryFindAlarm(result, out this.turnOff))
            {
                PromptDialog.Confirm(context, AfterConfirming_TurnOffAlarm, "Are you sure?",
                    promptStyle: PromptStyle.None);
            }
            else
            {
                await context.PostAsync("did not find alarm");
                context.Wait(MessageReceived);
            }
        }

 

위의 예를 보면 사용자가 "turn off my 7am alarm", "turn off my wake up alarm"이라고 입력을 하게 되면, 위의 메소드가 실행되는 구조 입니다. TryFindAlarm은 내부 메소드를 호출하는 곳으로 삭제할 알람을 찾는 부분이

 

고 찾게되면 정말 삭제할 것인지를 물어보는 sub-dialog를 이용해서 사용자 확인을 받고, 확인이 되면 AfterConfirming_TurnOffAlarm이라는 이름의 내부 메소드를 호출해서 최종적으로 처리를 하게 됩니다.

 

전체 소스를 확인합니다.

 

    [LuisModel("c413b2ef-382c-45bd-8ff0-f76d60e2a821", "6d0966209c6e4f6b835ce34492f3e6d9")]
    [Serializable]
    public class SimpleAlarmDialog : LuisDialog<object>

    {
        /// <summary>
        ///     기본 알람 이름
        /// </summary>
        public const string DefaultAlarmWhat = "default";

        /// <summary>
        ///     entity로 알람 제목
        /// </summary>
        public const string Entity_Alarm_Title = "builtin.alarm.title";

        /// <summary>
        ///     entity 시작 시간
        /// </summary>
        public const string Entity_Alarm_Start_Time = "builtin.alarm.start_time";

        /// <summary>
        ///     entity 시작 일자
        /// </summary>
        public const string Entity_Alarm_Start_Date = "builtin.alarm.start_date";

        /// <summary>
        ///     저장된 알람 정보
        /// </summary>
        private readonly Dictionary<string, Alarm> alarmByWhat = new Dictionary<string, Alarm>();

        /// <summary>
        ///     알람끄기?
        /// </summary>
        private Alarm turnOff;

        /// <summary>
        ///     기본 생성자
        /// </summary>
        public SimpleAlarmDialog()
        {
        }

        /// <summary>
        ///     루이스 서비스 생성자
        /// </summary>
        /// <param name="service"></param>
        public SimpleAlarmDialog(ILuisService service)
            : base(service)
        {
        }

        /// <summary>
        ///     알람 찾기
        /// </summary>
        /// <param name="result"></param>
        /// <param name="alarm"></param>
        /// <returns></returns>
        public bool TryFindAlarm(LuisResult result, out Alarm alarm)
        {
            //엔티티
            EntityRecommendation title;
            //루이스 결과 중 알람 제목을 찾아보고, 있으면, 그녀석의 값(entity)를 없으면 기본 알람 이름을 what에 입력
            var what = result.TryFindEntity(Entity_Alarm_Title, out title) ? title.Entity : DefaultAlarmWhat;
            //저장되어있는 알람 정보 중에 해당 알람을 반환
            return alarmByWhat.TryGetValue(what, out alarm);
        }

        /// <summary>
        ///     아무것도 찾지 못한 경우
        /// </summary>
        /// <param name="context"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        [LuisIntent("")]
        public async Task None(IDialogContext context, LuisResult result)
        {
            var message = "Sorry I did not understand: " + string.Join(", ", result.Intents.Select(i => i.Intent));
            await context.PostAsync(message);
            context.Wait(MessageReceived);
        }

        /// <summary>
        ///     알람 삭제 delete an alarm, delete my alarm "wake up"
        /// </summary>
        /// <param name="context"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        [LuisIntent("builtin.intent.alarm.delete_alarm")]
        public async Task DeleteAlarm(IDialogContext context, LuisResult result)
        {
            Alarm alarm;

            if (TryFindAlarm(result, out alarm))
            {
                alarmByWhat.Remove(alarm.What);
                await context.PostAsync($"alarm {alarm} deleted");
            }
            else
            {
                await context.PostAsync("did not find alarm");
            }
            context.Wait(MessageReceived);
        }

        /// <summary>
        ///     알람 검색 what time is my wake-up alarm set for?, is my wake-up alarm on?
        /// </summary>
        /// <param name="context"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        [LuisIntent("builtin.intent.alarm.find_alarm")]
        public async Task FindAlarm(IDialogContext context, LuisResult result)
        {
            Alarm alarm;

            if (TryFindAlarm(result, out alarm))
            {
                await context.PostAsync($"found alarm {alarm}");
            }
            else
            {
                await context.PostAsync("did not find alarm");
            }
            context.Wait(MessageReceived);
        }

        /// <summary>
        ///     알람 설정 turn on my wake up alarm, can you set an alarm for 12 called take antibiotics?
        /// </summary>
        /// <param name="context"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        [LuisIntent("builtin.intent.alarm.set_alarm")]
        public async Task SetAlarm(IDialogContext context, LuisResult result)
        {
            EntityRecommendation title;

            //제목 찾기
            if (!result.TryFindEntity(Entity_Alarm_Title, out title))
            {
                title = new EntityRecommendation(type: Entity_Alarm_Title) {Entity = DefaultAlarmWhat};
            }
            //날짜 찾기
            EntityRecommendation date;
            if (!result.TryFindEntity(Entity_Alarm_Start_Date, out date))
            {
                date = new EntityRecommendation(type: Entity_Alarm_Start_Date) {Entity = string.Empty};
            }
            //시간 찾기
            EntityRecommendation time;
            if (!result.TryFindEntity(Entity_Alarm_Start_Time, out time))
            {
                time = new EntityRecommendation(type: Entity_Alarm_Start_Time) {Entity = string.Empty};
            }
            //파서를 이용해서 날짜와 시간을 결합
            var parser = new Parser();
            var span = parser.Parse(date.Entity + " " + time.Entity);
            //날짜와 시간 값이 있다면
            if (span != null)
            {
                //시작과 종료 확인?
                var when = span.Start ?? span.End;
                //알람 생성
                var alarm = new Alarm {What = title.Entity, When = when.Value};
                //알람 추가
                alarmByWhat[alarm.What] = alarm;

                string reply = $"alarm {alarm} created";
                await context.PostAsync(reply);
            }
            else
            {
                await context.PostAsync("could not find time for alarm");
            }

            context.Wait(MessageReceived);
        }

        /// <summary>
        ///     알람 일시 정지 snooze alarm for 5 minutes, snooze alarm
        /// </summary>
        /// <param name="context"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        [LuisIntent("builtin.intent.alarm.snooze")]
        public async Task AlarmSnooze(IDialogContext context, LuisResult result)
        {
            Alarm alarm;
            if (TryFindAlarm(result, out alarm))
            {
                //todo : 시간이 입력되었다면 해당 시간 값을 찾아서 그걸 사용하도록 수정
                //현재는 7분 후에 다시 알람이 발생하도록 되어있음
                alarm.When = alarm.When.Add(TimeSpan.FromMinutes(7));
                await context.PostAsync($"alarm {alarm} snoozed!");
            }
            else
            {
                await context.PostAsync("did not find alarm");
            }
            context.Wait(MessageReceived);
        }

        /// <summary>
        ///     알람 시간 확인 how much longer do i have until "wake-up"?, how much time until my next alarm?
        /// </summary>
        /// <param name="context"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        [LuisIntent("builtin.intent.alarm.time_remaining")]
        public async Task TimeRemaining(IDialogContext context, LuisResult result)
        {
            Alarm alarm;
            if (TryFindAlarm(result, out alarm))
            {
                var now = DateTime.UtcNow;
                //알람의 시간과 현재 시간 비교
                if (alarm.When > now)
                {
                    //알람 시간에서 현재시간을 빼고 남은 시간을 알림
                    var remaining = alarm.When.Subtract(DateTime.UtcNow);
                    await context.PostAsync($"There is {remaining} remaining for alarm {alarm}.");
                }
                else
                {
                    await context.PostAsync($"The alarm {alarm} expired already.");
                }
            }
            else
            {
                await context.PostAsync("did not find alarm");
            }

            context.Wait(MessageReceived);
        }

        /// <summary>
        ///     알람 끄기 turn off my 7am alarm, turn off my wake up alarm
        /// </summary>
        /// <param name="context"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        [LuisIntent("builtin.intent.alarm.turn_off_alarm")]
        public async Task TurnOffAlarm(IDialogContext context, LuisResult result)
        {
            if (TryFindAlarm(result, out turnOff))
            {
                PromptDialog.Confirm(context, AfterConfirming_TurnOffAlarm, $"Are you sure? {turnOff} right?",
                    promptStyle: PromptStyle.None);
            }
            else
            {
                await context.PostAsync("did not find alarm");
                context.Wait(MessageReceived);
            }
        }

        /// <summary>
        ///     알람 끄기 - 처리
        /// </summary>
        /// <param name="context"></param>
        /// <param name="confirmation"></param>
        /// <returns></returns>
        public async Task AfterConfirming_TurnOffAlarm(IDialogContext context, IAwaitable<bool> confirmation)
        {
            if (await confirmation)
            {
                alarmByWhat.Remove(turnOff.What);
                await context.PostAsync($"Ok, alarm {turnOff} disabled.");
            }
            else
            {
                await context.PostAsync("Ok! We haven't modified your alarms!");
            }
            context.Wait(MessageReceived);
        }

        /// <summary>
        ///     알람 이외.. update my 7:30 alarm to be eight o'clock, change my alarm from 8am to 9am
        /// </summary>
        /// <param name="context"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        [LuisIntent("builtin.intent.alarm.alarm_other")]
        public async Task AlarmOther(IDialogContext context, LuisResult result)
        {
            //todo : 알람 시간을 변경하는 로직을 구현
            await context.PostAsync("what ?");
            context.Wait(MessageReceived);
        }

        /// <summary>
        ///     알람 클래스
        /// </summary>
        [Serializable]
        public sealed class Alarm : IEquatable<Alarm>
        {
            /// <summary>
            ///     시간
            /// </summary>
            public DateTime When { get; set; }

            /// <summary>
            ///     뭐?
            /// </summary>
            public string What { get; set; }

            /// <summary>
            ///     다른 알람 인스턴스와 비교
            /// </summary>
            /// <param name="other"></param>
            /// <returns></returns>
            public bool Equals(Alarm other)
            {
                return other != null
                       && When == other.When
                       && What == other.What;
            }

            /// <summary>
            ///     문자열 변환
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return $"[{What} at {When}]";
            }

            /// <summary>
            ///     다른 오브젝트와 비교
            /// </summary>
            /// <param name="other"></param>
            /// <returns></returns>
            public override bool Equals(object other)
            {
                return Equals(other as Alarm);
            }

            /// <summary>
            ///     해쉬코드 반환
            /// </summary>
            /// <returns></returns>
            public override int GetHashCode()
            {
                return What.GetHashCode();
            }
        }
    }

 

 

MessageController.cs 파일도 수정합니다.

 

        [ResponseType(typeof(void))]
        public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
        {
            if (activity != null)
            {
                // one of these will have an interface and process it
                switch (activity.GetActivityType())
                {
                    case ActivityTypes.Message:
                        await Conversation.SendAsync(activity, () => new SimpleAlarmDialog());
                        break;

                    case ActivityTypes.ConversationUpdate:
                    case ActivityTypes.ContactRelationUpdate:
                    case ActivityTypes.Typing:
                    case ActivityTypes.DeleteUserData:
                    default:
                        Trace.TraceError($"Unknown activity type ignored: {activity.GetActivityType()}");
                        break;
                }
            }
            return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
        }

 

 

그리고 실행해서 하나씩 살펴 보도록 하겠습니다.

 

 

5. 실행

 

 

set alarlm을 입력했더니, 무슨 소리인지 모르겠다고 하는군요.. ㅋ 그리고 set alarm이라고 입력했더니 시간을 찾을 수 없어서 등록할 수 없다고 합니다. 알람을 등록할 때는 반드시 시간을 알려줘야 합니다.

 

1) 알람 설정용 기본 문장

turn on my wake up alarm 6am, -> alarm [wake up at 7/25/2016 6:00:00 AM] created
오전 6시 알람을 wake up이란 이름으로 등록합니다.

 

can you set an alarm for 12 called take antibiotics? -> alarm [take antibiotics at 7/25/2016 12:00:00 PM] created
오후 12시에 약 먹는 시간을 take antibiotics라는 이름의 알람으로 등록합니다.

 

 

2) 등록된 알람 검색

what time is my wake up alarm set for? -> found alarm [wake up at 7/25/2016 6:00:00 AM]
wake up알람의 시간이 어떻게 되니?

 

is my wake up alarm on? -> found alarm [wake up at 7/25/2016 6:00:00 AM]

wake up 알람이 켜져있니?...인데..시간을 반환하고 있군요 흐흐.. 이 부분은 개선이 필요할 듯합니다.

 

 

3) 알람 끄기

 

turn off my 7am alarm -> did not find alarm

7시 알람 꺼줘 -> 그런 알람 없는데요;; 우리가 등록한 알람은 2개 뿐인데 하나는 오후 12시고 또 다른 하나는 오전 6시 알람이니..찾지 못했겠죠?

 

 

6. 간단하게 몇가지 내용을 입력해서 결과를 살펴 봤습니다.

 

이 심플 알람은 개선할 사항이 아주 많이 있습니다.

1) 알람 시간이 되었을 때 알려주는 기능;;

2) 알람 켜기/끄기

3) 알람 시간 변경

등이 있을 수 있습니다.


그리고, 여기서 다루지 않은 LUIS 모델들이 정말 많이 있으니 하나씩 찾아보면서 자신만의 채팅 봇을 만들어 보면 좋을 것 같습니다.

 

 

 

블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

포켓몬고의 열풍이 대한민국을 휩쓸고 있는 주말입니다. 평소에 전혀 게임에 관심이 없었던 사람들까지도 모두 알수 있을 정도로 가십성 기사는 정말 엄청나게 양산을 하는 것 같습니다. 게임 디자이너 중 한명이 한국 사람이라는 뉴스는 왜 나오는지.. 에혀;; 뭐.. 어찌 되었던 간에 앞으로 저는 앞으로 새로운 비전은 Bot이 이끌어 나갈 것이라는 마이크크로소프트의 이야기를 믿고 계속 준비를 하도록 하겠습니다.(물론 않되는 것도 있지만...) 언젠가 좋은 날이 오겠죠!

 

이번에 다룰 내용은 Activity의 기본적인 구성과 어떻게 메시지를 주고 받는 것인지에 대한 내용입니다. 이전에 다루지 않았던 내용으로 아마 조금더 Bot Framework을 이해하는데 도움이 되지 않을까 생각합니다.

 

 

0. 관련 포스트

Sending and Receiving Activities

https://docs.botframework.com/en-us/csharp/builder/sdkreference/routing.html

 

V3로 변경되면서 달라진 점 중에 하나는 기존 메시지를 처리하는 부분의 명칭이 Activity로 변경되었다는 것입니다. 그만큼 Bot Framework의 핵심 부분이라고 생각할 수 있을 것 같습니다.

 

모든 Activity는 적절한 목적지에 대한 라우닡 정보를 포함하고 있습니다. Bot은 사용자의 Activity를 수신하고, 내용을 처리한 후에 다시 보내게 됩니다.

 

 

1. Routing Activities

 

Activities는 커넥터 서비스가 적절한 수신자에게 전달하고, 메시지 작성자와 통신하도록 지원하는 여러가지 속성들을 가지고 있습니다.

커넥터 서비스 모델 중 Conversation의 구성 요소로 From -> Recipient가 있습니다.

 

1) From : 발송자의 activity

2) Recipient : 수신자의 activity

3) Conversation : 대화 메시지 일부

 

당신이 사용자로부터 Activity를 받으면, From 필드와 Recipient 필드에 필요한 정보가 입력되어 있어서 Bot은 이 정보를 이용해서 처리를 할 수 있습니다.

 

* 그러나 반드시 알 수 있는 것은 아닙니다. 예를 들면 Slack의 경우에는 알 수 없을 수 있기 때문입니다. .. Slack를 사용해보지 않아서 왜 알 수 없는지는..확인해 봐야겠습니다. 더 자세한 사항은 Replying to an Activity라는 문서를 참고하면 됩니다.

 

 

2. ChannelId and ServiceUrl

 

2개의 최상위 레벨 프로퍼티가 있습니다. ChannelId는 bot이 어떤 채널을 통해서 작업해야하는지를 나타내며, ServiceUrl은 API를 어떤 주소로 회신을 보내야할지를 나타내기 때문입니다.

 

1) ChannelId : Activity된 채널, 채널은 일종의 채팅방? 개념이라고 생각하시면 될 것 같습니다.

2) ServiceUrl : 회신을 보낼 때 사용할 url주소 http://skype.botframework.com

 

 

3. Creating Connector Client

 

ServiceUrl은 API 호출을 위한 endpoint를 제공합니다. ConnectorClient 클래스를 생성할 때 반드시 필요합니다.

 

var connector = new ConnectorClient(incomingMessage.ServiceUrl);

 

 

4. ChannelAccounts

 

ChannelAccount 레코드는 사용자나 봇이 통신을 하는 채널을 이야기 합니다.

 

1) Id : 채널 Id, joe.com, +14258828080, etc

2) Name : 사용자나 봇 이름 Jeo Smith

 

각 사용자와 봇은 각 채널별로 1에서 N개의 ChannelAccount를 가지고 있습니다.

 

 

5. ConversationAccount

 

ConversationAccount 객체는 기본적으로 ChannelAccount와 동일한 정보를 가지며, 몇가지 추가적인 메타 정보를 포함합니다.

 

1) Id : 채널내 대화를 위한 고유Id, Xy1xvh3jhv

2) Name : 대화 이름

3) IsGroup : 만약 true이면, 그룹 대화를 나타냅니다.(기본값은 false)

 

 

6. Replying to messages

 

당신의 봇이 메시지 Activity를 받게 되었다면, 회신을 보내야 합니다.

 

이때, 당신은 새로운 Activity가 필요하며, 이 때

 

1) 원본 메시지에 있던 From <-> Recipient 필드가 서로 바뀝니다.

2) Conversation은 원본 메시지에 있던 것을 사용합니다.

3) Text (와 적절한 첨부파일)

 

회신 메시지를 만들기 쉽도록 CreateReply라는 확장 메소드를 제공합니다.

 

// create properly formatted reply message
var replyMessage = incomingMessage.CreateReply("Yo, what's up?");

 

 

 

7. ReplyToActivity()

 

간단한 메시지를 회신하기 위해서는 ReplyToActivity() 메소드를 호출합니다. Connector 서비스가 메시지 전달을 위한 채널 정보와 필요한 사항을 적절하게 처리해 줍니다.

 

var connector = new ConnectorClient(incomingMessage.ServiceUrl);
var replyMessage = incomingMessage.CreateReply("Yo, I heard you.", "en");
await connector.Conversations.ReplyToActivityAsync(replyMessage);

 

 

8. SendToConversation()

 

SendToConversation() 메소드는 스레드의 종류를 유지하지 않는 점을 제외하고 ReplyToActivity와 거의 동일하며, 이 것은 회신으로 메시지를 전달할 수 없는 경우에 사용합니다.

 

var connector = new ConnectorClient(incomingMessage.ServiceUrl);
IMessageActivity newMessage = Activity.CreateMessageActivity();
newMessage.Type = ActivityTypes.Message;
newMessage.From = botAccount;
newMessage.Conversation = conversation;
newMessage.Recipient = userAccount;
newMessage.Text = "Yo yo yo!";
await connector.Conversations.SendToConversation((Activity)newMessage);

 

 

9. Multiple replies

...

 

 

10. Starting Conversations

 

대화를 초기화 하기 위해서는 CreateConversation() 혹은 CreateDirectConversation() 메소드를 호출해서 채널에ConversationAccount를 만들 수 있습니다. SendToConversation() 메소드를 호출해서 한번은 메시지를 전송할 수 있습니다.

 

 

11. Create 1:1 Conversations

 

CreateDirectConversation() 메소드는 1:1 대화를 만들 수 있습니다.

 

var connector = new ConnectorClient(incomingMessage.ServiceUrl);
var ConversationId = await connector.Conversations.CreateDirectConversationAsync(incomingMessage.Recipient, incomingMessage.From);
IMessageActivity message = Activity.CreateMessageActivity();
message.From = botChannelAccount;
message.Recipient = new ChannelAccount() { name: "Larry", "id":"@UV357341"};
message.Conversation = new ConversationAccount(id: ConversationId.Id);
message.Text = "Hello";
message.Locale = "en-Us";
var reply = await connector.Conversations.ReplyToActivityAsync(message);

 

 

 

12. Create Group Conversations

 

CreateConversation()메소드를 이용해서 새로운 그룹 대화를 만들 수 있습니다.

 

* 현재 Email은 대화는 그룹 대화만 지원합니다.

 

var connector = new ConnectorClient();
List<ChannelAccount> participants = new List<ChannelAccount>();
participants.Add(new ChannelAccount("joe@contoso.com", "Joe the Engineer"));
participants.Add(new ChannelAccount("sara@contso.com", "Sara in Finance"));
ConversationParameters cpMessage = new ConversationParameters(message.Recipient, participants, "Quarter End Discussion");
var ConversationId = connector.Conversations.CreateConversationAsync(cpMessage);
IMessageActivity message = Activity.CreateMessageActivity();
message.From = botChannelAccount;
message.Recipient = new ChannelAccount("lydia@contoso.com", "Lydia the CFO"));
message.Conversation = ConversationId;
message.ChannelId = "email";
message.Text = "Hey, what's up everyone?";
message.Locale = "en-Us";
var reply = await connector.Conversations.ReplyToActivityAsync((Activity)message);

 

 

 

 

13. 심화 학습

 

설명만 들어서는 감이 오지 않습니다~ 그래서, 실제 Echo bot 소스를 이용해서 어떤 값들이 어떻게 들어오는지 확인 하도록 하겠습니다.

 

Echo bot 셈플을 디버그 모드로 실행한 후 에뮬레이터를 이용해서 문자열을 전송합니다. 이 때 예전과 다른 점은 MicrosoftAppId와 MicrosoftAppPassword가 web.config에 입력이 되어 있는 상태이기 때문에 에뮬레이터에서도 입력을 해주어야 테스트가 가능합니다.

 

 

activity에 어떤 내용이 있을까요?

우선 Conversation 필드를 보겠습니다. 타입은 ConversationAccount군요, 그리고 Id, IsGroup, Name이라는 프로퍼티를 포함하고 있습니다.

 

Id가 채널내 대화를 위한 고유 Id라고 했고, IsGroup이 false이니 그룹 채팅은 아니네요, 대화방명?은 Conv1입니다.

 

 

다음은 From에 대해서 살펴 보겠습니다. From의 타입은 ChannelAccount 이군요, Id가 2c1c7fa3이고, 이름이 User1 이라는 사용자가 보낸 메시지 입니다. 이 내용은 에뮬레이터 상단에 입력되어 있는 내용이네요

 

 

이번에는 Recipient 수신자에 대한 정보를 보겠습니다. 타입은 ChannelAccount이고, Id가 56800324, 이름이 Bot1 으로 보낸 메시지인 것을 알 수 있습니다.

 

 

또 관심있는 것들이 ChannelId입니다.

ChannelId는 emulator라는 내용을 가지고 있습니다.

 

SurviceUrl은 뭔지 볼까요? 이녀석은 http://localhost:9000을 가지고 있습니다. 이 정보도 에뮬레이터에 존재하는 내용입니다.

 

음음..앞으로 이 Activity를 통해서 사용자가 입력한 내용들을 확인하고 원하는 것을 찾아서 결과를 반환하는 일만 남은 것 같습니다.

 

 

14. 정리

 

지금까지 알아본 봇 만들기의 기본에 대해서 정리를 합니다.

 

1) 봇을 만듭니다. 에뮬레이터로 테스트도 하면서..열심히 만들고

2) 만들어진 봇을 Microsoft Azure 서버에 App Service로 등록 합니다. 등록할 때 사용된 서버 주소를 메모해 놓습니다.

3) Bot Framework portal에 내가 만든 봇을 등록합니다. 등록할 때 생성되는 MicrosoftAppId와 MicrosoftAppPassword를 메모해 놓습니다.

 

4) MicrosoftAppId와 MicrosoftAppPassword를 내 봇 소스의 web.config 파일에 입력하고, 다시 한번 Azure에 Publish 합니다.

5) 이제 Bot Framework portal을 통해서 Facebook, skype, web chat 등 여러 플랫폼들을 이용할 수 있습니다.

 

 

블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

마이크로소프트 MVP 박문찬입니다. 

기온이 30도인 전형적인 열대야 저녁입니다. 샤워하고, 컴퓨터 앞에 앉자마자 땀이 주르륵;;; 결국 에어컨님의 힘을 빌어서 자리에 죽치고 안아서, 2주 후에 오프라인 모임 때 진행할 Bot Framework V3에 대해서 포스트 작업을 하려고 합니다. 오프라인 모임 때는 작성해 놓은 포스트를 중심으로 따라서 진행하도록 할 예정이니, 미리 예습을 하셔도 좋을 것 같습니다. 그럼, 다시 시작하는 마음으로 Getting Started 포스트를 살펴 볼까요~

 

작업일자 : 2016년 7월 15일 (시간이 지나면 뭐가 바뀌어있을지 알 수 없습니다;;; 눈뜨고 코베어가는 서울 보다 무서운 레드몬드;;;;)

 

윈도우 앱 개발 그룹 7월 오프라인 모임 - 주제 Bot Framework V3 함께 만들기

http://onoffmix.com/event/73081

 

 

0. 참고 포스트 - Getting Started

http://docs.botframework.com/en-us/csharp/builder/sdkreference/

 

 

1. Bot Framework 개요

 

마이크로소프트 Bot Builder는 각종 형태의 상호 작용과 가능성을 명시적으로 사용자에게 제공할 수 있는 봇을 만들 수 있는 강력한 프레임웍 입니다. 이 것은 사용하기 쉽고, C#의 장점을 이용해서 자연스러운 방법으로 봇을 만들 수 있게 합니다.

 

 

높은 수준의 특징

 

. 분리되고 조합가능한 강력한 dialog 시스템 구성

. Yes/No, 문자열, 숫자, 열거 형 같은 간단한 것들을 내장한 dialog 만들기

. LUIS와 같은 강력한 AI 프레임워크를 활용하여 dialog 만들기

. Bots are stateless which helps them scale.

. 자동으로 C#클래스를 이용해서 봇을 생성하고, 도움말, 네비게이션, 설명 및 확인 등을 지원하는 FormFlow를 지원

. 공개된 SDK 소스 http://github.com/Microsoft/botbuilder.

 

 

2. Install

 

인스톨을 하는 과정은 Getting started with the Connector 페이지로 이동해서 계속 진행 하겠습니다.

http://docs.botframework.com/en-us/csharp/builder/sdkreference/gettingstarted.html

 

마이크로소프트 Bot Framework Connector는 내가 만든 bot과 다른 커뮤니케이션 채널들(Skype, SMS, email, other)을 서로 연결시켜주는 역할을 합니다.

 

Bot Framework Connector을 사용하기 위해서는

 

1) MSA(Microsoft Account)를 이용해서 Bot Framework developer portal에 로그인을 해서 당신의 봇을 등록할 수 있습니다.

 

2) Azure REST endpoint를 이용해서 Connector service와 연결합니다.

 

3) 개발자가 접근 가능한 다른 커뮤니케이션 서비스의 계정(Facebook, Skype, 이 있어야 합니다. SMS, Email의 경우에도 뭔가 필요하겠죠?

 

 

3. Getting started in .NET

 

1) Visual Studio 2015 Update 3 (가장 최신 버전)이 필요 합니다. 또한, Extension들의 최신 업데이트도 필요합니다.

 

* Microsoft ASP.NET and Web Tools 업데이트 반드시!!


Windows 10 ver 1607 기념일 업데이트 버전은 아래와 같이 cmd에서 수동으로 입력해서 실행하셔야지 설치가 가능합니다.

DotNetCore.1.0.0-VS2015Tools.Preview2.exe SKIP_VSU_CHECK=1

 

 

* 에뮬레이터 최신버전 3.0.0.57 필요~

 

에뮬레이터 V3 다운로드

http://botframework.blob.core.windows.net/bf-v3/tools/emulator/publish.htm

 

 

2) 봇 템플릿 다운로드 : http://aka.ms/bf-bc-vstemplate

. zip파일(압축을 풀지 않고)을 “%USERPROFILE%\Documents\Visual Studio 2015\Templates\ProjectTemplates\Visual C#" 경로에 복사해서 붙여 넣는다.

예) C:\Users\kaki1\Documents\Visual Studio 2015\Templates\ProjectTemplates\Visual C#

 

3) 비주얼 스튜디오 시작

 

4) 봇 어플리케이션 템플릿으로 프로젝트를 생성

아래와 같이 선택할 수 있어야 있으면 다음으로 진행 합니다.

 

 

5) 템플릿을 이용해서 프로젝트를 생성하면 Echo Bot의 모든 기능을 가지고 있는 프로젝트가 생성됩니다.

그리고, 그 다음에 실행해야 할 사항들 입니다.

. bot을 Bot Connector에 등록하기

. AppId와 AppPassword를 Bot Framework 등록 페이지에서 복사해서 web.config에 붙여 넣기

. bot 프로젝트를 azure에 Published하기

 

 

4. Building your Bot

 

본격적으로 봇을 만들어 보겠습니다. 봇 템플릿의 핵심 기능은 Controllers\MessagesController.cs에 있는 Post 함수에 있습니다. 이 코드에서 사용자의 메시지 텍스트를 얻은 후 CreateReplyMessge를 이용해서 응답 메시지를 생성합니다. [BotAuthentication]은 당신의 봇을 Bot Connector가 인증을 해야하는 것을 명시적으로 나타냅니다.

 

    [BotAuthentication]
    public class MessagesController : ApiController
    {
        /// <summary>
        /// POST: api/Messages
        /// Receive a message from a user and reply to it
        /// </summary>
        public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
        {
            if (activity.Type == ActivityTypes.Message)
            {
                ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));

 

                // calculate something for us to return
                int length = (activity.Text ?? string.Empty).Length;

 

                // return our reply to the user
                Activity reply = activity.CreateReply($"You sent {activity.Text} which was {length} characters");
                await connector.Conversations.ReplyToActivityAsync(reply);
            }
            else
            {
                HandleSystemMessage(activity);
            }
            var response = Request.CreateResponse(HttpStatusCode.OK);
            return response;
        }

 


    }

 

위의 코드를 보면 V1과 여러가지 부분이 달라졌다는 것을 알 수 있습니다. Post메소드에 Activity가 생긴 것, ConnectorClient를 생성하는 부분, 회신 메시지를 만드는 CreateReply, 회신 방법 ReplyToActivityAsync 등이 그렇습니다.

느낌으로는 좀더 명확하게 변경되었다고 생각됩니다.

 

 

5. Emulator

 

에뮬레이터는 로컬에서 자신이 만든 봇을 테스트 하기 위한 용도로 사용됩니다. 거기다 이전 버전의 에뮬레이터와는 다르게 채널을 변경하는 기능도 포함되었습니다. 에뮬레이터 다운로드는 위에서 언급했기 때문에 생략 합니다.

 

에뮬레이터를 사용하기 위한 작업을 진행하겠습니다.

 

시작 브라우저를 Microsoft Edge로 지정 합니다. 그 이야기는 IE에서 지원이 않될지도 모르겠네요.. Edge를 사용해야 한다는 것은 Windows 10이 설치된 컴퓨터에서만 개발이 가능하다는..으흐흐...

 

 

프로젝트를 실행하면, default.htm 파일의 내용이 표시됩니다. 음..이쁘게 만들기를 원하시면 직접 꾸미셔도 좋을 것 같습니다.

 

F5키를 눌러서 실행하면 엣지 브라우저에서 아래와 같은 화면이 출력됩니다.

 

 

에뮬레이터를 이용해서 방금 만든 봇을 테스트를 하기 위해서는 port번호를 정확하게 지정해야 합니다. 예를 들어 지금의 화면에서는 3978번이 port 번호입니다.

 

봇 응용프로그램과 에뮬레이터가 상호 작용하기 전에 몇가지 항목을 설정해야 합니다. (하지만.. 기본 설정을 이용해도 잘 됩니다..)

 

1) URL 항목에 Port번호와 뒤에 /api/messages가 추가되어 있어야 합니다.

 

2) MicrosoftAppId는 입력하지 않습니다.

 

3) MicrosoftAppPassword는 입력하지 않습니다.

 

빨간색 테두리 쳐진 곳만 잘 확인하시면 됩니다.

 

 

모든 준비가 끝났다면 당신의 봇에게 인사를 해보도록 하세요. 입력한 글씨가 몇자인지를 반환해 줄 것 입니다.

 

 

 

6. Publishing your Bot Application to Microsoft Azure

 

헬로우 월드와 비슷한 에코 봇을 만들었으니 이제 이 봇을 다른 사람들이 사용할 수 있도록 해야합니다. 다른 사람들이 봇을 사용하기 위해서는 Azure에 Publishing을 해야지만 합니다. 그 과정을 살펴 보도록 하겠습니다.

 

시간과 지면 관계상 Microsoft Azure가 무엇인지에 대한 자세한 사항은 생략 하겠습니다. 만약 Azure에 가입을 하지 않으셨다면,

 

https://azure.microsoft.com/ko-kr/ 

 

사이트를 방문하셔서 무료로 가입을 하시면 됩니다. 또한, 무료 기간이 지나서 요금이 왕창 나오면 어떻게하나 걱정하실 필요가 없습니다. Azure는 사용한 만큼만 요금이 지불되기 때문에 등록했다고해서 요금 폭탄이 떨어지지 않으니 안심하셔도 좋습니다.(가상 머신 서비스 같은 것은 제외;;;)

 

Publish 하실 준비가 되었으면, 솔루션 탐색기에서 오른쪽 마우스 버튼을 클릭해서 컨텍스트 메뉴를 출력하고, Publish 항목을 선택합니다.

 

 

우리가 만든 봇 어플리케이션은 Microsoft Azure App Service로 등록을 해야합니다.

Microsoft Azure App Service를 클릭해서 세부 항목을 입력합니다.

 

 

세부 항목을 지정하기 위한 화면입니다. 오른쪽 상단에 제가 Visual Studio에 입력한 Microsoft account 정보가 출력됩니다. 저 Microsoft account정보와 Azure에 가입한 계정이 동일해야지만 아래 Subscription 콤보 박스에서 선택이 가능합니다.

 

또한, 제 계정 정보 하단에 노란색 Reenter your credentials라는 녀석이 보인다면, 저녀석을 클릭해서 다시 한번 로그인을 해주셔야 콤보 박스에 내용을 지정할 수 있습니다.

 

 

이제 정확한 정보가 표시됩니다. 아마 현장에서 Azure 가입하시고, 바로 진행하실려고 하면 이 부분에서 시간이 오래 지체될 수 있습니다. 미리 가입을 하시는 것이 좋습니다.

 

저는 Default-Web-EastAsia라는 리소스 그룹에 새로운 App Service를 만들겠습니다. New를 클릭합니다.

 

 

대략 내용을 확인 후 Create 버튼을 클릭합니다. Resource Group, App Service Plan 등은 사용자가 임의로 만들어서 사용할 수 있는 것들이기 때문에 크게 의미는 없습니다.

 

 

모든 정보가 정상적으로 등록이 된 후 아래와 같은 최종 Web Deploy 화면이 출력됩니다. 여기서 Destination URL에 출력된 주소를 복사해서 보관하세요!

 

그 외 Publish된 내용을 Debug하셔야 하는 경우에는 Next를 눌러서 Debug로 변경하시면 되고, Validate Connection을 눌러서 잘 연결되었는지 확인도 해주시기 바랍니다.

 

최종적으로 Publish 버튼을 눌러서 배포 합니다.

 

 

 

7. Registering your Bot with the Microsoft Bot Framework

 

자 그럼 내가 만든 봇을 Azure에 배포를 하였으니, 남은 작업은 봇을 Bot Framework portal에 등록하는 작업을 계속 진행 하겠습니다.

 

이 작업은 Bot Connector에서 Azure에 등록된 봇의 Endpoint를 이용해서 서로 데이터를 주고 받을 수 있도록 하는 작업이며, 이 과정을 진행하는 동안 MicrosoftAppIdMicrosoftAppPassword를 만들 수 있습니다.

 

1) Microsoft Bot Framework portal로 이동합니다. https://dev.botframework.com 그리고, Microsoft Account로 가입하거나 로그인 합니다.

 

2. 상단 메뉴 중 Register a bot을 클릭해서 Bot을 등록합니다.

Name, Bot handle, Description 등을 적당히 입력하시고, 중요한 부분은 Configuration에 Messaging endpoint 부분에 아까 복사해 놓았던 Destination URL과 /api/messages을 입력하시면 됩니다.

 

https://kakisamplebotv320160714111119.azurewebsites.net/api/messages

 

https와 /api/messages 두가지를 주의 하세요!!

 

그리고, Create Microsoft App ID and password를 클릭하셔서 계속 진행 합니다.

 

 

MicrosoftAppId와 MicrosoftAppPassword를 이렇게 구한 후 다시 원래 페이지로 이동 합니다.

 

나머지 필수 항목들을 채워 넣으신 후에 Register 버튼을 클릭합니다.

 

 

Bot V3가 등록 되었습니다~ ㅎㅎㅎ

 

이렇게 등록되었다고, 바로 테스트를 들어가시면 않됩니다. 방금 입력하고 만든 정보 3가지를 Web.config에 입력하신 후에 다시 Publish를 해 주셔야 정상 동작합니다.

 

BotId : Bot handle에 입력한 정보

MicrosoftAppId, MicrosoftAppPassword : 아까 작업 중에 만들어진 값 입력

 

Publish를 다시 하신 후 Portal에서 Test 버튼을 클릭하시면 Endpoint authorization successed라는 글씨가 출력되면 정상적으로 연결된 것입니다.

 

 

아래는 Web Chat을 이용해서 ifram 코드를 붙여 넣은 것입니다. 음 V1과 외관상으로는 차이가 없습니다.

다음에는 좀더 발전된 형태의 봇 만드는 방법에 대한 포스트를 작성하도록 하겠습니다.


블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

Bot Framework이 2016년 7월 9일 날짜로 버전 3로 업그레이드 되었습니다. 그래서 그동안 작업해 놓았던 자료들을 모두 업데이트 해야하는 상황이 발생했는데..으흠..기존 자료를 수정하기 보다는 그냥 새로운 자료를 만드는 방향으로 진행 해야할 것 같습니다. 과거 자료 수정해서 다시 올리기에는..시간대비 효율이 좋지 않고, 특히, 제가 싫어하는 일중에 하나가 했던 거 또 하는 것이라..

 

Upgrade your bot to V3

http://docs.botframework.com/en-us/support/upgrade-to-v3/#step-2-update-your-bot-code-to-version-30

 

 

에뮬레이터 V3 다운로드

http://botframework.blob.core.windows.net/bf-v3/tools/emulator/publish.htm

 

 

7/13일 에뮬레이터에서 오류가 발생하지 않습니다.

해결된 이유는 두가지 중에 하나이거나 두가지 모두입니다. 명확하게는 밝혀지지 않았습니다.;;;

 

1) Extension -> Update..중에 ASP.NET 최신 버전이 하나 있었습니다. 설치하면 아래와 같이 나옵니다.

설치하는데 상당히 오래 걸리는데..이 버전을 설치하고

 

2) 에뮬레이터 버전 3.0.0.57 버전으로 테스트를 하니 정상적으로 결과를 반환 했습니다.

 

 

정확한 원인은 밝혀 주세요~

 

 

 

 

* 가장 기초적인 Echo bot 실행 시키고 했는데도..500번 에러가 발생하고 있습니다. 음음...제가 잘못한 것인지..어떤 이유인지는 내일 또 찾아봐야 할 것 같습니다. 애뮬레이터 상으로는 몇가지 기능이 추가되어 있는 것을 확인 할 수 있었습니다.

 

 

업그레이드 된 내용을 간단하게 살펴 보겠습니다.

 

 

1. BotBuilder와 Connector가 하나의 SDK로 통합 되었습니다.

 

 

2. Message가 Activity로 변경되었습니다.

 

 

3. Activity Types과 Events

 

몇개의 이벤트 이름이 변경되고, 리팩터링 되었습니다. 새로운 ActivityTypes enumeration이 추가되었습니다.

 

 

4. Addressing

 

Addressing Activity objects가 변경되었습니다.

 

 

5. Sending Replies

 

사용자에게 비동기적으로 기존 연결이 아닌 다른 HTTP연결을 통해서 회신을 보낼 수 있게 되었습니다.

 

 

6. Bot Data Storage(Bot State)

 

BotState API를 이용해서 Bot의 상태를 저장하고, 불러올 수 있습니다.

 

 

7. 새로운 대화를 만들 수 있습니다.(Creating New Conversations)

 

 

8. Attachments and Options

 

첨부와 옵션 기능이 개선 되었습니다.

 

 

9. Web.Config에 키 입력하는 곳에 이름이 변경되었습니다.

 

 

에..위에서 변경된 사항을 약간더 알아 보려고, 소스를 다운 받아서 실행을 해봤는데..알수 없는 오류가 발생하는 군요..쿨럭..인터널 서버 오류라고 하는데..음음.. 이번주는 다시 삽질해서 정리한 다음에 다시 포스팅 하겠습니다.

 

하하하;;; 삽질은 아름다워~

 

 

 

 

 

 

 

블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

상쾌한 일요일 저녁입니다. 몇 일전에 우연히 발견한 봇 관련 포스트가 있어서 그 내용을 보면서 따라하기를 해 보려고 합니다. 벌써부터 기분이 좋아질려고 하는군요.. 이 포스트를 읽으시는 분들도 기분이 좋으시죠? 으흐흐;;;

올해는 봇이랑 뭔가 좋은 일을 만들어야 하는데 말입니다. 좋은 기운을 받아서 출발해 보죠

 

이 예제를 이용하기 위해서는 마이크로소프트 밴드가 필요하겠네요. 밴드가 아니라 다른 것을 가지고 있으면 그걸 이용하셔도 좋습니당. 물론 개발자 SDK는 직접 구하셔야겠네요.

 

 

1. 참고 포스트

Developing a Microsoft Health Bot based on Data captured from the Microsoft Band

https://blogs.msdn.microsoft.com/uk_faculty_connection/2016/06/17/developing-a-microsoft-health-bot-based-on-data-captured-from-the-microsoft-band/

 

 

2. 밴드의 어떤 데이터를 이용할까요?

 

. 운동 모드(달리기와 연습) : 매 초마다 심박을 기록합니다.

. 수면 추적 : 2분 동안 동작하고, 8분은 꺼져있습니다.

. 그 외 시간 : 1분 동안 동작하고, 9분은 꺼져있습니다.

. 수동 : 강제 확인 기능으로 Me Tile을 클릭하면 볼 수 있습니다.

 

 

3. 어디에 저장되고, 어떻게 사용할 수 있나요?

 

각종 정보는 MS Health로 전송됩니다. 그리고 API를 이용해서 조회해서 볼 수 있으며, 폰에 어플리케이션이 필요하지는 않습니다.

 

Integrate Microsoft Health data into your application or service.

http://developer.microsoftband.com/cloudAPI

 

 

4. 시작하기..

 

비주얼 스튜디오와 기타 설정하는 방법에 대해서는 아래 포스트를 참고하시기 바랍니다.

 

Bot Framework 시작하기 Part1

http://kaki104.tistory.com/496

 

 

5. Microsoft Health API

 

밴드는 자체 플래쉬 메모리에 운동한 데이터를 기록해 놓았다가, 모바일 장치에 Microsoft Helth 앱과 동기화를 하면 해당 데이터를 Microsoft cloud에 올리게 됩니다. 해당 데이터를 조회하는 방법은 Web dashboard를 이용하거나 MS Health API를 이용하는 방법이 있습니다.

 

Web dashboard를 통해 간단하게 운동량을 알 수 있습니다.~

 

 

 

API를 이용해서 데이터를 가지고 오기 위해서는 OAuth 2 인증 방법을 이용해서 인증 후 사용이 가능합니다. (브라우저에서 인증을 하는 방법)

 

그런데, bot은 브라우저가 아니기 때문에 인증하는 방법이 어렵습니다. 그래서~ 아래 이미지와 같은 방식을 통해서 인증 처리를 합니다. (세부 사항은 영문을 참고하세용)

 

 

이렇게 하기 위해서는 봇을 OAuth 2.0 client로 등록을 해야합니다.

 

1) 내 App 등록하는 화면에서 Add an app 버튼을 클릭해서 앱을 등록 합니다. 앱 이름은 MSHealthBot으로 합니다.  https://apps.dev.microsoft.com/?mkt=en-us#/appList

 

 

 

2) Application Id를 복사해서 메모장에 붙여 넣으세용(ClientId가 됩니다.)

 

3) Generate New Password를 클릭해서 새로운 패스워드 생성(이 때 생성된 비밀번호는 한번만 보여주니 복사해서 메모장에 저장 -> ClientSecret이 됩니다.)

 

4) Add Platform을 클릭해서 플랫폼 추가, Web으로

이 때 Redirect URI는 local에서 bot이 실행된 주소에 /api/auth/receivetoken 이라는 경로를 추가해서 등록

예를 들면 http://localhost:3978/api/auth/receivetoken 이렇게..

 

5) Live SDK support 클릭

 

6) Save

 

7) 아래의 코드를 Global.asax.cs 파일 하단에 아래 내용을 추가합니다.

 


    /// 인증키 저장용
    /// </summary>
    public class CredentialStore : ICredentialStore
    {
        private readonly Dictionary<string, string> _idMap = new Dictionary<string,
            string>();

        public void AddToken(string id, string token)
        {
            _idMap[id] = token;
        }

        public string GetToken(string id)
        {
            string val = null;
            if (_idMap.TryGetValue(id, out val))
            {
                return val;
            }
            return null;
        }
    }
    /// <summary>
    /// 인증키
    /// </summary>
    public interface ICredentialStore
    {
        string GetToken(string id);
        void AddToken(string id, string token);
    }
    public class MyDependencies
    {
        public static ICredentialStore _store = new CredentialStore();
    }

 

 

소스에 대한 이해..

간단하게 구현된 인증 토큰을 서버 메모리에 저장하기 위한 코드 입니다.

 

 

8) 솔루션 탐색기 창에서 Controllers에서 마우스 오른쪽 클릭 -> Add -> Class, AuthController란 이름으로 클래스를 추가하고, 아래 소스를 복사해서 붙여 넣습니다.

 

using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
using Newtonsoft.Json;

namespace MSHealthBot
{
    public class AuthController : ApiController
    {
        /// <summary>
        ///     서버에 올릴 경우 서버 주소로 변경합니다.
        /// </summary>
        private static readonly string RedirectUri =
            @"http://localhost:3978/api/auth/receivetoken";

        /// <summary>
        ///     조회 범위
        /// </summary>
        private static readonly string Scopes =
            "mshealth.ReadDevices mshealth.ReadActivityHistory mshealth.ReadActivityLocation mshealth.ReadDevices mshealth.ReadProfile offline_access";

        private readonly ICredentialStore _creds;

        private readonly string ClientId;
        private readonly string ClientSecret;

        /// <summary>
        ///     생성자
        /// </summary>
        public AuthController()
        {
            ClientId = Environment.GetEnvironmentVariable("MSHEALTHBOT_HEALTHAPI_CLIENTID");
            ClientSecret = Environment.GetEnvironmentVariable("MSHEALTHBOT_HEALTHAPI_CLIENTSECRET");
            _creds = MyDependencies._store;
        }

        [Route("api/auth/home")]
        [HttpGet]
        public HttpResponseMessage Home(string UserId)
        {
            var resp = Request.CreateResponse(HttpStatusCode.Found);
            resp.Headers.Location = CreateOAuthCodeRequestUri(UserId);
            return resp;
        }

        private Uri CreateOAuthCodeRequestUri(string UserId)
        {
            var uri = new
                UriBuilder("https://login.live.com/oauth20_authorize.srf");
            var query = new StringBuilder();
            query.AppendFormat("redirect_uri={0}", Uri.EscapeUriString(RedirectUri));
            query.AppendFormat("&client_id={0}", Uri.EscapeUriString(ClientId));
            query.AppendFormat("&client_secret={0}",
                Uri.EscapeUriString(ClientSecret));
            query.AppendFormat("&scope={0}", Uri.EscapeUriString(Scopes));
            query.Append("&response_type=code");
            if (!string.IsNullOrEmpty(UserId))
                query.Append($"&state={UserId}");
            uri.Query = query.ToString();
            return uri.Uri;
        }

        private Uri CreateOAuthTokenRequestUri(string code, string refreshToken = "")
        {
            var uri = new UriBuilder("https://login.live.com/oauth20_token.srf");
            var query = new StringBuilder();
            query.AppendFormat("redirect_uri={0}", Uri.EscapeUriString(RedirectUri));
            query.AppendFormat("&client_id={0}", Uri.EscapeUriString(ClientId));
            query.AppendFormat("&client_secret={0}",
                Uri.EscapeUriString(ClientSecret));
            var grant = "authorization_code";
            if (!string.IsNullOrEmpty(refreshToken))
            {
                grant = "refresh_token";
                query.AppendFormat("&refresh_token={0}",
                    Uri.EscapeUriString(refreshToken));
            }
            else
            {
                query.AppendFormat("&code={0}", Uri.EscapeUriString(code));
            }
            query.Append(string.Format("&grant_type={0}", grant));
            uri.Query = query.ToString();
            return uri.Uri;
        }

        [Route("api/auth/receivetoken")]
        [HttpGet]
        public async Task<string> ReceiveToken(string code = null, string state
            = null)
        {
            if (!string.IsNullOrEmpty(code) && !string.IsNullOrEmpty(state))
            {
                var tokenUri = CreateOAuthTokenRequestUri(code);
                string result;
                using (var http = new HttpClient())
                {
                    var c = tokenUri.Query.Remove(0, 1);
                    var content = new StringContent(c);
                    content.Headers.ContentType = new
                        MediaTypeHeaderValue("application/x-www-form-urlencoded");
                    var resp = await http.PostAsync(new
                        Uri("https://login.live.com/oauth20_token.srf"), content);
                    result = await resp.Content.ReadAsStringAsync();
                }
                dynamic obj = JsonConvert.DeserializeObject(result);
                _creds.AddToken(state, obj.access_token.ToString());
                return "Done, thanks!";
            }
            return "Something went wrong - please try again!";
        }
    }
}

 

소스의 이해...

OAuth 2.0 인증을 위한 코드와 Access Token으로 변경하는 코드입니다. 코드가 실행되기 위해서는 2개의 환경 변수에 값이 설정되어야 합니다.

MSHEALTHBOT_HEALTHAPI_CLIENTID, MSHEALTHBOT_HEALTHAPI_CLIENTSECRET는 소스에 api key를 포함하지 않고, 환경 변수에 입력 후 사용하는 방법입니다. 이 방법을 사용하기 위해서는 powershell을 관리자 모드로 실행하시고, 해당 소스가 위치한 폴더로 이동하신 후에

 

setx -m 키 벨류

 

형태로 입력을 하시면 됩니다. 이렇게 입력하는 api key가 2종류 있으니 사용해 보시기 바랍니다.

 

 

9) Model.cs class를 추가 후 다음

https://gist.githubusercontent.com/peted70/9e5db9148137f5b3f95e68517eda8b0e/raw/ee1cb88a4fd6e9276664e71ee576ca4d58d7027d/bot-mshealth-model

해당 페이지의 내용을 복사해서 붙여 넣습니다.

이 내용은 Microsoft Health에서 제공하는 데이터 모델 들입니다.

 

 

6. LUIS

 

드디어 LUIS를 이용해서 뭔가 하도록 하겠습니다. 세부적인 내용은 우선 패스~ 무조건 따라서 하나 만들어 봅니다.

 

1) JSON 파일을 다운로드해서 저장 합니다. 저장할 때 확장자는 반드시 json으로 합니다.

https://github.com/peted70/ms-health-bot/blob/2bbdbd7ed311db0362f1f6d9c95ee489dee8544a/assets/MSHealthBot.json

 

 

2) 루이스 사이트로 이동합니다. https://www.luis.ai

가입은 Microsoft Account만 있으면 가능하며, 추가로 몇가지 정보를 더 입력합니다.

 

 

3) Add New을 선택한 후 Import Existing Application을 선택합니다.

 

4) 아까 저장했던 json 파일을 선택한 후 Import 버튼을 클릭합니다.

5) Import과정이 완료 된 후 왼쪽 하단에 Train 버튼을 클릭합니다. 그러면 한참 혼자 연습을 합니다.

 

6) Train이 완료된 후 왼쪽 메뉴 중 상단에 Publish 버튼을 눌러서 서비스를 등록 합니다.

 

7) Publish가 완료 된 후 URL에 포함되어 있는 API Key 2개를 보관하셔야 합니다.

 

이제는 자연어를 입력하면, LUIS를 이용해서 단어를 분석해서 반환 합니다.

물론 한글로 입력하는 것은 아니되옵니다.

 

 

7. 나머지 Bot 완성하기

 

1) MessageController class에 작업을 해야하는데, 그 전에 Nuget package 중에 NadaTime을 설치해야 합니다. 이 패키지는 날자 형식을 파싱할 때 사용되는데, 검색을 하기전에 Include prerelease를 체크해야 검색이 가능합니다.

 

2) MessageController 전체 소스입니다.

using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Connector.Utilities;
using Newtonsoft.Json;
using NodaTime;
using NodaTime.Text;

namespace MSHealthBot
{

    [BotAuthentication]
    public class MessagesController : ApiController
    {
        private const string ApiVersion = "v1";

        private readonly ICredentialStore _creds;

        /// <summary>
        ///     기본 생성자
        /// </summary>
        public MessagesController()
        {
            _creds = MyDependencies._store;
        }

        private async Task<MSHealthUserText> ParseUserInput(string input)
        {
            var escaped = Uri.EscapeDataString(input);

            using (var http = new HttpClient())
            {
                var key = Environment.GetEnvironmentVariable("MSHEALTHBOT_LUIS_API_KEY");
                var id = Environment.GetEnvironmentVariable("MSHEALTHBOT_LUIS_APP_ID");

                string uri =
                    $"https://api.projectoxford.ai/luis/v1/application?id={id}&subscription-key={key}&q={escaped}";
                var resp = await http.GetAsync(uri);
                resp.EnsureSuccessStatusCode();

                var strRes = await resp.Content.ReadAsStringAsync();
                var data = JsonConvert.DeserializeObject<MSHealthUserText>(strRes);
                return data;
            }
        }

        /// <summary>
        ///     POST: api/Messages
        ///     Receive a message from a user and reply to it
        /// </summary>
        public async Task<Message> Post([FromBody] Message message)
        {
            if (message.Type == "Message")
            {
                var userid = message?.From?.Id;
                // Lookup the user id to see if we have a token already..
                var token = _creds.GetToken(userid);
                var prompt = "";

                //토큰 확인
                if (string.IsNullOrEmpty(token))
                {
                    var loginUri = new
                        Uri($"http://localhost:3978/api/auth/home?UserId={userid}");
                    prompt =
                        $"Please pay a visit to {loginUri} to associate your user identity with your Microsoft Health identity.";
                }
                else
                {
                    var data = await ParseUserInput(message.Text);

                    if (data.intents.Length <= 0 || data.entities.Length <= 0)
                    {
                        return
                            message.CreateReplyMessage(
                                "I don't have enough information to understand the question - please try again...");
                    }

                    var words = string.Join(", ", data.intents.Select(p => p.intent));
                    Debug.WriteLine(words);

                    var topIntent = data.intents[0].intent;

                    switch (topIntent)
                    {
                        case "SummariseActivity":
                            var firstOrDefault = data.entities.FirstOrDefault(e => e.type == "ActivityType");
                            if (firstOrDefault != null)
                            {
                                var entityStr = firstOrDefault.entity;

                                // This could be either date, time or duration..
                                var entityTime = data.entities.FirstOrDefault(e =>
                                    e.type == "builtin.datetime.time" ||
                                    e.type == "builtin.datetime.duration" ||
                                    e.type == "builtin.datetime.date");

                                if (entityTime.type == "builtin.datetime.duration")
                                {
                                    var res = PeriodPattern.NormalizingIsoPattern.Parse(entityTime.resolution.duration);

                                    // Now call the relevant Microsoft Health API and respond to the user...
                                    var st = SystemClock.Instance.GetCurrentInstant().InUtc().LocalDateTime - res.Value;

                                    var start = st.ToDateTimeUnspecified();
                                    var end = DateTime.Now;
                                    var res2 = await GetActivity(token, entityStr, start, end);
                                    var sleep = JsonConvert.DeserializeObject<Sleep>(res2);

                                    // create a textual summary of sleep in that period...
                                    var num = sleep.itemCount;
                                    if (num <= 0)
                                    {
                                        prompt = "You didn't track any sleep";
                                        break;
                                    }
                                    var total = sleep.sleepActivities.Sum(a =>
                                    {
                                        if (a.sleepDuration != null)
                                        {
                                            var dur = PeriodPattern.NormalizingIsoPattern.Parse(a.sleepDuration);
                                            return dur.Value.ToDuration().Ticks;
                                        }
                                        return 0;
                                    });

                                    var av = total/num;
                                    var sleepSpan = TimeSpan.FromTicks(av);
                                    var totalSpan = TimeSpan.FromTicks(total);

                                    var avSleepStr = $"{sleepSpan.ToString(@"%h")} hrs {sleepSpan.ToString(@"%m")} mins";
                                    var totalSleepStr =
                                        $"{totalSpan.ToString(@"%d")} days {totalSpan.ToString(@"%h")} hrs {totalSpan.ToString(@"%m")} mins";

                                    prompt =
                                        $"You have tracked {num} sleeps - average sleep per night {avSleepStr} for a total of {totalSleepStr}";
                                }
                            }
                            break;
                        default:
                            Debug.WriteLine($"topIntent:{topIntent}");
                            break;
                    }
                }

                //// calculate something for us to return
                //var length = (message.Text ?? string.Empty).Length;

                // return our reply to the user
                //return message.CreateReplyMessage($"You sent {length} characters");
                // return our reply to the user
                return message.CreateReplyMessage(prompt);
            }
            return HandleSystemMessage(message);
        }

        private Message HandleSystemMessage(Message message)
        {
            if (message.Type == "Ping")
            {
                var reply = message.CreateReplyMessage();
                reply.Type = "Ping";
                return reply;
            }
            if (message.Type == "DeleteUserData")
            {
                // Implement user deletion here
                // If we handle user deletion, return a real message
            }
            else if (message.Type == "BotAddedToConversation")
            {
            }
            else if (message.Type == "BotRemovedFromConversation")
            {
            }
            else if (message.Type == "UserAddedToConversation")
            {
            }
            else if (message.Type == "UserRemovedFromConversation")
            {
            }
            else if (message.Type == "EndOfConversation")
            {
            }

            return null;
        }

        private async Task<string> MakeRequestAsync(string token, string path,
            string query = "")
        {
            var http = new HttpClient();
            http.DefaultRequestHeaders.Authorization = new
                AuthenticationHeaderValue("Bearer", token);
            var ub = new UriBuilder("https://api.microsofthealth.net")
            {
                Path = ApiVersion + "/" + path,
                Query = query
            };
            var resStr = string.Empty;
            var resp = await http.GetAsync(ub.Uri);
            if (resp.StatusCode == HttpStatusCode.Unauthorized)
            {
                // If we are unauthorized here assume that our token may have expired and use the
                // refresh token to get a new one and then try the request again..
                // TODO: handle this - we can cache the refresh token in the same flow as the access token
                // just haven't done it.
                return "";
                // Re-issue the same request (will use new auth token now)
                //return await MakeRequestAsync(path, query);
            }
            if (resp.IsSuccessStatusCode)
            {
                resStr = await resp.Content.ReadAsStringAsync();
            }
            return resStr;
        }

        private async Task<string> GetActivity(string token, string activity,
            DateTime Start, DateTime end)
        {
            var res = string.Empty;
            try
            {
                res = await MakeRequestAsync(token, "me/Activities/",
                    string.Format("startTime={0}&endTime={1}&activityTypes={2}&ActivityIncludes=Details",
                        Start.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'"),
                        end.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'"),
                        activity));
            }
            catch (Exception ex)
            {
                return $"API Request Error - {ex.Message}";
            }
            await Task.Run(() =>
            {
                // Format the JSON string
                var obj = JsonConvert.DeserializeObject(res);
                res = JsonConvert.SerializeObject(obj, Formatting.Indented);
            });
            return res;
        }
    }
}

 

 

8. 서비스를 사용하기

 

서비스를 실행 후 Bot Emulator을 실행해서 테스트 할 수 있습니다.

 

일단 시작하고 아무 글이나 입력하면, Live에 로그인을 하는 링크가 출력됩니다. 링크를 클릭 합니다.

 

 

how much sleep have I had in last 2 weeks?

라고 입력하면, 2주 동안 발생한 sleep 데이터를 분석해서 반환해 줍니다.

 

 

 

이 셈플 예제는 Health API의 기능을 모두 조회할 수는 없습니다. (sleep만 조회가 가능하더군요..) 기본적인 사용법에 대한 설명이 있는 것이니, 개발자 스스로 성능을 개선해 나가면 더 좋은 채팅 봇이 될 것 같습니다.

 

포스트에 이상한 점이나 질문은 리플로 남겨주시면 처리하겠습니다.

 

블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

티스토리 툴바