블로그 이미지
* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/ kaki104

카테고리

List All (551)
Xamarin Forms (4)
Bot Framework (19)
Azure (9)
Windows 10 (35)
Facebook News & Tips (158)
Windows App(Universa.. (83)
Windows 8&8.1 (113)
Windows Phone 8 (42)
Silverlight (37)
HTML5 & MVC4 (16)
WPF (1)
Portable Class Library (2)
Uncategorised Tips a.. (3)
Kinect for Windows (2)
ETC (12)
kaki104 Scrap (4)
App News (11)
Total521,316
Today35
Yesterday108


나만의 AI Speaker 만들기


라즈베리파이, UWP, Microsoft Azure 서비스를 이용해서 한글을 지원하는 AI Speaker를 만드는 과정을 설명드릴려고 합니다.

총 9개의 동영상으로 구성할 예정입니다.



Part1

. Smart Speaker, AI Speaker란
. AI Speaker 종류(국내, 해외)
. AI Speaker 관련 기사 및 사이트
. System diagram
. Microsoft Harman Kardon Invoke with Cortana
. Microsoft Suface Headphone
. 준비물

. Part1 바로가기



Part2

. Bot 생성 및 배포
. NuGet packages설치
. Azure에 Publish
. Channel 생성 및 연결
. DirectLine 추가
. Bot 연결 테스트
. Part2 바로가기


. Part2까지 소스 - 모든 개발이 완료되면 전체 소스를 Git에 업로드하도록 하겠습니다.

KakiAISpeaker.Bot_part2.zip


Part3

Part4

Part5

Part6

Part7

Part8

Part9


Posted by MVP kaki104


이번 포스트는 Bot에서 OneDirve의 파일을 조회하고 사진이나 음악 파일을 재생 시킬 수 있는 방법에 대해서 설명을 하려고 합니다.


Bot 만들고, ppt만드는데 2주넘게 걸린 것 같네요.. 과정이 너무 까다롭고 레퍼런스가 없어서 삽질의 끝을 본 것 같습니다.



1. 환경및준비
2. 참고자료
3. Microsoft Graph
4. 시스템 구성
5. BotAuth
6. OneDriveBot 작업 순서
7. 핵심코드


8. 동영상

https://youtu.be/FQb2Imp00to


9. PDF


OneDriveBot.pdf



Posted by MVP kaki104


이번 포스트는 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



Posted by MVP kaki104


이번 포스트 주제는 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


Posted by MVP kaki104


지난 포스트에서 작성했던 Hello World Bot을 Azure에 배포하고, Telegram에 연동해서 대화를 하는 과정을 설명합니다.

코딩은 없습니다. 편하게 보시면 될 것 같습니다.



동영상

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

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


동영상 링크



내용

1. Hello World Bot 사용하기
2. Bot과 연결 할 수 있는 서비스
3. Telegram과 연결하기



PPT

botframework2.pptx


Posted by MVP kaki104


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


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



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


1. 참고 자료 링크

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

3. Bot Framework Emulator

4. .Net Bot 만들기

5. 실습


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

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

동영상 링크


PPT


botframework.pptx


Posted by MVP kaki104

대화한 내용을 찾아보기 위해서 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

Posted by MVP kaki104

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

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



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/

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



Posted by MVP kaki104

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 모델들이 정말 많이 있으니 하나씩 찾아보면서 자신만의 채팅 봇을 만들어 보면 좋을 것 같습니다.

 

 

 

Posted by MVP kaki104

포켓몬고의 열풍이 대한민국을 휩쓸고 있는 주말입니다. 평소에 전혀 게임에 관심이 없었던 사람들까지도 모두 알수 있을 정도로 가십성 기사는 정말 엄청나게 양산을 하는 것 같습니다. 게임 디자이너 중 한명이 한국 사람이라는 뉴스는 왜 나오는지.. 에혀;; 뭐.. 어찌 되었던 간에 앞으로 저는 앞으로 새로운 비전은 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 등 여러 플랫폼들을 이용할 수 있습니다.

 

 

Posted by MVP kaki104

티스토리 툴바