[Nodejs/Watson] Nodejs에 Watson 챗봇 연동하기 - (2)

2020. 9. 22. 19:26프로젝트/nodejs

안녕하세요, 저번 글 ( wwhurin.tistory.com/19 ) 에 이어서 nodejs에 왓슨을 챗봇을 연동해보겠습니다. (1)에서 nodejs에서 api를 연동하기 위한 초석을 깔았다면, 이젠 웹에서 값을 받아볼 시간입니다. (1)에서도 언급했듯이, 연동 여부와 방법을 간단하게 익히기 위한 취지였으므로 코드가 정갈하지 않습니다. 굉장히 중구난방이죠. (...) 전체를 공개하기엔 아직 부끄러워서, 연동을 위한 핵심 로직들만 공유할 예정입니다. 여러분의 프로젝트에 맞추어 사용해주세요!


IBM Watson 챗봇과 nodejs 연동

연동 마지막 글입니다!


 

1. api 만들기

앞의 글에서 언급했듯이, 이 SDK ( github.com/watson-developer-cloud/node-sdk ) 를 사용할 예정입니다. 따라서 프로젝트 폴더로 가서 cmd 창을 킨 후, 다음과 같은 명령어를 입력해주세요.

npm install ibm-watson

이제 왓슨 챗봇 api로 정보를 요청해 결과를 받아오는 api를 하나 생성해야 합니다. nodejs 프로젝트 샘플 기준 server.js에서 작성된 코드입니다.

 

- 선언

/* ========================================================== */
//watson api
/* ========================================================== */
const AssistantV2 = require('ibm-watson/assistant/v2');
const { IamAuthenticator } = require('ibm-watson/auth');
const assistant = new AssistantV2({
  authenticator: new IamAuthenticator({ apikey: 'your api key' }),
  serviceUrl: 'https://gateway.watsonplatform.net/assistant/api/yoururl',
  version: '2018-09-19'
});
/* ========================================================== */

v2를 사용할 예정입니다. v1은 workspaceId를 필요로 하는데, workspaceId를 찾지 못해서 v2로 성급하게 넘어갔습니다. 지금 서치해보니 바로 나오는 말로는, AssistantId가 동일한 역할을 하는 것 같습니다. v2는 sessionID를 자꾸 요구해서 v1으로 넘어갈 수 있다면 v1으로도 작업 해봐야 겠습니다. 

 

일단 위 코드에서 api key와 api key와 함께 있는 url을 입력해주세요. 

 

- api 작성 

/*

  watson api 

*/

app.post("/api/chatBot", function(request, response){
  var sData = request.body.sData;
  assistant.createSession({
      assistantId: 'your assistantId'
    }).then(res => {
      //console.log(res.result.session_id);
      var sessionId = res.result.session_id;
      assistant.message({
        input: { text: sData },
        assistantId: 'your assistantId',
        sessionId: sessionId,
      })
      .then(assiRes => {
        console.log(JSON.stringify(assiRes.result, null, 2));
        response.send(assiRes.result);
        return;
      })
      .catch(err => {
        console.log(err);
      });
    }).catch(err => {
      console.log(err);
    });  
 });
 

여러분의 assistantId를 입력해주시면 됩니다. sessionId는 새로 만들어진 것을 매번 사용합니다. 이제 /api/chatBot으로 주소를 요청하면 챗봇의 결과를 받을 수 있습니다. 만약 테스트를 해보고 싶으시다면 postman으로 요청을 날려보셔도 좋고, sData에 임의의 값을 넣어서 get으로 요청해서 테스트해봐도 좋습니다. sData는 사용자가 보낸 text일 예정입니다. 

 

생각보다 복잡하고, 더러운 코드입니다. (...) 시간이 날 때 조용히 고치고 이 문장들을 지울지 모르겠지만, 제대로 정리가 안 된 코드인것만은 확실합니다. createSession 처리를 해주면서 복잡해졌는데, sessionId를 처음에 다른 id와 착각해서 넣었더니 당연하게도 에러가 나서 찾은 이슈를 통해 해결하느라 복잡해진 것 같습니다.

github.com/watson-developer-cloud/node-sdk/issues/1009

 

Not Found: Invalid Session · Issue #1009 · watson-developer-cloud/node-sdk

Hello guys, I tryed to use v2 integration but return the error: message: 'Invalid Session', How can i have this session id?

github.com

위 이슈인데 createSession 처리를 해주면 된다길래 따로 선언을 할까 했는데, 일단은 api를 요청할 때마다 create 하도록 했습니다. 따로 사용하시게 된다면 정리하시는걸 추천해드립니다. 

 

요청이 올바르게 보내진다면 cmd창에 다음과 같은 응답이 표시될 겁니다.

- 개선의 경우) 

{
  "output": {
    "intents": [
      {
        "intent": "개선제안",
        "confidence": 1
      }
    ],
    "entities": [],
    "generic": [
      {
        "response_type": "text",
        "text": "정보 제공에 감사드립니다. 개선하실 문장 혹은 단어를 다음과 같이 보내주세요.\n예) Hello/안녕\n종료를 원 할 경우 *종료* 를 입력해주세요."
      }
    ]
  }
}

 

-*종료*의 경우)

{
  "output": {
    "intents": [],
    "entities": [
      {
        "entity": "종료",
        "location": [
          0,
          4
        ],
        "value": "*종료*",
        "confidence": 1
      }
    ],
    "generic": [
      {
        "response_type": "text",
        "text": "감사합니다. 검토 후 반영하도록 하겠습니다. 좋은 하루 되세요 😊"
      }
    ]
  }
}

둘의 차이가 보이시나요? 둘 중 하나만 테스트 해보셔도 좋습니다. 응답이 잘 오는 것 정도로만 일단 확인해주세요. 응답의 형태가 다르게 오는건 하나는 entities로 등록했고, 하나는 intents로 등록했기 때문입니다. 이에 따른 처리는 화면에서 알맞게 할 예정이니 다르구나, 정도만 알아주세요. 

 

 

2. 페이지에서 요청하기

- 버튼 클릭 이벤트

$("#btn_send").click(function(){
	send_watson();
});

 

- text 처리 이벤트

function send_watson(){
//텍스트 창에서 사용자가 입력한 값 받아와서 그려주기
    console.log($("#input_send").val());
    var sData = $("#input_send").val();
    draw_me(sData);
	
    //만약 종료 명령이 오면 반복 입력 종료
    if(sData === "*종료*")
        bRoop = false;
	
    //반복 입력이 false여야지만 api로 요청 보내는 함수 실행
    if(!bRoop)
        get_watson(sData);

    //초기화
    var sData = $("#input_send").val("");
}

버튼을 클릭하면 실행되는 함수입니다. 사용자가 입력한 텍스트를 받아와 화면에 말풍선을 그리고, 텍스트를 초기화 합니다. 중간에 있는 if가 체크하는 bRoop는 true이면 사용자가 개선할 문장을 보내는 중이니 계속해서 사용자가 하도록 처리해주는 분기점입니다. 바로 이렇게요.

사용자가 보내는 메세지가 매번 왓슨 챗봇에게 요청하지 않도록 합니다. bRoop가 false면 사용자가 보내는 메세지가 왓슨 챗봇에게 보내지는 상태로 보시면 될 것 같습니다. 

 

 

- api 요청

function get_watson(sData){
    $.ajax({
        method: "POST",
        url: "./api/chatBot",
        contentType: "application/json",
        data: JSON.stringify({sData: sData })
    })
    .done(function(data) {
        console.log(data);
	
    	//왓슨 응답 중 화면에 print 할 변수
        var sYou = data.output.generic[0].text;
        draw_you(sYou); //화면에 그리기
	
    	//만약 intents 항목이 비어있지 않다면 check_roop로 가서 intent 이름 확인
        //확인 후 "개선제안" 이라면 반복입력 받도록 bRoop=true, 아니라면 bRoop=false
        if(data.output.intents.length > 0){
            var sCkeck = data.output.intents[0].intent;
            check_roop(sCkeck);
        }
    }).catch(err => {
      console.log(err);
    });  
}

이제 api로 요청을 보내는 함수입니다. ajax를 사용하며, 기존에 작성되어있던 요청 함수와 비슷하게 작성해봤습니다. 만약 api 함수를 조금 다르게 만드셨다면 알맞게 수정해주세요. 저는 JSON 형태로 데이터를 보내며 sData에 사용자가 보낸 텍스트가 담겨있습니다. 

 

올바르게 응답이 온다면 형태는 2가지로 나뉩니다. 

 

*generic

이건 중복 항목입니다. 밑에 이제 2가지의 차이점을 설명하기 전에 공통적으로 오는 항목을 먼저 말씀드리려고 합니다. generic은 왓슨의 응답이 담긴 배열로 실제로 사용하게 될 text가 들어있습니다.

 

*intents

(1)의 글에서 등록했던걸 보시면 개선은 intent로 등록되어 있습니다. 따라서 entities는 비어있죠. 개선이 들어오면 사용자의 입력을 계속 받아야 하므로, intents에 값이 있다면 이게 개선제안 intents의 응답인지 확인해야 합니다. (개선제안은 제가 지정한 intent 항목의 이름입니다.)

 

만약 intents에 값이 담겨서 온다면 check_roop에서 개선제안 이라는 이름의 intents가 맞는지 확인하여 맞다면 반복할 수 있게 true로, 아니라면 false로 바꿔줍니다.

 

 

*entities

 

intents와 비교하면 차이가 보이시나요? *종료* 명령을 보냈을 때의 응답입니다. entities에 담겨서 온 걸 확인하실 수 있습니다.

 

 

 

 

 

 

 

- 사용자 입력이 계속될지 여부 확인

function check_roop(sCkeck){
    if(sCkeck === "개선제안")
        bRoop = true;
    else
        bRoop = false;
}

 

- 말풍선 그리기

function draw_me(sMe){ //user
    var s = `<div class="chat-bubble me">${sMe}</div>`
    $(".chat-body").append(s);
}

function draw_you(sYou){ //chatbot
    var s = `<div class="chat-bubble you">${sYou}</div>`
    $(".chat-body").append(s);
}

값을 입력받아 말풍선을 그려주는 함수입니다.

 

이렇게 해서 만들어진 결과물은 아래와 같습니다. 

https://translate-page.us-south.cf.appdomain.cloud/

 

3. CSS 

화면의 챗봇 css는 아래 템플릿을 활용하여 만들었습니다. 참고해주시면 될 것 같습니다. 

 

www.css3transition.com/free-chat-bot-html-template/

 

Free chat bot html template - Css3 Transition

A Chatbot is a Artificial Intelligence(AI) based software That can we used to communicate with user through website, messaging app in their Suitable Language.

www.css3transition.com


 

직접 간단하게 2개의 경우로만 나뉜 챗봇을 구현해봤는데 생각보다 고려할게 많았습니다. 자잘한 처리로는 엔터키를 누르면 텍스트 전송 이벤트 달기부터 시작하여, 유저가 입력중이라면 입력중 말풍선 띄우기, api 응답 기다리는 중에도 입력중 말풍선 띄워주기, 메세지 보낸 시간 표기하기 등등. 좀 더 나아가선 응답에 따른 db에 저장까지 할 수 있었다면 좋겠지만 점점 스케일이 커지는 것 같아 일단 여기서 줄여봅니다. python으로 적용하게 된다면 좀 더 깔끔하게 작성해보도록 하겠습니다.

 

작게나마 어떤 식으로 응답이 오는지 정도만이라도 도움이 되었길 바랍니다. 😊

 

혹시 틀린 정보가 있다면 피드백은 언제나 환영입니다.