Watson으로 쉽게 만드는 카카오톡 ChatBot

이 포스팅은 2. 내가 만든 채팅 서비스를 어플리케이션으로 노출하기에 이어지는 포스팅입니다. 이 단계에서는 회의실 예약을 위한 Backend 서비스를 올리고 대화를 통해 예약하기까지의 단계를 진행합니다.

< 이 포스팅과 연결된 글 목록 >

3. 대화 내용으로 회의실 예약하기

2.1 Simple-RBS 서비스 디플로이 하기

이 포스팅에서는 이미 만들어져 있는 회의실 예약 시스템을 각자의 블루믹스에 디플로이 하여 사용할 예정입니다.

1)Simple-RBS의 github에서 Deploy to Bluemix 버튼을 클릭합니다.

deployRBS01

2)로그인 버튼을 눌러 로그인합니다.

deployRBS02

3) 회의실 예약 시스템이 사용할 호스트네임을 입력하고 Bluemix의 Region, Organization, Space를 설정합니다. 호스트네임은 블루믹스 내에서 유일한 값이어야 합니다.

deployRBS03

4) Deploy가 완료된 후 블루믹스의 대쉬보드를 보면 어플리케이션이 생성되어 있습니다. 어플리케이션 주소를 누르면 어플리케이션으로 이동합니다.

deployRBS04

5) 어플리케이션에서 회의실 예약 시스템에서 사용할 수 있는 API목록을 확인하고 API를 테스트할 수 있습니다.

deployRBS05

2.2 Simple-RBS 시스템에 사이트, 미팅룸 생성하기

위 단계에서 생성한 어플리케이션에 사이트와 미팅룸을 생성합니다.

1) 먼저 Site API에 있는 POST /site 노드를 클릭합니다.

Parameters 섹션을 보면 좌측에 Value를 입력할 수 있고 우측에 Example Value가 있습니다.

2) Example Value 박스를 클릭하면 Value 입력 창에 자동으로 Example Value가 입력됩니다.

3) JSON Object의 siteid 값을 camomile로 수정합니다.

4) Try it out! 버튼을 클릭하면 API가 호출됩니다.

deployRBS06

Try it out! 버튼을 클릭한 후에 아래처럼 response 코드가 200으로 나타나면 정상적으로 처리된 것 입니다.

deployRBS07

마찮가지로 사이트 내에 위치할 회의실을 생성합니다.

5) Room API에 있는 POST /room 노드를 클릭합니다.

6) 마찮가지로 Example Value를 클릭합니다.

7) siteid 값을 위에서 생성한 값(camomile)으로 수정합니다.

8) roomid 값을 room1/camomile 으로 수정합니다.

9) Try it out! 버튼을 클릭하여 API를 호출합니다.

deployRBS08

Try it out! 버튼을 클릭한 후에 아래처럼 response 코드가 200으로 나타나면 정상적으로 처리된 것 입니다.

deployRBS09

이 단계에서 작업한 내용을 확인하는 방법은 아래와 같습니다.

  • 사이트 생성을 테스트 하려면 Site API에 있는 GET /site/list를 클릭하고 Try it out!을 클릭합니다.
  • 룸 생성을 테스트 하려면 Site API에 있는 GET /site/room을 클릭하고 Parameters 섹션의 siteid에 사이트 아이디(camomile)를 입력합니다. 마찮가지로 Try it out!을 클릭합니다.

2.3 ChatBot에게 예약 요청하면 시스템에 예약 생성하기

이 단계에서는 코드를 수정하여 대화 서비스 – 어플리케이션 – 예약시스템을 모두 연동하여 하나의 플로우를 완성합니다.

2. 내가 만든 채팅 서비스를 어플리케이션으로 노출하기 튜토리얼에서 작성한 코드를 수정합니다. Tutorial 2를 완료하지 못하신 분은 Tutorial3 Start Code Branch를 Checkout하여 진행하실 수 있습니다. 코드를 새로 Clone하는 경우 npm install 명령을 다시 수행하는 것을 잊지 마십시오.

git checkout tutorial3
npm install

2.3.1 환경변수 추가하기

먼저 위에서 deploy한 Simple RBS의 REST 서비스 url을 저장합니다. REST Service Url은 https://{rbs-project-name+bluemix-domain}/api/smr/v1 입니다. 예를들면 아래와 같습니다.

http://simple-rbs-camomile-project.eu-gb.mybluemix.net/api/smr/v1

이 URL을 .env 파일에 저장하여 환경변수로 사용하겠습니다.

# RBS(Room Reservation Service) URL
RBS_URL=http://simple-rbs-camomile-project.eu-gb.mybluemix.net/api/smr/v1

2.3.2 어플리케이션 수정하기

이번에는 어플리케이션이 Simple RBS를 호출하는 코드를 추가하겠습니다.

1) 필요한 모듈 import 하기

/api/message.js 상단에 다음 코드를 추가합니다.

const request = require('request');
const moment = require('moment');

2) checkAvailability() 함수에 로직 추가하기
checkAvailability() 함수에 아래 로직을 추가합니다. checkAvailability() 함수는 특정 시간에 사용 가능한 room을 검색하기 위한 함수입니다. /freebusy/available api를 사용하면 room을 특정 짓지 않고 사용 가능한 방을 받을 수 있지만 이 튜토리얼에서는 방이 1개 뿐이기에 /freebusy/room api를 사용했습니다.

let checkAvailability = (data, action) => {

  // Context로부터 필요한 값을 추출합니다.
  let date = action.dates;
  let startTime = action.times[0].value;
  let endTime = action.times[1]?action.times[1].value:undefined;

  // 날짜 값과 시간 값을 조합하여 시작 시간과 종료 시간을 Timestamp 형태로 변환합니다. 편의를 위해 종료 시간이 따로 명시되지 않는 경우 시작 시간에서 1시간 후로 설정하도록 합니다.
  let startTimestamp = new moment(date+"T"+startTime+"+0900");
  let endTimestamp = new moment(startTimestamp).hours(startTimestamp.hours() + 1);
  if(endTime){
    endTimestamp = new moment(date+" "+endTime);
  }
  
  // roomid는 편의상 하드코딩 합니다.
  let roomid = 'room1/camomile';

  // /freebusy/room은 roomid, start, end 값을 query parameter로 받아 해당 룸의 가용성을 리턴하는 api입니다.
  let reqOption = {
    method : 'GET',
    url : process.env.RBS_URL + '/freebusy/room',
    headers : {
      'Accept': 'application/json',
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    qs : {
      'roomid' : roomid,
      'start' : startTimestamp.valueOf(),
      'end' : endTimestamp.valueOf()
    }
  };
  
  return new Promise((resolved, rejected) => {
    request(reqOption, (err, res, body) => {
      if(err){
        rejected(err);
      }
      body = JSON.parse(body);


      // body.freebusy 의 length가 0보다 크면 기존에 예약정보가 있다는 의미로 해당 시간에 룸이 이미 예약되어 있음을 의미합니다. 그게 아니라면 해당 룸은 사용 가능한 상태입니다.
      if(body.freebusy && body.freebusy.length > 0){
        data.output.text = "Rooms are not available at the requested time. Please try again."
      }
      else{
        data.output.text = roomid + " is available. Would you confirm this reservation?"
      }

      resolved(data);
    })
  });
}

3) confirmReservation() 함수에 로직 추가하기
confirmReservation() 함수에 아래 로직을 추가합니다. confirmReservation() 함수는 사용자로부터 confirm을 받고 실제로 room을 예약 하는 기능을 합니다.

let confirmReservation = (data, action) =>{

  // context에서 필요한 값을 추출합니다.
  let date = action.dates;
  let startTime = action.times[0].value;
  let endTime = action.times[1]?action.times[1].value:undefined;

  // user 정보는 action 정보에 담겨있지 않으므로 data에서 추출합니다.
  let user = data.context.user;

  let startTimestamp = new moment(date+"T"+startTime+"+0900");
  let endTimestamp = new moment(startTimestamp).hours(startTimestamp.hours() + 1);
  if(endTime){
    endTimestamp = new moment(date+" "+endTime);
  }

  // 편의를 위해 site, room, purpose 및 attendees 정보는 하드코딩되어있습니다.
  let reqOption = {
    method : 'POST',
    url : process.env.RBS_URL + '/book',
    headers : {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    json : {
      "roomid": 'room1/camomile',
      "start" : startTimestamp.valueOf(),
      "end" : endTimestamp.valueOf(),
      "purpose": "quick review",
      "attendees": 5,
      "user" : {
        "userid": user.id
      }
    }
  };

  return new Promise((resolved, rejected) => {
    request(reqOption, (err, res, body) => {
      data.context.action = {};
      if(err){
        data.output.text = "Your reservation is not successful. Please try again."
        resolved(data);
      }
      resolved(data);
    })
  });
}

4) getConversationResponse() 함수 수정하기
위 단계에서 작성한 함수는 http call을 포함하고 있어서 바로 값을 리턴하지 않고 Promise 객체를 리턴하게 되었습니다. 이에 따라 getConversationResponse()함수도 변경이 필요합니다.

let getConversationResponse = (message, context) => {
  let payload = {
    workspace_id: process.env.WORKSPACE_ID,
    context: context || {},
    input: message || {}
  };

  payload = preProcess(payload);

  return new Promise((resolved, rejected) => {
    // Send the input to the conversation service
    conversation.message(payload, function(err, data) {
      if (err) {
        rejected(err);
      }
      else{
        
        let processed = postProcess(data);
        if(processed){
          // return 값이 Promise 일 경우
          if(typeof processed.then === 'function'){
            processed.then(data => {
              resolved(data);
            }).catch(err => {
              rejected(err);
            })
          }
          // return 값이 변경된 data일 경우
          else{
            resolved(processed);
          }
        }
        else{
          // return 값이 없을 경우
          resolved(data);
        }
        
      }
    });
  })
}

2.3.4 Timezone 설정 확인

Watson conversation에 timezone을 설정하는 방법은 context에 timezone 값을 저장하는 것입니다.
설정할 수 있는 Timezone 값은 링크를 참조하십시오.

이 예제에서는 클라이언트 사이드에서 Timezone을 설정했습니다.
프로젝트의 root 디렉토리 밑에 있는 /public/js/api.js를 열어 sendRequest 함수를 확인하면 아래와 같이 타임존을 강제로 설정하고 있습니다.

payloadToWatson.context.timezone = "Asia/Seoul";

2.3.5 로컬에서 테스트 하기

위에서 작성한 코드를 로컬에서 테스트 합니다. Command 창을 열어 프로젝트 폴더로 이동합니다.

request 모듈을 설치합니다.

npm install request --save

아래 명령을 통해 어플리케이션을 시작시킵니다.

npm start

아래처럼 대화를 통해 예약을 진행할 수 있습니다.

test_local

이어지는 포스팅은

bluemix/watsonservice/2017/02/19/watsonchatbot-4-watson-conversation/”>Watson으로 쉽게 만드는 카카오톡 ChatBot 4. Node.js 어플리케이션 카카오톡과 연동하기

입니다.