while (1): study();

Dialogflow를 이용한 콘도 예약 서비스 챗봇 구현 본문

챗봇

Dialogflow를 이용한 콘도 예약 서비스 챗봇 구현

전국민실업화 2021. 7. 25. 01:48
728x90

 챗봇이란 '인간과의 대화를 통해서 특정한 작업을 수행하도록 제작된 컴퓨터 프로그램'라고 위키피디아에서 정의하고 있습니다. 이렇게만 말하면 '오, 그렇구나' 싶고 크게 와닿는 바는 없습니다. 

Usage as a brand communication channel

Drift's 2020 State of Conversational Marketing report(https://www.drift.com/blog/state-of-conversational-marketing/)에 따르면, 근래 브랜드의 의사소통 수단 중 가장 많이 성장한 것은 챗봇입니다. 이는 크게 두 가지 이유에 기반합니다.


1. 정감있는 사용자 경험을 제공합니다.

2. 인력면에서 비용절감 효과가 있습니다.


 기업 입장에서 소비자를 만족시키는 것과, 비용을 절감하는 것은 분명 손익과 직결되는 중요한 문제들입니다. 따라서 사내 복지 서비스의 일환으로 제공되는 콘도 예약 서비스를 가정하고, 이를 돕는 챗봇을 만들어보려고 합니다.

 

1. 선행 사례 및 기술 선정

챗봇은 크게 4종류가 있습니다. 이 중에서 '콘도 예약 서비스'라는 특성에 맞는 챗봇을 선택하는 것부터 시작합니다.

챗봇의 종류

 대화형 챗봇은 흔히들 생각하는 '인공지능'의 형태와 가깝습니다. 엄밀한 의미에서의 정의는 더 포괄적이지만, 사람과 사람처럼 대화할 수 있다는 것입니다. 물론 이는 쉽지 않은 일이기에 개발하는 데 시간도 인력도, 그리고 데이터도 많이 들어가고 높은 수준의 전문성을 요구합니다. 자연어 생성(NLG; Natural Language Generation)이라는 어려운 기술을 사용하기 때문입니다. 예를 들어 최근 AI윤리 문제를 대두시킨 이루다가 있습니다.

이루다 - 스캐터랩

 트리형 챗봇객관식으로 질의응답을 하는 것이 큰 특징입니다. 정해진 응답만 할 수 있기 때문에, 사전에 조사만 잘 하면 전문성 없이도, 비교적 낮은 비용으로 구축하는 게 가능하겠네요. 예를 들어 고려대학교 블랙보드의 KULTIPS가 아래와 같은 버튼식(객관식)으로 질의응답을 합니다.

KULTIPS - 고려대학교

 추천형 챗봇검색 결과를 리스트로 반환합니다. 네이버의 검색창이 조금 단순화되었다고 보면 됩니다. 사실 리스트로 응답이 주어진다는 것은 경우의 수가 그리 많지 않다는 뜻이고, 따라서 트리형 챗봇에서 조금 확장된 버전이라고 생각하면 될 것 같습니다. 롯데택배의 로다같은 경우 '택배예약'이라는 질의에 대해서 가능한 경우의 수 '택배예약, 반품예약, 취급점 조회' 등을 모두 반환합니다.

로다 - 롯데택배

 시나리오형 챗봇순서대로 정보를 수합합니다. 어떤 태스크를 진행함에 있어서 시간적 종속성이 있다면 시나리오형 챗봇을 고려할 만합니다. 순서대로 예상 시나리오만 잘 짜면 되므로, 비용이나 전문성 면에서도 높은 수준을 요구하지 않습니다. 삼성닷컴의 챗봇은 순서대로 입력을 받는 대표적인 케이스입니다.

삼성닷컴 챗봇 - 삼성닷컴

 그럼 이 중에서 어떤 것을 고를지, 두 가지 측면에서 살펴보겠습니다. 우선, 사내 복지 서비스라는 점을 감안하여 이 곳에 많은 비용을 투자할 필요는 없습니다. 직원들의 사기를 올려주는 것은 좋지만, 그렇다고 사활을 걸만큼 중요한 일은 아니니까요. 두 번째로 상황에 적합한 지도 확인해보아야 합니다. 콘도 예약 서비스같은 경우는 우선 원하는 콘도를 선택하고, 이후에 인원, 날짜 등 세부 정보를 차례로 받는 '시간적 종속성'이 존재합니다. 

 두 가지 기준에 의해서 결과를 정리해보면 다음과 같습니다.

기술 선정표

 비용면에서 그다지 많이 부담되지 않고, 순차적으로 정보를 받은 시나리오형 챗봇을 선택하는 것이 적절한 판단인 듯 보입니다. 따라서 시나리오형 챗봇을 이용하여 콘도 예약 서비스를 마저 구현해보도록 하겠습니다.

 

2. 구축 기획

저는 Diagflow를 사용하기로 했습니다. 

https://dialogflow.cloud.google.com/

Diagflow

Dialogflow는 구글에서 제공하는 클라우드 서비스의 일환으로, 시나리오형 챗봇을 쉽게 만들 수 있는 기반을 제공합니다. 다른 프레임워크도 많이 있지만, Dialogflow는 세 가지 측면에서 이점이 있습니다.


1. 무료로 이용이 가능합니다.

2. 간단하게 구축이 가능합니다.

3. 강력한 언어모델을 지원합니다.


 프레임워크를 선정했으니 데이터에 대해서 구체적으로 정의해보겠습니다. 

데이터 정의

 사내 복지 서비스의 지원 범위는 위의 세 장소라고 가정합니다. 체크인 날짜는 굳이 Google Calendar API를 쓰지 않아도, Dialogflow에서 기본 제공하는 언어모델이 워낙 강력하기 때문에 단순히 텍스트로 날짜를 입력받기로 했습니다. (실제로 '내일', '모레', '8월 1일' 등 모든 케이스에 대해서 문제없이 인식해냈습니다.) 인원까지 모든 정보를 수합한 뒤에는 조식 서비스를 신청할 지 여부를 입력받습니다. 실제 사이트에 들어가서 확인해보니 픽업 서비스같은 건 없는데, 조식 서비스는 제공을 하더군요.

 정의한 데이터에 따라서 전체 시나리오의 흐름을 플로우차트로 그려보면 아래와 같습니다.

예상 시나리오

위의 예상 시나리오를 바탕으로 챗봇을 구현해보겠습니다.

 

3. 구축

챗봇 구현에 있어서 가장 중요한 것은 Entity와 Intent입니다. Entity는 챗봇이 대화 과정에서 꼭 받아야하는 정보들을 의미하며, Intent는 대화의 단위별 의도를 의미합니다. 즉, 챗봇은 대화의 의미를 잘 파악하면서 정보를 수합해나가야 합니다.

 제가 받아야 하는 정보는 콘도 이름, 날짜, 인원, 조식 여부입니다. 그러나 날짜와 인원같은 경우 이미 구글의 언어모델이 학습한 객체를 사용하기로 했습니다. 각각 @sys.date_time, @sys.number입니다. 따라서 콘도 이름과 조식 여부만 따로 Entity를 만들었습니다.

Entity

 정의한 Entity에 기반하여 Intent를 만들면 됩니다. 위에서 제시한 예상 시나리오와 별반 다를 것이 없습니다.

Intent

 각각의 Intent는 해당 시기에 어떤 작업을 하는지 직관적으로 알 수 있도록 작명하는 것이 중요합니다. 챗봇이 대화를 시작하고, 콘도 이름과 날짜, 인원, 조식 여부를 차례로 받는 것을 알 수 있습니다. 마지막 조식 여부에 대해 yes, no로 분기하지 않은 것은 이후 DB 연동 과정에서 용이성을 고려한 것입니다.

 마지막 Intent인 breakfast는 꽤 중요하니 자세히 보도록 하겠습니다.

breakfast contexts

 출력 컨텍스트로 첫 Intent인 start를 주어 다시 처음으로 루프할 수 있도록 해주었습니다. 또한 get_number_of_people-followup에는 지금까지 수합한 모든 정보가 파라미터로 들어있습니다. DB에 정보를 모두 저장해야 하기 때문에, 마지막 Intent에서 지금까지 받은 모든 정보를 저장해야 합니다.

breakfast parameters

 여기까지 하면 실제로 구동은 잘 됩니다만, 마지막으로 DB에 연동하는 작업을 해주겠습니다. 마지막 Intent의 Fulfillment탭에서 Enable webhook call for this intent를 활성화시켜주어야 합니다.

 이후 사이드바의 Fulfillment 탭에서 인라인 에디터를 활성화시키고, 다음을 각각 index.js, package.json에 입력해주었습니다. 대부분의 코드는 Docs에 있습니다. (https://cloud.google.com/dialogflow/es/docs/fulfillment-inline-editor)

index.js

'use strict';

const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const admin = require('firebase-admin');

process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
admin.initializeApp();
let db = admin.firestore();
db.settings({timestampsInSnapshots: true});

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
  const agent = new WebhookClient({ request, response });
  const sessionId = request.body.session.split("/").reverse()[0]; // or agent.session

  function writeOrderToDb (newOrder) {
    const docRef = db.collection('condoreserve').doc(sessionId);

    return db.runTransaction(t => {
      return t.get(docRef)
      .then(doc => {
        t.set(docRef, {
          orders: admin.firestore.FieldValue.arrayUnion(newOrder)
        }, {merge: true});
        /*if (!doc.data()) {
          t.set(docRef, { orders: [newOrder] });
        }
        else {
          t.update(docRef, {
            orders: admin.firestore.FieldValue.arrayUnion(newOrder)
          });
        }*/
      });
    }).catch(err => {
      console.log(`Error writing to Firestore: ${err}`);
    });
  }

  function confirmOrder(agent) {
    const reserve = agent.context.get('get_number_of_people-followup'),
          person = reserve.parameters.number,
          type = reserve.parameters.condo_name,
          date = reserve.parameters.date,
    	  breakfast = agent.parameters.breakfast;

    //agent.add(`Confirming ${person} ${type} in ${date}`);

    // important to return, otherwise console.logs will not appear and non-deterministic behavior will ocurr
    return writeOrderToDb({
      "person": person,
      "type": type,
      "date": date,
      "breakfast": breakfast,
    });
  }

  let intentMap = new Map();
  intentMap.set('breakfast', confirmOrder);
  agent.handleRequest(intentMap);
});

package.json

{
  "name": "dialogflowFirebaseFulfillment",
  "description": "This is the default fulfillment for a Dialogflow agents using Cloud Functions for Firebase",
  "version": "0.0.1",
  "private": true,
  "license": "Apache Version 2.0",
  "author": "Google Inc.",
  "engines": {
    "node": "8"
  },
  "scripts": {
    "start": "firebase serve --only functions:dialogflowFirebaseFulfillment",
    "deploy": "firebase deploy --only functions:dialogflowFirebaseFulfillment"
  },
  "dependencies": {
    "actions-on-google": "^2.5.0",
    "firebase-admin": "^8.2.0",
    "firebase-functions": "^2.0.2",
    "dialogflow": "^0.6.0",
    "dialogflow-fulfillment": "^0.6.1"
  }
}

 이렇게 하면 구글에서 제공하는 웹훅을 사용할 수 있게 되고, Deploy만 해주면 손쉽게 firebase와 연동하여 데이터를 저장할 수 있습니다.

 

4. 실제 서비스 사용 예시

 Dialogflow에서 제공하는 Web demo를 이용하여 테스트해보도록 하겠습니다. 다만, Web demo 환경의 한계인지 챗봇이 말하는 내용에 엔터가 들어가질 않습니다. 이로 인해 가독성이 조금 떨어져 보일 수 있으니 양해바랍니다.

실제로 사용해보는 것은 여기서 가능합니다.

https://jcy1996.tistory.com/55

 

HUGO 직접 사용해보기

Dialogflow의 Web Demo 환경에서 HUGO를 직접 사용해볼 수 있습니다.

jcy1996.tistory.com

 간단한 인삿말을 입력하면 챗봇 서비스가 시작됩니다.

테스트 - 1

 예, 아니오부터 시작해서 콘도 이름 또한 최대한 선택지를 명시해주었습니다. 이 챗봇의 목적은 대화가 아닌 서비스 예약입니다. 오히려 경우의 수가 많아지고 에러율이 높아지면, 이로 인해 사용자 만족도가 떨어질 우려가 있습니다. 따라서 다소 자유도를 해치더라도 조금 더 안정적인 방식을 택했습니다.

테스트 - 2

아무리 안전한 방식이라고 해도 개구진 사용자들이 존재할 수 있기 때문에, 적당한 응답이라면 인식하도록 훈련시켰습니다.

테스트 - 3

 보시는 것과 같이 날짜를 적당히 텍스트로 뱉더라도, 사전 학습된 모델이 알아서 날짜 정보를 도출해냅니다. 이런 경우에는 확실히 달력 API를 쓰는 것이 오히려 더 불편하고, 메리트가 없습니다. 

 또한 인원을 입력받는 과정에서 꼭 '숫자'로 입력할 것을 강요했습니다. 이는 '네 명'과 같이 숫자를 텍스트로 입력하면 경우의 수가 너무 많아지기 때문입니다. 한국어의 특성상 4는 '넷' 혹은 '네' 혹은 '사' (혹은 '너' 혹은 '넉') 등으로 표현될 수 있습니다. 모든 숫자에 대해서 이런 경우의 수를 모두 고려하기 보다는, 숫자만 입력받는 것이 경제적인 듯 보입니다.

테스트 - 4

 안타깝게도 실제 호텔 사이트와 연동은 안한 상태기 때문에, 언제나 객실이 존재한다는 대답을 반환합니다. 객실을 찾고 나면 조식 서비스 여부도 받기로 했었죠. 정합성을 위해서 '예', '아니오'가 아니라 '네', '응', '아니' 등이 들어와도 '예', '아니오'로 인식하도록 Entity를 만들어 놓았습니다. 적절히 응답하면 예약을 확정하고, 확정된 내역을 리마인드시켜줍니다. 

테스트 - 5

 만약 예약을 더 진행하겠다고 한다면 처음으로 돌아가고, 그렇지 않다면 서비스를 종료합니다.

테스트 - 6

 추가적으로 만약 모델이 알아듣지 못하는 발화를 하는 경우, Fallback을 반환합니다. 여기서는 입력에 대한 가이드라인을 제시하여 최대한 안정적으로 (오류없이) 정보를 입력할 수 있도록 지원합니다.

Default fallback

마지막으로 DB와 잘 연동이 되었는지 확인해보겠습니다. Google Firebase에 접속해보면, 아까 입력한 내용이 정상적으로 데이터베이스에 들어와있음을 알 수 있습니다.

Firebase 연동

 

여기까지 Dialogflow를 이용하여 콘도 예약 서비스 챗봇을 구축해보았습니다. 준비한 내용은 깃허브에 정리해놓았습니다.

깃허브: https://github.com/jeongchanyoung-1234/HUGO-the-reservation-chatbot

 

GitHub - jeongchanyoung-1234/HUGO-the-reservation-chatbot: 쉽고 빠르게 콘도 예약을 도와주는 시나리오형 챗봇

쉽고 빠르게 콘도 예약을 도와주는 시나리오형 챗봇, HUGO입니다. Contribute to jeongchanyoung-1234/HUGO-the-reservation-chatbot development by creating an account on GitHub.

github.com

 

728x90

'챗봇' 카테고리의 다른 글

HUGO 직접 사용해보기  (4) 2021.07.25
Comments