Watson์œผ๋กœ ์‰ฝ๊ฒŒ ๋งŒ๋“œ๋Š” ์นด์นด์˜คํ†ก ChatBot

์ด ํฌ์ŠคํŒ…์€ 4. Node.js ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์นด์นด์˜คํ†ก๊ณผ ์—ฐ๋™ํ•˜๊ธฐ์— ์ด์–ด์ง€๋Š” ํฌ์ŠคํŒ…์ž…๋‹ˆ๋‹ค. ์ด ํฌ์ŠคํŒ…์—์„œ๋Š” ์˜ˆ์•ฝํ•œ ํšŒ์˜์‹ค ์ทจ์†Œ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•จ์œผ๋กœ์จ ๊ทธ๋™์•ˆ ํ–ˆ๋˜ ๋‚ด์šฉ์„ ๋ชจ๋‘ ๋ณต์Šต ๋ฐ ์‘์šฉํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

< ์ด ํฌ์ŠคํŒ…๊ณผ ์—ฐ๊ฒฐ๋œ ๊ธ€ ๋ชฉ๋ก >

5. [์‘์šฉ] ํšŒ์˜์‹ค ์˜ˆ์•ฝ ์กฐํšŒ ๋ฐ ์ทจ์†Œ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ํ•˜๊ธฐ

5.1 Watson Conversation ๋Œ€ํ™” ์„œ๋น„์Šค ๋งŒ๋“ค๊ธฐ

Watson Conversation์˜ ๋Œ€ํ™” ์„œ๋น„์Šค๋Š” ํฌ๊ฒŒ Intent, Entity ๊ทธ๋ฆฌ๊ณ  Dialog๋ฅผ ์ƒ์„ฑํ•จ์œผ๋กœ์จ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ 1. Watson Conversation ์„œ๋น„์Šค๋กœ ๋Œ€ํ™” ์„œ๋น„์Šค ๋งŒ๋“ค๊ธฐ ์™€ Watson Conversation์—์„œ ์‚ฌ์šฉํ•˜๋Š” Expression Language๋ฅผ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค. ์ด ํฌ์ŠคํŒ…์€ Tutorial 1์—์„œ ๋งŒ๋“  ์„œ๋น„์Šค์— ์ƒˆ๋กœ์šด ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. Tutorial 1์„ ์™„๋ฃŒํ•˜์ง€ ๋ชปํ•œ ๋ถ„์€ workspace์— ๋งํฌ์—์„œ workspace-tutorial1.json์„ ๋‹ค์šด๋กœ๋“œ ํ•˜์—ฌ import ํ•˜์‹  ํ›„์— ์ง„ํ–‰ํ•˜์‹ญ์‹œ์˜ค.

๋จผ์ € Watson Conversation์˜ Tool์— ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค. Bluemix์— ๋กœ๊ทธ์ธํ•˜๊ณ  Watson Conversation ์„œ๋น„์Šค๋ฅผ ์ถ”๊ฐ€ํ•œ ์กฐ์ง๊ณผ ์˜์—ญ์˜ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. Conversation ์„œ๋น„์Šค ์ด๋ฆ„์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค. ๊ด€๋ฆฌ ํƒญ์—์„œ Launch Tool ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.

5.1.1 Intent ๋งŒ๋“ค๊ธฐ

3๊ฐœ์˜ Intent๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

1) #Check_Reservation_List

intent1

2) #Cancel_Reservation

intent2

3) #Check_Next_Reservation

intent3

5.2.2 Entity ์ถ”๊ฐ€

Entity ํ™”๋ฉด์˜ System Entities ํƒญ์—์„œ @sys-number ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ™œ์„ฑํ™” ์‹œํ‚ต๋‹ˆ๋‹ค.

entity

5.2.3 Dialog ์ถ”๊ฐ€ํ•˜๊ธฐ

Dialog ํ™”๋ฉด์—์„œ ์•„๋ž˜์™€ ๊ฐ™์ด 3๊ฐœ์˜ ์ตœ์ƒ์œ„ ๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์‹ญ์‹œ์˜ค.

Index Triggered by Response Condition Response Jump to
5 #Check_Reservation_List
{
  "context": {
    "user": {
      "id": "hjjo\\@kr.ibm.com"
    },
    "action": {
      "command": "check-reservation"
    }
  },
  "output": {}
}
6 #Check_Next_Reservation
{
  "context": {
    "user": {
      "id": "hjjo\\@kr.ibm.com"
    },
    "action": {
      "command": "check-next-reservation"
    }
  },
  "output": {}
}
7 #Cancel_Reservation
{
  "context": {
    "user": {
      "id": "hjjo\\@kr.ibm.com"
    },
    "action": {
      "command": "check-reservation-for-cancellation"
    }
  },
  "output": {}
}

#Cancel_Reservation ๋…ธ๋“œ ํ•˜์œ„์— ๋‹ค์Œ์„ ์ฐธ์กฐํ•˜์—ฌ ๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์‹ญ์‹œ์˜ค.

Index Triggered by Response Condition Response Jump to
7-1 @sys-number
{
  "context": {
    "action": {
      "command": "confirm-cancellation"
    },
    "removeIndex": "< ? @sys-number - 1 ?>"
  },
  "output": {
    "text": {
      "values": [
        "Your reservation is successfully cancelled. Thanks !"
      ]
    }
  }
}
7-2 true Please try again :-D

์ด ํŠœํ† ๋ฆฌ์–ผ์„ ๋ชจ๋‘ ์™„๋ฃŒํ•œ workspace๋ฅผ import ํ•˜๋ ค๋ฉด ๋งํฌ์—์„œ workspace-tutorial5.json์„ ๋‹ค์šด๋กœ๋“œ ํ•˜์‹ญ์‹œ์˜ค.

5.2 ํšŒ์˜์‹ค ์˜ˆ์•ฝ ์กฐํšŒ ๋ฐ ์ทจ์†Œ Action ์ถ”๊ฐ€ํ•˜๊ธฐ

์ด ํฌ์ŠคํŒ…์—์„œ๋Š” ๊ธฐ์กด ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค. ์•ž์„  ํŠœํ† ๋ฆฌ์–ผ์„ ์™„๋ฃŒํ•˜์ง€ ๋ชปํ•˜์‹  ๋ถ„์€ tutorial5๋ฅผ checkoutํ•˜์‹ญ์‹œ์˜ค.

git checkout tutorial5

/api/message.js๋ฅผ ์ˆ˜์ •ํ•˜์—ฌ ํšŒ์˜์‹ค ์˜ˆ์•ฝ ์กฐํšŒ ๋ฐ ์ทจ์†Œ Action์„ ์ถ”๊ฐ€ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

1) doAction ํ•จ์ˆ˜์˜ case๋ฌธ์— ๋‹ค์Œ์˜ ์ผ€์ด์Šค๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

let doAction = (data, action) => {
  console.log("Action : " + action.command);
  switch(action.command){
    case "check-availability":
      return checkAvailability(data, action);
      break;
    case "confirm-reservation":
      return confirmReservation(data, action);
      break;
    // ์‚ฌ์šฉ์ž์˜ ์˜ˆ์•ฝ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
    case "check-reservation":
      return checkReservation(data, action);
      break;
    // ์‚ฌ์šฉ์ž์˜ ์˜ˆ์•ฝ ๋ฆฌ์ŠคํŠธ ์ค‘ ๊ฐ€์žฅ ๋น ๋ฅธ ์‹œ๊ฐ„์˜ ์˜ˆ์•ฝ๋งŒ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. 
    case "check-next-reservation":
      return checkNextReservation(data, action);
      break;
    // ์˜ˆ์•ฝ ์ทจ์†Œ์˜ ๋ชฉ์ ์œผ๋กœ ์˜ˆ์•ฝ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
    case "check-reservation-for-cancellation":
      return checkReservation(data, action).then(data => {
        if(Array.isArray(data.output.text)){
          data.output.text.unshift("Please tell me the number of the reservation you want to cancel.");
        }
        return data;
      });
      break;
    // ์˜ˆ์•ฝ์„ ์ทจ์†Œํ•ฉ๋‹ˆ๋‹ค.
    case "confirm-cancellation":
      return confirmCancellation(data, action);
      break;
    default: console.log("Command not supported.")
  }
}

2) checkReservation()ํ•จ์ˆ˜๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌํ˜„ํ•˜์—ฌ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

/** 
 * ์‚ฌ์šฉ์ž์˜ ํšŒ์˜์‹ค ์˜ˆ์•ฝ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ํ•จ์ˆ˜
 * @param  {Object} data : response object
 * @param  {Object} action 
 */ 
let checkReservation = (data, action) => {
  // context์—์„œ ํ•„์š”ํ•œ ๊ฐ’์„ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.
  let date = action.dates;
  let startTime, endTime;
  if(action.times){
    startTime = action.times[0]?action.times[0].value:undefined;
    endTime = action.times[1]?action.times[1].value:undefined;
  }

  // ๋‚ ์งœ ๊ฐ’๊ณผ ์‹œ๊ฐ„ ๊ฐ’์„ ์กฐํ•ฉํ•˜์—ฌ ์‹œ์ž‘ ์‹œ๊ฐ„๊ณผ ์ข…๋ฃŒ ์‹œ๊ฐ„์„ Timestamp ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํŽธ์˜๋ฅผ ์œ„ํ•ด ์ข…๋ฃŒ ์‹œ๊ฐ„์ด ๋”ฐ๋กœ ๋ช…์‹œ๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ์‹œ์ž‘ ์‹œ๊ฐ„์—์„œ 1๊ฐœ์›” ํ›„๋กœ ์„ค์ •ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  let startTimestamp = new moment();
  if(startTime){
    startTimestamp = new moment(date+"T"+startTime+"+0900");
  }
  let endTimestamp = new moment(startTimestamp).month(startTimestamp.month() + 1);
  if(endTime){
    endTimestamp = new moment(date+" "+endTime);
  }

  // /book/search/byuser API๋Š” site id, user id, start time, end time์„ Query parameter๋กœ ๋ฐ›์•„ ํ•ด๋‹น ์‹œ๊ฐ„์— ์‚ฌ์šฉ์ž์˜ ์˜ˆ์•ฝ ๋ฆฌ์ŠคํŠธ๋ฅผ returnํ•ด์ฃผ๋Š” api์ž…๋‹ˆ๋‹ค.
  let reqOption = {
    method : 'GET',
    url : process.env.RBS_URL + '/book/search/byuser',
    headers : {
      'Accept': 'application/json',
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    qs : {
    "siteid" : "camomile",
    "userid" : data.context.user.id,
    "start" : startTimestamp.valueOf(),
    "end" : endTimestamp.valueOf()
    }
  };
  
  return new Promise((resolved, rejected) => {
    request(reqOption, (err, res, body) => {
      data.context.action = {};
      if(err){
        rejected(err);
      }
      console.log(reqOption, body);
      body = JSON.parse(body);
      // body ์˜ length๊ฐ€ 0๋ณด๋‹ค ํฌ๋ฉด ๊ธฐ์กด์— ์˜ˆ์•ฝ์ •๋ณด๊ฐ€ ์žˆ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.
      if(body && body.length > 0){
        let resvs = [];
        let index = 0;
        for(let resv of body){
          //์˜ˆ์•ฝ ๋ชฉ๋ก์„ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๋Š” ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ resvs ๋ณ€์ˆ˜์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
          resvs.push((++index) + ": " + new moment(resv.start).format(config.dateTimeFormat) + " ~ " + new moment(resv.end).format(config.dateTimeFormat) + ", " + resv.roomid + ", " + resv.purpose);
        }
        //์˜ˆ์•ฝ ๋ชฉ๋ก์„ Context์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
        data.context.reservations = body;
        //์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ค„ ์˜ˆ์•ฝ ๋ชฉ๋ก์€ Output์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
        data.output.text = resvs;
      }
      else{
        data.output.text = ["Your reservation is not found."];
      }
      resolved(data);
    })
  });
}

3) checkNextReservation()ํ•จ์ˆ˜๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌํ˜„ํ•˜์—ฌ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

let checkNextReservation = (data, action) => {
  return checkReservation(data, action).then(data => {
    if(data.output.text && Array.isArray(data.output.text)) data.output.text = data.output.text[0];
    return data
  });
}

4) confirmCancellation()ํ•จ์ˆ˜๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌํ˜„ํ•˜์—ฌ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

/** 
 * ํšŒ์˜์‹ค ์ทจ์†Œ
 * @param  {Object} data : response object
 * @param  {Object} action 
 */ 
let confirmCancellation = (data, action) => {
  // user ์ •๋ณด๋Š” action ์ •๋ณด์— ๋‹ด๊ฒจ์žˆ์ง€ ์•Š์œผ๋ฏ€๋กœ data์—์„œ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.
  let user = data.context.user;
  let eventId = data.context.eventid;
  let reservations = data.context.reservations;
  let index = data.context.removeIndex;

  let reqOption = {
    method : 'DELETE',
    url : process.env.RBS_URL + '/book',
    headers : {
      'Accept': 'text/plain',//'application/json',
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    qs : {
      "eventid" : reservations[index].id,
      "userid" : user.id,
      "roomid" : reservations[index].roomid
    }
  };

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

5.3 ํ…Œ์ŠคํŠธ

์•„๋ž˜ ๋ช…๋ น์„ ์‹คํ–‰ํ•˜์—ฌ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹œ์ž‘์‹œํ‚ค๊ณ  localhost:3000/ ์— ์ ‘์†ํ•˜์—ฌ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค.

npm start

test

์ด์–ด์ง€๋Š” ํฌ์ŠคํŒ…์€

Watson์œผ๋กœ ์‰ฝ๊ฒŒ ๋งŒ๋“œ๋Š” ์นด์นด์˜คํ†ก ChatBot 6. [์‹ฌํ™”] ์˜ˆ์•ฝ ์‹œ๊ฐ„์ด ๋˜๋ฉด ChatBot์ด ์•Œ๋ ค์ฃผ๊ธฐ (ํ…”๋ ˆ๊ทธ๋žจ)

์ž…๋‹ˆ๋‹ค.