2018년 4월 20일 금요일

네이버 톡톡과 nta 모듈

네이버 톡톡과 nta 모듈

네이버톡톡 챗봇 쉽게 만들기


내용에 앞서 TMI(Too Much Information)를 싫어하시는 분들을 위해서 예상독자를 적어봅니다.
  • 네이버 톡톡에 챗봇을 구현해보시려는 분.
  • 또 nta 오픈소스에 관심이 있는 분.
  • 챗봇 개발 구조를 알고 싶으신 분.

이 글은 네이버 톡톡 뿐만 아니라 다른 플랫폼( 페이스북 메신저, 텔레그램, 라인 등등 )을 이용한 챗봇 개발에도 도움이 될거에요. 열심히 작성했는데 부족한 점이 있다면 피드백 주세요!


대화 시스템이라는 것은 기본적으로 사용자와 챗봇의 정보 교환입니다.
사용자와 서버는 네이버톡톡, 카카오톡, 페이스북 메신저 등과 같은 플랫폼을 통해서 정보교환을 합니다. 엄밀하게 따지면 챗봇은 사용자가 아니라 플랫폼과 대화를 하고 있다고 볼 수도 있겠죠.
소개해드릴 nta모듈은 네이버 톡톡이라는 플랫폼과 내가 만든 챗봇이 쉽게 대화할 수 있도록 도와줍니다.

[글의 순서]
1. 소개
2. 단도직입 nta 사용법
3. 네이버 톡톡이란?
4. nta의 해결방법
5. nta의 확장성

1. 소개

nta란?
네이버 톡톡의 웹훅 서버를 파이썬으로 구축하고자 하는 개발자를 위한 모듈입니다.
파이썬 서버 구축에 naver_talk_sdk 모듈을 적용하면 시나리오(비즈니스 로직) 구현에 집중할 수 있습니다.
nta 모듈은 네이버 톡톡 플랫폼과 서버가 대화를 할 때 필요한 코드들을 전부 담고 있습니다.
(전부의 기준은 네이버 톡톡 github 공식 페이지의 문서입니다.)
더 나아가 명령 분배 디자인 패턴을 미리 구축해줍니다.
자세한 내용이 궁금하시면 글을 끝까지 읽어주세요 ㅎㅎ
+ nta는 오픈소스를 사용했습니다. 
requests 모듈을 직접적으로 사용하고 있습니다.
fbmq의 아이디어를 참고했습니다.
line-bot-sdk의 구조를 참고했습니다.

2. 단도직입 nta 사용법

nta의 기본적인 사용 방법에 대해 알려드리겠습니다.
웹 서버는 flask를 활용하겠습니다.
더 자세한 사용 방법은 nta CookBook을 따로 만들 생각입니다.
여기서는 간단하게 echo봇에 대해 다루겠습니다.
(echo봇은 사용자가 하는 말을 그대로 따라 하는 챗봇입니다.)

0) 전체 코드 
from flask import Flask, request
from nta import NaverTalkApi

app = Flask(__name__)
ntalk = NaverTalkApi("SECRET_ACCESS_TOKEN")

@app.route('/', methods=['POST'])
def enterance():
    req = request.get_data(as_text=True)
    ntalk.webhook_handler(req)

@ntalk.handle_send
def send_handler(event):
    user_id = event.user_id
    text = evetn.text
    ntalk.send( user_id, text )
이 코드를 서버에 올리면 에코봇은 완성입니다.
자세히 알아볼까요?

1) 설치합니다. (requests 모듈이 없다면 먼저 설치합니다.)
pip install requests
pip install nta
pip install nta --upgrade
2) 모듈 import
nta에서 NaverTalkApi class를 임포트 합니다.
from nta import NaverTalkApi
3) 객체에 SECRET_ACCESS_TOKEN으로 정체성을 부여합니다.
Secret Access Token은 네이버 파트너 센터에서 받아옵니다.
ntalk = NaverTalkApi("SECRET_ACCESS_TOKEN")
4) 네이버 톡톡에서 post로 받은 request body를 ntalk에게 알려줍니다.
flask 활용을 기준으로 넘겨주겠습니다.
req = flask.request.get_data(as_text=True)
ntalk.webhook_handler(req)
5) handler를 등록합니다.
nta 모듈을 제대로 사용하기 위해서는 handler를 이해하는 것이 중요합니다!
handler 등록이란 네이버 톡톡으로부터 이벤트가 발생했을 때 작동할 함수를 등록하는 것입니다. 예를 들면 사용자가 채팅방을 방문하면 open 이벤트가 발생하는데 그때 처리할 함수를 handler 함수에 등록하는 방식입니다. 이와 같은 원리로 사용자가 메시지를 보내면 send 이벤트가 발생하는데 그때 처리할 함수를 handler 함수로 등록해주면 됩니다. 이는 파이썬의 데코레이터 기능을 응용하여 해결했습니다.
(이 부분은 FBMQ 오픈소스 프로젝트를 참고했습니다. 감사합니다 conbus!)
더 자세한 설명은 목록중 4번 nta 해결방법 event처리를 참고해주세요.

여기서 우리는 echo봇을 만드는 것이 목적이기 때문에 사용자에게 메시지를 받을 때만 처리합니다. 즉 send handler만 등록해주면 됩니다. 등록해주는 방식은 아래와 같습니다.
@ntalk.handle_send
def send_handler(event):
    user_id = event.user_id
    text = event.text
handler로 등록된 함수를 보면 event를 파라미터로 받는 걸 볼 수 있습니다. (함수 등록시 반드시 포함해야함) event 파라미터는 이벤트에 대한 중요한 정보들을 담고 있습니다.
예를 들면 send 이벤트가 발생했을 때 중요한 정보는 
  • 누가 보냈는지
  • 어떤 말을 했는지
이 두 가지가 중요합니다. 이 정보를 event 파라미터의 attribute로 접근할 수 있습니다.
따라서 event.user_id에는 유저의 정보가 들어있고 event.text에는 유저가 보낸 말이 들어있습니다.
그럼 추출한 text를 어떻게 유저에게 되돌려 줄까요?

5) 유저에게 메시지 전송하기
nta는 유저에게 메시지를 전송하는 방법이 간단합니다.
챗봇이 보낸다. 유저에게. 메시지를.
ntalk.send( user_id, "보내고 싶은 말을 보낸다." )
이렇게 세 가지 순서로 기억하시면 됩니다.

3. 네이버 톡톡이란?

nta 모듈이 어떻게 네이버 톡톡이랑 대화를 쉽게 도와주나..?
이를 설명하기 위해서는 네이버 톡톡의 구조를 먼저 알아야 합니다.
참고로 다른 플랫폼도 네이버 톡톡과 비슷한 구조입니다.
(보내기 API가 없는 카카오톡은 제외)

아마 네이버 톡톡이 생소하신 분들이 있을 텐데 짧게 소개하겠습니다.
네이버 톡톡은 사용자대 사용자 플랫폼이 아니라 주로 스토어 대 사용자 플랫폼입니다.
즉, 네이버 스토어 관리자와 손님이 사용하는 채팅 플랫폼이죠.
이것만 제외하면 카카오톡이나 페이스북 메신저와 다를 것이 없습니다.
이 네이버 톡톡이 아마 작년(?)부터 챗봇을 도입할 수 있는 기능을 열었습니다.
(언제부터 시작됐는지 정확한 날짜는 잘 모르겠네요;;)
네이버 스토어들은 이제 시간 제약과 인력의 한계에서 벗어나서 사용자를 응대할 수 있게 됐습니다.
소개는 끝! 이제 본래 목적인 구조에 대해서 알아보겠습니다.
더 자세한 건 네이버톡톡 파트너센터와 공식 github 페이지에서 확인하세요.

<네이버 톡톡 챗봇 구조>

본격적으로 네이버 톡톡을 이용한 사용자와 챗봇이 어떻게 대화를 하는지 구조에 대해서 알아보겠습니다.

결론부터 이야기하자면 챗봇은 유저가 아닌 네이버 톡톡과 대화를 하고 있습니다. 따라서 챗봇 개발은 유저와의 대화가 아니라 플랫폼과의 정보 교환에 초점이 맞춰져야 합니다. (물론 서비스 자체는 유저에게 포커싱을 해야겠죠 ㅎㅎ) 그러니 개발에서는 챗봇이 위 그림에서 빨간색 Event를 어떻게 처리할 것인지, Payload를 어떻게 보낼 것인지를 고민해야합니다. (파란색 부분은 플랫폼이 해줍니다.) 중요한 것이 두가지죠. Event와 Payload.

1) Event의 처리
Event의 종류는 여러가지입니다. Open 이벤트도 있고 Send 이벤트도 있고 Profile 이벤트도 있고 ...
당연히 각각의 이벤트들은 서로 다른 목적으로 발생합니다. 따라서 각기 다른 정보를 담고 있습니다. (참고로 이벤트와 정보는 json 값으로 넘어옵니다.) 챗봇 개발을 해보셨다면 아시겠지만 각각 이벤트에 대해 챗봇은 다른 반응을 보여야 합니다. 그러니 이벤트에 따라 로직을 분기하는 것이 핵심입니다. 
요약하면 Event 처리는 아래 두 가지가 중요하겠네요.
  • 이벤트에 따른 각기 다른 정보를 활용해야 한다.
  • 이벤트에 따라 챗봇이 처리하는 방식이 다르다.

2)  Payload의 처리
Payload의 종류도 여러가지 입니다. 챗봇이 네이버 톡톡에게 요구사항을 보내는 거죠. 
유저의 프로필을 달라. 사용자에게 메세지를 보내라. 이미지를 보내라. 고정메뉴를 등록해라 등등
이 값은 json 값으로 보내집니다. 네이버 톡톡 보내기 API에 post를 쏴야합니다. 때때로 보내기 API는 에러 메세지를 반환합니다. 에러 메세지에 적절한 반응을 또 해야합니다. 이 에러 메세지를 챗봇은 로그로 남겨야 합니다. 심지어 status 200과 함께 반환되는 에러메세지도 있습니다. 
요약하면
  • Payload 하나 보내는데 해야할 일이 많다.
그럼 nta가 Event와 Payload의 처리를 어떻게 해결했을까요?

4. nta의 해결방법

이 둘을 해결하기 위해 nta모듈은 디자인 패턴을 적극적으로 도입했습니다.
결론적으로 nta모듈에서 Event 처리는 명령 분배 디자인 패턴을 적용했습니다. 
Payload 처리는 퍼사드 패턴을 적용했습니다.
아래에서는 패턴에 대한 간단한 소개(what)와 함께 why와 how를 설명하겠습니다.

1) Event의 처리

What) 명령 분배 디자인 패턴(Command Dispatcher Pattern)
명령 분배 패턴은 자세히 설명한 한글 문서는 찾을 수 없었습니다.
대신 이 논문을 참고했습니다.
명령 분배 디자인 패턴은 요청을 캡슐화하고 각기 다른 요청에 대한 명령을 실행하는 패턴입니다. 더 간단하게 말하면, 요청이 들어오면 알아서 그 요청에 해당하는 명령을 실행하는 패턴입니다. 흔히 알려진 Command 패턴과는 구분됩니다. 

Why) 왜 명령 분배 디자인 패턴인가?
챗봇은 플랫폼에서 요청이 들어오면 해당 요청에 대한 명령을 수행합니다.
이는 명령 분배 패턴이 해결하려는 문제와 부합합니다. 
또 명령 분배 패턴은 커맨드 패턴에 비해 handler의 등록에 있어서 더 유연합니다. 챗봇은 시나리오 구현이 기획에 의해 계속 변동될 수 있기 때문에 handler의 등록이 유연해야합니다.
위에서 Event를 처리할 때 두가지가 중요하다고 했었죠. 
그 두가지는 각각 명령 분배 패턴에 적용될 수 있습니다.
  • 이벤트에 따른 각기 다른 정보를 활용해야 한다. => 이벤트를 객체로 캡슐화 한다.
  • 이벤트에 따라 챗봇이 처리하는 방식이 다르다.  => 이벤트에 따른 함수를 호출한다.

How) 어떻게 적용했는가?
네이버 톡톡을 명령 분배 디자인 패턴으로 해결하면 아래와 같은 흐름을 그릴 수 있습니다.
nta 모듈 사용자는 빨간색 테두리가 그려진 handler만 신경쓰면 됩니다.



handler는 아래와 같이 등록합니다.
send (사용자가 메세지를 보낸 경우) 와 open (사용자가 채팅방에 입장한 경우)를 예로 보여드리겠습니다.
@ntalk.handle_send
def send_handler(event):
    # 사용자가 메세지를 보냈을 때 비즈니스 로직을 구현합니다.

@ntalk.handle_open
def open_handler(event):
    # 사용자가 채팅방에 들어왔을 때 비즈니스 로직을 구현합니다.

내부적으로 어떻게 등록되는지 볼게요.
데코레이터로 등록하면 아래의 코드에 의해서 각각의 함수가 등록됩니다. (파이썬은 함수가 일급 객체니까요!)
이렇게 handler 함수 등록은 끝납니다.
요약: 다루고자 하는 이벤트가 있으면 handle_이벤트 데코레이터로 등록 해준다.
코드로 이동


이제 어떻게 dispatch를 해주는지 보겠습니다.
아래는 dispatch를 해주는 부분의 코드입니다.
단순합니다. event가 발생하면 등록된 event를 찾아서 실행한다.
만약에 모듈 사용자가 handler를 등록하지 않았다면 log 메세지를 찍습니다.

2) Payload의 처리

What) 퍼사드 패턴(Facade Pattern)
Wiki 설명 중.
What problems can the Facade design pattern solve? [2]
To make a complex subsystem easier to use, a simple interface should be provided for a set of interfaces in the subsystem.
The dependencies on a subsystem should be minimized.
퍼사드 디자인 패턴에 대해서는 이 블로그를 참고해주세요. 정리가 잘 돼있네요 :)
간단하게 말해서 퍼사드 패턴은 이것저것 복잡한 서브 시스템의 작동을 클라이언트에서 인터페이스 하나로 해결하는 패턴입니다. 

Why) 왜 퍼사드 패턴인가?
위에서 Payload를 하나 보내는데 해야할 일이 많았죠? 
나열해볼게요.
  • 네이버 톡톡이 원하는 json형식에 맞춰서 Payload를 만들어야하고.
  • header에 Token도 넣어야하고
  • post를 보내야합니다. 
  • 보내가 난 다음 반환값의 에레도 체크 해줘야합니다.
이런 반복되는 서브 프로세스를 매번 클라이언트 코드에서 payload를  전송할 때마다 적어줄 수는 없죠.
퍼사드 패턴을 적용하면 반복되는 복잡한 서브프로세스를 한줄로 사용할 수 있습니다.

How) 어떻게 적용했는가?
"사용자에게 메세지를 보내는 요청"을 하는 Payload로 예를 들어 설명하겠습니다.
nta에서는 이 Payload를 GenericPayload라고 했습니다. GenericPayload를 보내기 위해서 client 코드에서는 send 메소드를 사용하면 됩니다. 
ntalk.send(user_id, text)
이 한줄이 위에서 나열한 Payload를 보낼때 해야하는 일들을 해줍니다.
 클라이언트는 단순히 ntalk의 send만 알고 있으면 됩니다. 참 쉽죠?
아래에서는 서브 시스템들을 어떻게 동작시키는지 구체적으로 뜯어볼게요.
코드로 이동

nta/api.py NaverTalkApi
먼저 버전 체크를 합니다.
nta는 파이썬 2.7을 지원하기 위해서 unicode를 체크합니다. 한글 깨짐을 방지하기 위함입니다.
그리고 필요한 정보들을 GenericPayload 객체에 담아 초기화합니다. 
그리고 _send 메소드에 payload를 넘겨 호출합니다.

코드로 이동

nta/api.py NaverTalkApi

_send 메소드를 굳이 분기한 이유는 payload는 달라도 동작하는 서브 프로세스들이 같기 때문입니다. 지금은 GenericPayload를 예로 설명하고 있지만 ProfilePayload, ActionPayload 등 페이로드의 종류는 여가지입니다. 그래서 한 단계 더 나눴습니다. 동작은 아래 순서대로
  1. 페이로드를 json화 해줍니다. (payload 객체들은 자신의 attribute들을 네이버 톡톡에 맞는 json 형식으로 변환하는 메소드를 가지고 있습니다.) 
  2. post를 보냅니다. 
  3. 에러 메세지를 체크합니다.
  4. callback과 _after_send는 모듈 사용자 재량인데 보내기를 하고 난 후 실행할 함수를 등록하면 작동합니다.

5. nta의 확장성

nta 모듈은 현재 네이버 톡톡 공식 github에 올라온 문서들을 전부 모듈화 했습니다.
nta와는 별개로 네이버 톡톡의기능들은 계속 추가되고 있습니다.(nta가 네이버의 공식 오픈소스는 아닙니다.)
만약 네이버 톡톡이 새로운 Event나 Payload를 추가했는데 nta가 처음부터 리펙토링을 해야한다거나 새로운 기능을 포기해야한다면 사용하고 싶지 않겠죠.. 또 오픈소스에 투자되는 시간이 너무 많아질겁니다. 그럼 개발을 안하게되고.. 오픈소스는 점점 관심을 잃고.. repository 저 밑으로 내려가고..
하지만 nta는 Event와 Payload에 적용된 디자인 패턴 덕분에 확장성 면에서 큰 장점을 보여줍니다.
최근에 추가한 Event와 Payload를 예로 확정성을 설명하겠습니다.
Event는 3스탭, Payload는 2스탭으로 추가할 수 있습니다. (테스트 코드 작성까지 하면 +2 step 정도 생각하시면 됩니다.)

Event의 확장성
HandOver Event(17년 12월 15일 추가된 이벤트입니다.)
스탭은 3가지 입니다.
1) Handover 이벤트 클래스를 만든다.
2) 팩토리 메서드에서 이 이벤트를 만들 수 있도록 분기한다.
팩토리 메서드가 갑자기 튀어나왔는데 nta/api.py를 보시면 WebhookParser가 그 역할을 합니다. 팩토리 메서드란 객체를 생성하는 메소드 입니다. WebhookParser는 이벤트를 객체화하는 역할을 합니다.
3) 이 이벤트에 대한 명령을 등록할 handler를 만든다.

커밋된 코드를 보겠습니다.
1) 이벤트 클래스를 만든다.


2) 팩터리 메서드에 새로운 이벤트를 분기한다.


3) 이 이벤트에 대한 명령을 등록할 handler  decorator를 만든다.



사실 nta/models/__init__.py에 HandOverEvent를 등록 해야줘야합니다. + 0.2 스탭정도 되겠네요.
이렇게 하면 기능적으로 nta 모듈에서 새로운 이벤트를 사용할 수 있게됩니다.
nta 오픈 소스에 contribute 하시려면 test 코드까지 작성하여 통과하시면 됩니다.

Payload의 확장성

Persistent menu 등록입니다.
네이버 톡톡에 고정메뉴를 등록해달라고 하는 Payload 입니다.
두가지 스텝입니다.
1) PersistentMenu Payload 클래스를 만든다.
2) Persistent Menu Payload를 보낼 수 있는 메소드를 만든다.

커밋된 코드를 보겠습니다.
1) PersistentMenu Payload 클래스를 만든다.

2) Persistent Menu Payload를 보낼 수 있는 메소드를 만든다.


역시나 +0.2 스탭으로 nta/models/__init__.py에 PersistentMenuPayload를 등록해야합니다. 기능적으로 ntalk.persistent_menu 를 활용하면 네이버 톡톡에 고정메뉴를 등록할 수 있습니다. contriubutor는 test 코드까지 작성하면 됩니다.

나름 열심히 글을 적어 봤는데 잘 이해가 되실지 모르겠네요.
또 아직 모자란 부분이 많습니다. 조언도 질문도 모두 환영합니다. 아래 적힌 메일이나 페이스북 메신저로 자유롭게 이야기해주세요 :)
황원요
hollal0726@gmail.com

댓글 1개:

  1. 안녕하세요. 자기개발 목적으로 간단하게 Python 기반 챗봇을 구현하려다가 방문하게 되었습니다.

    네이버 파트너센터에 채널 등록까지 마쳤고, 플라스크로 간단하게 로컬과 테스트 해보려고 했더니, https로 서비스를 가동해야 하는 것과 ssl 인증서 때문에 막혔습니다.

    뭣도 모르고 openssl 설치해서 어떻게 해보려고 했지만, 결국 사설 인증이라 안되는 것 같고, 도메인과 공인 ssl 구매해서 진행해야만 하나요?

    제가 묻고도 어쩌면 당연한 소리 같습니다만, 뭣하나 해볼 수 없는 사실에 노이로제가 걸릴 것만 같아요.

    로컬에서 가동한 플라스크 웹서버와 간단하게 연동되는 모습을 볼 수는 없을까요?

    답글삭제

네이버 톡톡과 nta 모듈

네이버 톡톡과 nta 모듈 네이버톡톡 챗봇 쉽게 만들기 내용에 앞서 TMI(Too Much Information)를 싫어하시는 분들을 위해서  예상독자 를 적어봅니다. 네이버 톡톡에 챗봇을 구현해보시려는 분. 또 nta 오픈소...