라이크 어 Poncho: JiveScript 날씨 챗봇

들어가며

최근 챗봇(chatbot)에 대한 관심이 증가하고 있다. 페이스북, 구글, MS 등 글로벌 공룡기업들은 이미 오래전부터 인공지능 기반의 챗봇을 연구했다. 또한 자연어처리와 인공지능 관련 스타트업을 인수함과 동시에 그 성과물을 공개하면서 공격적인 행보를 보이고 있다. 예를 들어 페이스북이 인수한 wit.ai, 구글이 인수한 api.ai 등은 챗봇을 개발할 수 있는 서비스를 제공한다. 국내의 경우 올해 3월 알파고(Alphago)와 이세돌의 세기의 대결이 치뤄지면서, 국내에서는 일반인들도 인공지능에 대해 관심을 가지게 되었다. SKT, 네이버, 카카오 등 국내 최고의 업체들도 예전부터 관련 기술을 준비했고, 최근 앞다투어 챗봇 서비스와 개발 도구를 오픈하기 위해 준비중이라는 기사가 나왔다.

하지만 wit.aiapi.ai 등과 같은 개발 도구로 한글 챗봇을 만들기에는 그 기능이 부족하다. 따라서 아직은 챗봇 엔진을 직접 개발해야 한다. 챗봇에는 크게 "규칙/검색 기반" 챗봇과 "학습" 기반 챗봇이 있다. "규칙" 기반 챗봇의 경우, 미리 정의된 규칙에 대해 사용자의 메시지를 비교하여 매칭하는 방식이다. 따라서 정해진 규칙에 벗어나는 질문에는 대답할 수 없으며 대답도 고정적이다. 반면 "학습" 기반 챗봇은 대량의 데이터를 바탕으로 규칙 자체를 컴퓨터가 학습하게 된다. 하지만 기술 진입장벽이 높고, 학습을 위한 대규모의 학습 데이터를 필요로 한다.

이 글에서는 "규칙" 기반 챗봇 개발 도구의 하나인 RiveScript의 규칙 해석기에 한글 형태소 분석기인 twitter-korean-text를 추가하여 필자가 직접 개발한 JiveScript에 대해 소개한다. 그리고 JiveScript를 활용하여 페이스북 메신저에서 동작하는 날씨 챗봇을 만들어 본다.

챗봇이 화두다

페이스북은...

페이스북은 작년 1월 챗봇 개발을 위해 wit.ai를 인수한 후, 올해 4월 열린 'F8 2016' 에서 페이스북 메신저 플랫폼의 챗봇 개발을 돕기 위한 메시징 API를 공개했다. 이 자리에서 인공지능 챗봇의 하나로 판초(Poncho) 날씨봇을 시연했다. 페이스북 메신저에서 판초를 등록한 후 "What is the weather like?"와 같이 질문하면, 자신의 위치정보를 기반으로 현재 날씨를 답해준다. 현재는 한글도 지원하며 "서울 날씨" 또는 "서울 날씨 알려줘"와 같이 질문하면 해당 지역의 날씨를 알려준다.

poncho_chatting

판초 날씨 봇과 채팅하기

구글은...

구글은 올해 9월, 자연어 처리 및 챗봇 개발 플랫폼 업체인 api.ai를 인수했다. 또한 새로운 메신저인 알로(Allo)를 출시했다. 알로(Allo)에는 인공지능 챗봇인 어시스턴트(Google Assistant)가 탑재되어 있다.

google_allo_assitant

구글 어시스턴트와 채팅하기

국내는...

네이버는 인공지능 대화 시스템인 라온(LAON) 프로젝트를 진행중이며, 카카오의 경우 플러스친구라는 이름으로 챗봇과 유사한 형태를 서비스하고 있다. 그리고 다양한 서비스 업체에서 라인과 카카오톡 등 메신저 플랫폼을 기반으로 챗봇을 개발할 수 있도록 돕는 메신저 API를 공개할 것으로 보인다. 아직은 페이스북이나 구글에 뒤진 후발 주자이나, 대화 학습을 위한 한글 데이터가 풍부하고 글로벌 기업에 비해 한글 형태소 처리에 경험이 풍부하다는 점을 생각해 볼 때, 국내 시장에서는 여전히 우위를 지킬 듯 하다.

나는 한국인입니다

wit.ai나 api.ai 등 챗봇 개발 플랫폼 서비스를 활용하면, 페이스북 또는 슬랙과 같은 메신저 플랫폼 위에서 동작하는 챗봇을 구현할 수 있다. 하지만 이러한 범용적인 챗봇 개발 플랫폼 서비스로는 적당한 수준의 한글 챗봇 서비스를 만들기란 쉽지 않다. 따라서 서비스 업체 개발자로서 챗봇을 만들기로 했다면, 한글을 이해할 수 있는 챗봇 엔진을 만드는 일은 서비스 개발자의 몫이다. 물론 네이버나 카카오의 챗봇 메신저 API가 공개되면, 자연어 처리나 개체명 인식 등 사용자의 대화를 파싱(parsing)할 수 있게 돕는 도구가 만들어질 것이다. 하지만 경쟁업체보다 앞서 챗봇을 제작하여 사용자 경험을 높이려면, 기술이 성숙되기 전에 선점하는 것이 중요하리라고 본다.

챗봇을 만들려고 마음 먹었다면...

챗봇이 사용자의 대화를 이해할 수 있는 범위와 답변할 수 있는 범위를 한정지어야 한다.

  • 대화 문맥의 범위 날씨, 영화, 쇼핑 등 특정 범위의 대화를 할 수 있나 vs. 다양한 주제에 대해 대화할 수 있나
  • 대화 상태의 범위(정보 교환 방식) 1회성 질문에만 답변할 수 있나 vs. 대화를 연속해서 진행할 수 있나(이전 대화를 기억)
  • 답변 선택 및 생성 방식(지능 획득 방법) 질문마다 정해진 답변이 있나(검색/규칙 기반 모델) vs. 질문에 따라 답변을 생성하나(생성형 모델)

아래는 "엑사젠"님이 쓰신 "혼자 힘으로 한국어 챗봇 개발하기"에서 발췌한 내용입니다.

chatbot_cat

대화형 AI 시스템의 분류, http://exagen.tistory.com/1

chatbot_cat2

지능 획득 방법에 따른 AI 시스템의 분류, http://exagen.tistory.com/1

[덧붙임] 혼자 힘으로 한국어 챗봇 개발하기 by 엑사젠 챗봇의 개념을 이해하고 실제로 동작하는 챗봇을 만들고자 한다면, 엑사젠님의 쓰신 "혼자 힘으로 한국어 챗봇 개발하기"를 꼮 읽어보기 바란다. 챗봇에 대한 개념과 적용 사례를 설명하고, 봇이 이해할 대화를 만드는 방식이 상세히 설명되어 있다. 한국어의 특성을 고려하여 규칙을 작성하는 방법은 상당히 유용하다. 무엇보다 규칙기반 챗봇 개발 도구인 Chatscript를 설치하여 실제로 동작하는 챗봇을 만들어 볼 수 있다.

코날(KoNal): 한국 날씨 봇 Demo

특정 문맥에 대해서만 대화할 수 있고, 1회성으로만 질의 응답하는 검색/규칙 기반 챗봇이 가장 단순하다. 이 글에서는 검색/규칙 기반 챗봇 개발 도구를 사용해서 국내 특정 지역의 날씨를 알려주는 챗봇을 개발한다. 딥러닝 기반의 학습형 채팅 봇 개발에 대해서는 Deep Learning for Chatbots가 도움이 된다.

konal_chatting

코날 날씨 봇과 대화하기

당분간은 해당 Demo 서버가 유지될테니 실제 동작을 테스트해보려면, 페이스북 메신저로 코날(konal) 봇과 대화를 나눠보기 바란다. (구현에 사용한 날씨 API 쿼터가 200회/1일이어서 응답이 오지 않을 수도 있다)

대화 스크립트 작성하기

가장 먼저 할 일은 챗봇이 지원할 대화 형태를 정의하는 일이다. 코날 날씨봇은 특정 지역의 날씨에 대한 질문, 그리고 인사/감사와 같은 단순한 일상 대화를 나눌 수 있게 만들고자 한다.

나> 안녕 봇> 만나서 반가워 나> 서울 강남구 도곡동 날씨 봇> 오늘 서울 강남구 도곡동 날씨는 "...."입니다. 나> 경남 진주시 날씨 알려줘 봇> 오늘 경남 진주시 날씨는 "..."입니다. 나> 제주 날씨는? 봇> 오늘 제주 날씨는 "..."입니다. 나> 고마워 봇> 별말씀을 나> 꺼져 봇> 반사

굵게 표시한 부분은 해당 지역의 날씨를 묻는 대화다. 날씨를 묻는다는 점에서는 문맥적으로 동일하지만, 그 형식은 서로 다르다.

  • 서울 강남구 도곡동 날씨 [시도] [시군구] [읍면동] [날씨]
  • 경남 진주시 날씨 알려줘 [시도] [시군구] [날씨] [알려줘]
  • 제주 날씨는? [시도] [날씨는?]

따라서 서로 비슷해보이지만 형식이 완전히 다르므로, 질문의 의도를 파악하기 위해서는 최대 3개의 규칙을 정의해야 한다.

챗봇 개발 도구 선정하기

규칙 기반 챗봇 개발 도구로는 ChatScriptRiveScript가 있다.ChatScript는 채팅봇 경연대회인 뢰브너 프라이즈(Loebner Prize)에서 2010년, 2011년 2014년, 2015년 총 4차례 우승팀에서 사용되었을 정도 성능을 입증받았다. 가장 큰 장점은 형태소 분석기를 내장하고 있어서, 동사/명사 원형을 기준으로 규칙을 작성할 수 있다는 점이다. 따라서 규칙이 단순해지며 그 개수도 적어진다. 예를 들어 사용자가 "I am at the hotel" 또는 "I was at the hotel"과 같이 답할 수 있는 상황이라면, ChatScript 엔진은 사용자가 입력한 문장을 형태소 분석을 거쳐 그 원형(be)을 찾은 후 규칙을 탐색할 수 있다.

chatscript_script_nlp

ChatScript 규칙 작성 예시

ChatScript의 특장점에 대해서는 exagen님이 쓰신 "2. 인공지능 ChatScript의 특장점"을 참고하기 바라며, ChatScript를 설치하고 사용해보려면 "ChatScript Tutorial"을 참고할 수 있다.

안타깝게도 ChatScript의 강력한 장점인 형태소 분석기는 영어권을 지원하며, 한글이 지원되지 않는다는 점이다. 또한 C언어로 작성되었기에, 개인적으로는 수정을 해볼 염두가 나지 않았다.

또다른 규칙 기반 챗봇 개발 도구로는 RiveScript가 있다. ChatScript만큼은 강력한 기능을 제공하지 않고, 형태소 분석 기능도 포함되어 있지 않다. 하지만 규칙 작성 방법이 꽤 간단하여, 챗봇을 쉽고 빠르게 개발할 수 있다는 장점이 있다. 또한 규칙 해석기는 Perl로 작성되었지만, Java나 Python 등 다른 프로그래밍 언어도 지원하므로 자신의 프로그래밍 언어 입맛에 맞게 골라서 확장할 수 있다. 물론 오픈소스다.

rivscript_script_x

RiveScript 규칙 작성 예시

위의 예시에서 보는 것처럼, RiveScript는 대소문자 구분 없이 문자가 그대로 일치해야 한다. 예를 들어 "what was you"라고 묻는 경우에는, 위의 규칙이 매칭되지 않는다.

JiveScript

한글은 문장에서 동일한 의미를 갖는 동사가 여럿이며, 그 형태도 다양하게 변형될 수 있다. 예를 들어 "날씨 [알려줘]", "날씨 [알려줄래]", "날씨 "보여줘"는 모두 같은 의미를 갖는다. 심지어 동사가 없이 "날씨"라고만 말하더라도 그 의미가 통한다. 따라서 다양한 동사의 변형에 대해 서로 다른 규칙을 작성하기 보다, "날씨 [알다]", "날씨 [보다]"와 같이 동사 원형을 기준으로 규칙을 작성하는 것이 더 간편하다.

그래서 만들었습니다.

RiveScript의 자바 해석기에 한글 형태소 분석기를 더하여 JiveScript를 만들어 보았습니다.

JiveScript = [RiveScript 챗봇 개발도구] + [twitter-korean-text 형태소 분석기]

프로젝트는 라이브러리와 예제로 구성되어 있습니다.

jivescript-korean: https://github.com/socurites/jivescript-korean

1
2
3
4
jivescript-korean
├── jivescript-korean-core
├── jivescript-korean-ext
├── pom.xml
  • jivescript-korean-core: rivescript에 twitter-korean-text 형태소 분석기를 적용
  • jivescript-korean-core-ext: rivescript에 지원하지 않는 추가적인 기능 제공

jivescript-korean-examples: https://github.com/socurites/jivescript-korean-examples

1
2
3
4
jivescript-korean-examples/
├── jivescript-korean-examples-konal
├── jivescript-korean-examples-util
├── pom.xml
  • jivescript-korean-examples-util: example에서 공통적으로 사용하는 유틸리티
  • jivescript-korean-exampls-konal: 코날 날씨 봇 프로젝트

채팅봇 standalone으로 실행하기

코날 날씨 채팅 봇은 로컬에서 standalone으로 실행할 수 있다. 아래는 examples-konal 프로젝트에 있는 JiveScriptKonalBotDemo.java 예제다.

1
2
3
4
5
6
7
8
9
10
11
public class JiveScriptKonalBotDemo {
	/** template file directory path. */
	private static final String TEMPLATE_DIR_PATH = "konal/rive";
	private static final String KEYWORD_DIR_PATH = null;
	/** analyzing user request. */
	private static final boolean ENABLE_ANALYZE = true;
	public static void main(String[] args) {
		JiveScriptStandaloneBot botDemo = new JiveScriptStandaloneBot();
		botDemo.run(TEMPLATE_DIR_PATH, KEYWORD_DIR_PATH, ENABLE_ANALYZE);
	}
}
  • TEMPLATE_DIR_PATH rivescript 규칙이 정의된 디렉토리 상대경로
  • KEYWORD_DIR_PATH twitter-korean-text 형태소 분석기에 미정의된 명사를 추가로 등록
  • ENABLE_ANALYZE 규칙 매칭시 형태소 분석기 적용 여부

실행후 특정 지역의 날씨를 질의하면, 매칭된 규칙에 따라 봇이 응답한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
      .   .       
     .:...::      JiveScript Java // JSBot
    .::   ::.     Version: 0.6.0
 ..:;;. ' .;;:..  
    .  '''  .     Type '/quit' to quit.
     :;,:,;:      Type '/help' for more options.
     :     :      
:: building bot
송준이> 서울시 강남구 도곡동 날씨
Bot> 오늘 서울 강남구 도곡동 날씨입니다
송준이> 제주시 날씨
Bot> 오늘 제주 날씨입니다
송준이> 고마워
Bot> 오히려 제가 감사합니다

jivescript 규칙 정의 관례

jivescript 규칙 정의는 rivescript 관례를 그대로 따른다. 자세한 내용은 RiveScript Tutorial에서 확인할 수 있다. 코날 챗봇은 총 5개의 파일로 구성된다.

1
2
3
4
5
6
7
8
9
konal/
├── jive
│   └── keywords.jive
└── rive
    ├── begin.rive
    ├── command_weather.rive
    ├── hello.rive
    ├── myself.rive
    └── star.rive
  • begin.rive 전역 변수(var) 및 치환 규칙(substitution), 배열(array) 등 기본 데이터를 정의
  • myself.rive 봇 자체에 대한 대화를 정의
  • hello.rive 인사/감사 등 일상 대화를 정의
  • command_weather.rive 날씨에 대한 대화를 정의
  • star.rive 앞의 규칙에 매칭되지 않을 경우 적용할 규칙을 정의

아래는 날씨에 대한 대화를 정의한 command_weather.rive 파일이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 *	시도 시군구 읍면동
 */
+ [*] (@sido) [*] (@sigungu) (@town) [@weather] [@show] [*]
- 오늘 <star1> <star2> <star3> 날씨입니다|sido=<star1>/sigungu=<star2>/town=<star3>
/**
 *	시도 시군구
 */
+ [*] (@sido) [*] (@sigungu) [@weather] [@show] [*]
- 오늘 <star1> <star2> 날씨입니다|sido=<star1>/sigungu=<star2>/town=
/**
 *	시도
 */
+ [*] (@sido) [*] [@weather] [@show] [*]
- 오늘 <star1> 날씨입니다|sido=<star1>/sigungu=/town=
/**
 *	시군구
 */
+ [*] (@sigungu) (@town) [@weather] [@show] [*]
- 오늘 <star1> 날씨입니다|sido=/sigungu=<star1>/town=
/**
 *	시군구 읍면동
 */
+ [*] (@sigungu) (@town) [@weather] [@show] [*]
- 오늘 <star1> <star2> 날씨입니다|sido=/sigungu=<star1>/town=<star2>
/**
 *	읍면동
 */
+ [*] (@town) [@weather] [@show] [*]
- 읍면동이 <star1>이라는 것은 잘 알겠는데, "시도""시군구"도 알려줘
^ 지역을 자세히 알려줘, 예를 들면 "[서울시] [강동구] [암사동]" ~{weight=3}|sido=/sigungu=/town=
[덧붙임] twitter-korean-text 한글 형태소 분석기 twitter-korean-text는 트위터에서 만든 오픈소스 한글 형태소 분석기로, scala를 기반으로  한다.  완벽한 형태의 형태소 분석을 지원하지는 않지만, 간단한 형태소 분석기가 필요한 경우 적용이 쉽고, 성능도 뛰어나다. [덧붙임] JiveScript에서 지원하지 않는 것들 RiveScript에서 지원하지만 구현상의 이유로 지원하지 않는 기능으로는 오브젝트 매크로(Object Macro), 스트림(stream), 음수 가중치(weight)가 있다.

페이스북 메신저 기반 챗봇 개발하기

JiveScript를 이용하면 규칙 기반 챗봇 엔진을 개발할 수 있다. 앞서 본 standalone 챗봇 엔진을 페이스북 메신저에서 사용할 수 있게 만들려면 몇가지 수고를 더해야 한다.

메신저 기반 챗봇 아키텍처

fb_chatbot_achitecture

페이스북 메신저 기반 챗봇 아키텍처

  • 메신저 실제로 사용자가 챗봇과 대화하는 인터페이스 레이어 예제) 페이스북 메신저 플랫폼
  • 메신저 API CallBack 서버 메신저에서 입력/선택 등의 이벤트에 요청/응답을 주고받는 레이어 예제) nodejs 기반 페이스북 메신저 API를 사용하여 개발
  • 챗봇 서버 사용자 입력 메시지를 챗봇에 정의된 규칙에 매칭하여 챗봇의 응답 메시지를 생성하는 레이어 예제) JiveScript를 사용하여 코날 날씨 봇 개발
  • Back데이터 매칭된 규칙에 대해 챗봇 응답 메시지를 생성할 때 필요한 추가적인 데이터를 제공하는 레이어 예제) SKP Deveoper API에서 제공하는 날씨 데이터 활용

개발하기에 앞서...

페이스북 메신저를 기반의 챗봇을 개발하려면, 페이스북 페이지, 페이스북 앱을 먼저 생성해야 한다(기존에 생성한 페이지 또는 앱이 있다면 그대로 사용해도 된다). 또한 페이스북 메신저의 이벤트를 받는 CallBack 서버는 HTTPS 포트만을 지원하므로, HTTPS 포트를 지원하도록 서버를 준비해둔다.

CallBack 서버 샘플 예제 코드 다운로드

CallBack 서버로 사용할 서버를 준비해둔다. 해당 서버는 HTTPS로 접속할 수 있어야 한다. 해당 서버에서 페이스북에서 제공하는 샘플  예제 코드를 다운로드 한다.

1
git clone https://github.com/fbsamples/messenger-platform-samples

페이지 액세스 토큰 발급

[Token Generation] 영역에서 앞서 만든 페이스북 페이지에 대한 페이지 액세스 토큰을 발급한다.

fb_app_page_access_token

페이스북 페이지 액세스 토큰 발급

토큰은 새로 발급받을 수 있으며, 이전에 발급된 토큰 역시 유효하다.

CallBack 서버 실행하기

앞서 다운로드 한 CallBack 샘플 프로젝트로 이동한다.

설정 파일을 수정한다.

1
2
3
4
5
6
{
    "appSecret": "",
    "pageAccessToken": "",
    "validationToken": "",
    "serverURL": ""
}
  • appSecret 페이스북 앱 페이지 내의 [Settings] 메뉴의 App Secret 값
  • pageAccessToken 앞서 발급한 페이지 액세스 토큰
  • validationToken CallBack 서버에서에서 request를 검증할 때 사용하는 토큰으로 webhook 등록시 사용할 값
  • serverURL CallBack 서버의 URL 예) https://wwww.jivechatbot.com

index.html 파일을 수정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script>
      window.fbAsyncInit = function() {
        FB.init({
          appId: 'APP_ID',
          xfbml: true,
          version: 'v2.6'
        });
      };
...
<div class="fb-send-to-messenger" 
        messenger_app_id='APP_ID'
        page_id='PAGE_ID'
        data-ref="PASS_THROUGH_PARAM" 
        color="blue" 
        size="standard">
      </div>
    </div>
...
<div class="fb-messengermessageus" 
        messenger_app_id='APP_ID'
        page_id='PAGE_ID'
        color="blue"
        size="standard">
      </div>
  • APP_ID 페이스북 앱 페이지 내의 [Settings] 메뉴의 App ID 값
  • PAGE_ID 앞서 생성한 페이스북 페이지의 [About] > Facebook Page ID 값

설정이 끝나면 서버를 실행한다.

1
2
3
4
5
$ npm install
$ npm start
> messenger-get-started@1.0.0 start /home/ubuntu/messenger-platform-samples/node
> node app.js
Node app is running on port 5000

Webhook 추가하기

앞서 생성한 페이스북 앱 페이지에서 [+ Add Product] > [Messenger]를 선택한다. Webhooks 영역의 [Setup Webhooks]를 선택한다.

fb_app_webhook

페이스북 메신저 webhook 추가하기

  • Callback URL 페이스북 메신저 이벤트를 받을 CallBack 서버의 URL HTTPS만 지원
  • Verify Token CallBack 서버에서에서 request를 검증할 때 사용할 토큰
  • Subscription Fields 페이스북 메신저에 리스닝할 이벤트 message_deliveries, messages, messaging_optins, and messaging_postbacks를 선택

[Verify and Save]를 클릭하여 저장한다. 성공하면, 앞서 생성한 페이스북 페이지의 이벤트를 구독하도록 설정한다.

fb_app_subscribe_page

페이스북 페이지 이벤트 구독하기

테스트하기

페이스북 메신저에서 KoNal을 검색한 후 메신지를 보낸다. 샘플 CallBack 서버는 사용자가 입력한 메시지를 그대로 반환한다.

fb_callback_sample_sendtext_app

샘플 CallBack 서버와 채팅하기

fb_callback_sample_sendtext

샘플 CallBack 서버 코드: 텍스트 메시지 전송

Back 데이터 준비하기: SKP Developer API

챗봇 서버 자체는 날씨 데이터를 갖고 있지 않다. 챗봇 서버는 사용자가 날씨를 알고 싶은 "지역"만을 추출한다. 아래와 같이 KoNal 날씨 챗봇을 standalone으로 실행한 경우,

1
2
3
4
5
6
7
8
9
10
      .   .       
     .:...::      JiveScript Java // JSBot
    .::   ::.     Version: 0.6.0
 ..:;;. ' .;;:..  
    .  '''  .     Type '/quit' to quit.
     :;,:,;:      Type '/help' for more options.
     :     :      
:: building bot
송준이> 서울시 강남구 도곡동 날씨
Bot> 오늘 서울 강남구 도곡동 날씨입니다

챗봇은 사용자가 "서울시 강남구 도곡동"의 날씨를 질문하고 있다는 정보를 추출할 뿐이다.

따라서 해당 지역의 날씨는 외부 서버에서 조회해야 한다.

SKP Developer API

SKP Developer API에서는 11번가, 멜론, T 맵 등 다양한 API를 제공한다. 이 글에서는 날씨 API를 Back 데이터로 활용한다. 아래는 시간별 날씨 API의 필수 파라미터다.

skp_weather_api

SKP Developer 시간별 날씨 API 파라미터 정보

  • city 시도
  • country 시군구
  • village 도곡동

지역명을 파라미터로 사용하는 경우 정확히 그 값이 같아야만 동작한다. city에 "서울시"라고 입력하는 경우 날씨가 조회되지 않는다.

챗봇은 사용자가 입력한 지역명, 예를 들어 "서울 강남구 도곡동"을 city, country, village로 토큰화할 수 있어야 한다. 이에 대한 내용은 JiveScript 챗봇을 개발할 때 자세히 설명한다.

KoNal JiveScript 챗봇 실행하기

KoNal 서버 실행하기

CallBack 서버로 이동한 후, JiveScript와 KoNal 예제 프로젝트를 다운로드하고, KoNal 서버를 실행한다.

1
2
3
4
5
6
7
8
9
10
$ git clone jivescript-korean
$ git clone jivescript-korean-examples
$ cd jivescript-korean
$ mvn clean install -Dmaven.test.skip
$ cd ..
$ cd jivescript-korean-examples
$ cd jivescript-korean-examples/jivescript-korean-examples-util/
$ mvn clean install -Dmaven.test.skip
$ cd ../jivescript-korean-examples-konal/
$ mvn clean compile spring-boot:run

CallBack 서버에서 KoNal 서버 호출하도록 만들기

한글 파라미터를 encode해야 하므로, urlencode 모듈을 패키지에 추가한다.

1
2
3
4
5
6
$ vi package.json
..
    "request": "^2.72.0",
    "urlencode": "^1.1.0"   <--- 여기
..
$ npm install

KoNal 서버를 호출하도록 sendTextMessage(..) 함수를 아래와 같이 수정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function sendTextMessage(recipientId, messageText) {
  console.log("jive=" + messageText);
  var botReply = "";
  request.get(
    'http://[konal-ip]:8080?message=' + urlencode(messageText),
    function(error, response, body) {
      if ( !error && response.statusCode == 200 )  {
        console.log(body);
        botReply = body;
        var messageData = {
          recipient: {
            id: recipientId
          },
          message: {
            //text: messageText,
            text: botReply,
            metadata: "DEVELOPER_DEFINED_METADATA"
          }
        };
        callSendAPI(messageData);
      } else if ( error ) {
        console.error(error);
      }
    }
  );
}

보는 것과 같이 사용자가 입력한 메시지를 KoNal 챗봇에 전달한 후, 챗봇이 반환한 답변을 전송하도록 수정했다. 이제 KoNal 챗봇과 대화하면, 앞서 본 것과 같이 날씨정보를 알려주는 것을 확인할 수 있다.

konal_chatting

코날 날씨 봇과 대화하기

KoNal JiveScript 챗봇 코드 보기

KoNal JiveScript 챗봇은 spring-boot를 기반으로 만들었다.

Bot과 RestTemplate 빈 등록하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
        @Bean
	public JiveScriptBot bot() {
		JiveScriptBot bot = (new JiveScriptExtBotBuilder())
				.analyze(ENABLE_ANALYZE)
				.parse(TEMPLATE_DIR_PATH, KEYWORD_DIR_PATH)
				.build()
		;
		return bot;
	}
	@Bean
	public RestTemplate restTemplate() {
		RestTemplate restTemplate = new RestTemplate();
		return restTemplate;
	}
	@Bean
	public HttpEntity<String> httpEntity() {
		HttpHeaders headers = new HttpHeaders();
		headers.set("x-skpop-userId", "######### USER ID #########");
		headers.set("Accept-Language", "ko_KR");
		headers.set("Accept", "application/json");
		headers.set("access_token", "######## ACCESS TOKEN ########");
		headers.set("appKey", "#### APP KEY ###########");
		HttpEntity<String> entity = new HttpEntity<String>("", headers);
		return entity;
	}
  • bot() JiveScriptBot 빈을 생성한다. 이때 앞서 설명한 KoNal 챗봇 스크립트 디렉토리 위치를 지정한다
  • restTemplate(), httpEntity() SKP Deveoper API를 호출하기 위한 빈을 등록한다.

Rest 컨트롤러 만들기

CallBack 서버의sendTextMessage(..) 함수는 사용자가 입력한 메시지를 전달받아  bot에 전달한다. bot은 정의된 규칙에 매칭되는 응답을 리턴한다.

1
2
3
4
5
6
7
8
    @ResponseBody
    String listen(@RequestParam("message")String message) {
		System.out.println("message=" + message);
		JiveScriptReplyBuilder reply = bot.reply("송준이", message);
		String weatherResult = ""; 
		if ( bot instanceof JiveScriptExtBot ) {
			JiveScriptExtBot extBot = (JiveScriptExtBot)bot;
			reply.getReplyAsText();

만약 지역정보가 없다면(extDomain.isEmpty()), 봇의 응답을 그대로 리턴한다. "안녕", "감사합니다"와 같은 경우다. 만약 지역 정보가 있다면, SKP Deveoper API를 호출하여(restTemplate.exchange(..)) 날씨정보를 조회한 후, 날씨 메시지를 생성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
			if ( extDomain.isEmpty() ) {
				return reply.getReplyAsText();
			}
			try {
				String sido = (extDomain.hasProp("sido") ) ? extDomain.getProp("sido") : "";
				String sigungu = (extDomain.hasProp("sigungu") ) ? extDomain.getProp("sigungu") : "";
				String town = (extDomain.hasProp("town") ) ? extDomain.getProp("town") : "";
				KonalDistrict standardDistrcit = disctrictRepository.getStandardDistrcit(sido, sigungu, town);
				System.out.println("standardDistrcit=" + standardDistrcit);
				ResponseEntity<String>  responseEntity = restTemplate.exchange("http://apis.skplanetx.com/weather/current/hourly?lon=&village=" + standardDistrcit.getTown() + "&county=" + standardDistrcit.getSigungu() + "&lat=&city=" + standardDistrcit.getSido() + "&version=1", HttpMethod.GET, httpEntity, String.class);
				String responseBody = responseEntity.getBody();
...
// json parsing
...
					weatherResult += "\n";
					weatherResult += sky + ", 현재 온도는 " + tempCurr + "(최저: " + tempMin + ", 최고: " + tempMax + "), 습도는 " + humidity + "%입니다.";

지역정보 Parsing하기

앞서 이야기했듯이, 날씨 Back 데이터 API를 호출하려면, 사용자가 입력한 지역명, 예를 들어 "서울 강남구 도곡동"을 city, country, village로 토큰화할 수 있어야 한다.  KoNal 챗봇 스크립트를 다시 한번 보자.

1
2
3
4
5
6
7
8
9
10
/**
 *	시도 시군구 읍면동
 */
+ [*] (@sido) [*] (@sigungu) (@town) [@weather] [@show] [*]
- 오늘 <star1> <star2> <star3> 날씨입니다|sido=<star1>/sigungu=<star2>/town=<star3>
/**
 *	시도 시군구
 */
+ [*] (@sido) [*] (@sigungu) [@weather] [@show] [*]
- 오늘 <star1> <star2> 날씨입니다|sido=<star1>/sigungu=<star2>/town=

응답 메시지 뒷 부분에 "|"를 기준으로 지역 정보를 parsing하는 구문이 추가되어 있다. 첫 번째 규칙은, 시도/시군구/읍면동이 모두 있는 경우이며, 두 번째 규칙은 시도/시군구만 있는 경우다. RiveScript와 마찬가지로, JiveScript는 <star> 구문을 지원하며, 사용자가 입력한 텍스트를 <star> 변수에 할당할 수 있다.

이 <star> 변수를 이용하여 "|" 뒤에 Back 데이터 조회에 필요한 변수를 정의하고 값을 할당한다. 이 경우에는 sido(시도), sigungu(시군구), town(읍면동)이다. 이러한 외부 데이터 연동을 위한 구문은 RiveScript를 확장한 구문이며, jivesript-korean-ext 패키지에 구현되어 있다. 이러한 확장 기능을 사용하여 도메인 변수를 설정한 경우, 아래와 같이 getDomainEntity() 메서드 호출을 통해 값을 가져올 수 있다.

1
2
3
4
5
6
			JiveExtDomainEntity extDomain = ((JiveScriptExtReplyBuilder)reply).getDomainEntity();
			...
			try {
				String sido = (extDomain.hasProp("sido") ) ? extDomain.getProp("sido") : "";
				String sigungu = (extDomain.hasProp("sigungu") ) ? extDomain.getProp("sigungu") : "";
				String town = (extDomain.hasProp("town") ) ? extDomain.getProp("town") : "";

앞으로

기사에서 이야기하는 것처럼 챗봇이 메신저 점유율에 큰 영향을 줄 수 있을지, 그리고 사용자가 접근할 수 있는 주요 인터페이스가 될 지는 알 수 없습니다. 하지만 관심을 갖고 지켜보면 재미있는 일들이 벌어질 수도 있을 듯 합니다.

참고자료


Popit은 페이스북 댓글만 사용하고 있습니다. 페이스북 로그인 후 글을 보시면 댓글이 나타납니다.