오픈 소스 Lucene(루씬)을 기반으로 하는

대표적인 검색엔진인 Solr(쏠라)와 Elasticsearch(엘라스틱서치)는 Lucene을 기반으로 하지만 사용법은 전혀 다르다.

특히 데이터 수집(색인-Indexing)에 있어서, 

일반적인 문서 색인은 Solr가 쉽게 구현할 수 있고, 웹 로그와 같은 짧은 형태의 데이터는 Elasticsearch가 쉽게 구현할 수 있는 것 같다.

또, 검색(Search) 문법에 있어서 Solr는 Lucene의 문법을 그대로 따르고, Elasticsearch는 자신만의 문법을 구현한 차이가 있다.

이 두 가지 검색엔진의 차이를 이해하고, 

둘 다 익히기 위해 예제를 만드는 중으로 세부 기능을 구현하는데 시간이 많이 걸려 (언제 끝날지...)

일단 먼저 기본적인 색인 / 검색 기능을 구현하고 설치 / 사용하는 방법을 정리한다.

Solr와 Elasticsearch예제는 각각 Github 에서 다운로드 받을 수 있다.

    1. Solr 설치
    2. Solr 주요 설정과 코드
    3. Elasticsearch 설치
    4. Elasticsearch 주요 설정과 코드

 

먼저, Elasticsearch 예제 사용법을 정리한다.

예제는 그림과 같이 기존에 공유한 웹 프로젝트인 Project9에 검색 기능을 추가하는 형태로 작성했다.

Project9에 대한 설명은 기존 블로그에서 확인 할 수 있다.

Project9의 게시판 데이터를 대상으로 하고,

공지사항과 일반 게시판의 게시판 종류를 데이터 종류로 구분해서 통합 검색 기능을 구현하였다.

 

설치 방법은 Elasticsearch설치와 Project9 설치로 구분된다.

Elasticsearch 설치

  1. 다운로드 및 설치 
  2. 실행
  3. 형태소 분석기 설치
  4. 사전 복사
  5. 저장소 생성

Project9 설치

    소스 다운로드 및 설치

참고: Project9 설치는 기존 방식과 동일하기 때문에 여기에서 별도로 정리하지 않는다.

 

Elasticsearch  설치

 

1. Elasticsearch 다운로드 및 설치 

Elasticsearch 다운로드 사이트에서 운영체제에 맞는 압축 파일을 다운로드 받아서, 압축을 해제 한다.

 

2. Elasticsearch 실행

압축을 해제한 폴더에서 bin 폴더에 있는 elasticsearch.sh(Linux)나 elasticsearch.bat (윈도우)를 실행한다.

그림과 같이 started가 보이면 잘 실행 된 것이다.

 

주의: Elasticsearch는 JDK 1.8에서도 실행되지만, JDK 11 이상에서 실행하도록 강력하게 추천한다.

그리고, jdk 폴더에 JDK 13 버전이 포함되어서 배포된다.

JAVA_HOME이 설정되지 않은 경우, 이 JDK 13에서 실행된다.

 

3. 형태소 분석기 설치

콘솔창(cmd)에서 Elasticsearch가 설치된 경로(bin)로 이동한 후,

다음 명령어를 실행해서 Elasticsearch에서 기본적으로 제공하는 형태소 분석기 nori를 설치한다.

            elasticsearch-plugin install analysis-nori

 

4. 사전 복사

비었지만 나중에 사용할 사용자 사전(userdict.txt), 불용어 사전(stopwords.txt), 유의어 사전(synonyms.txt) 파일을 복사한다.

저장소 생성시 지정해 두었기 때문에 복사해야 한다.

사전 파일과 저장소 정보를 지정한 파일을 얻기 위해서는 Project9 Elasticsearch 예제 파일을 먼저 다운로드 받아야 한다. 

문서 작성의 단계상 다음 단계에 하는 것으로 정리했지만, Project9의 Elasticsearch 예제 소스를 다음과 같이 다운로드 받는다.

또는, Eclipse에서 다운로드 받는 것이 편리하고, Eclipse로 git 소스를 다운로드 받는 것은 기존 블로그를 참고하면 된다.

        git clone https://github.com/gujc71/Project9_es.git

 

다운 받은 Project9 소스 폴더에서 project9_es\elasticsearch에 있는 3개의 사전 파일을 [elasticsearch설치경로]/config 폴더에 복사한다.

 

5. 저장소(Index) 생성

앞서 설치한 형태소 분석기와 사전을 Elasticsearch에 인식시키기 위해 Elasticsearch를 다시 실행한다.

저장소 구성 정보를 저장한 index_board.json 파일이 있는 project9_es 설치 경로로 이동해서

다음 명령어를 실행한다.

          curl -XPUT localhost:9200/project9 -d @index_board.json -H "Content-Type: application/json"

project9이라는 저장소(index)를 생성하고,

index_board.json 파일에 작성된 데로 저장 구조(Schema)를 생성한다.

 

Project9 설치

 

Project9의 설치는 기존에 공개한 웹 프로젝트로 기존 블로그에 정리되어 있다.

이 내용과 동일하고, Github 프로젝트 명만 project9이 아닌 project9_solr로 바꾸어 설치하면 된다.

그리고, 적당한 계정으로 로그인해서 [통합검색] 메뉴에서 다양한 검색 결과를 확인할 수 있다.

 

주의: Project9의 Elasticsearch 웹 예제를 실행하면, 1 분간격으로 데이터를 수집하도록 되어 있다.

Elasticsearch가 실행되어 있지 않으면 오류가 발생할 수 있고,

너무 빨리 통합 검색을 실행하면, 실행 결과가 없을 수 있다.

콘솔 창에서 Project9의 실행 로그 중에 데이터 수집 결과가 보이거나 조금 시간이 지난 뒤에 검색을 해야 한다.

 

이상으로 예제를 실행하는 방법을 정리하였고

데이터를 수집해서 색인하고, 색인한 데이터를 프로그램에서 조회하는 주요 부분에 대한 코드 정리는 다음 블로그에서 정리한다.

 

검색 서버를 구축 하려면 필요한 사항이 많지만, 가장 중요한 부분은 색인(Indexing)과 검색(Search)이다.

Elasticsearch에서는 색인과 검색을 모두 Java로 개발했다.

(Solr에서는 DIH - Data Import Handler를 이용하여 SQL문만 작성하여 색인한다.)

Logtash라고 데이터를 수집하는 별도의 프로그램이 있지만,

RDBMS의 부모 / 자식 관계, 첨부 파일 색인 처리에 어려움이 많아서 직접 개발하였다.

Logtash는 로그 데이터 수집에는 편리했지만 그 외에는 불편한 것이 많았다.

그래서 Log 가 붙은 것인지…

 

모든 기능을 아직 구현하지 않았고 (기약할 수 없지만 구현 중),

구현 방법을 모두 여기에 정리 할 수 없어서, 색인과 검색 개발에 관련된 몇 가지 주요한 사항만 정리한다.

주의: 용어나 옵션에 대한 상세한 설명을 정리하지 않기 때문에 Elasticsearch를 잘 모른다면 다음 내용이 어려울 수 있다.

    1. Solr 설치
    2. Solr 주요 설정과 코드
    3. Elasticsearch 설치
    4. Elasticsearch 주요 설정과 코드

 

정리할 주요 내용은 다음 그림과 같다.

 

색인

색인 작업은 Elasticsearch 검색 서버가 처리하는 것이고,

색인과 관련해서 구현해야 하는 것은 데이터를 수집해서 Elasticsearch 검색 서버에 전달하는 것이다(IndexingCtr.java).

데이터 수집 작업은 수집한 데이터를 저장하는 공간(저장소-Index)에 대한 정의와 데이터를 수집하는 방법에 대한 정의로 구분된다.

 

색인할 데이터를 저장할 저장소(Index)에 대한 정의는 다운받은 Project9 프로젝트의 project9_es 폴더에 있는 index_board.json 파일에 있다.

Elasticsearch 설치 과정에서 이 파일을 실행해서 스키마를 정의하였다.

이것은 Json 계층 구조로 작성하고, 가장 최상위는 다음과 같다.

{
  "settings": {
    "number_of_shards" : 1,
    "number_of_replicas": 0,  
    "index": {...}
  },
  "mappings": {
      "properties": {...}
  } 
}

index_board.json

최상위는 저장소에 대한 설정(settings)과 구조(mappings)로,

설정은(settings) 샤드(number_of_shards) 개수, 복제(number_of_replicas) 개수, 색인(index) 규칙에 대해서 정의하고,

구조(mappings)는 속성키(properties) 하위에 색인해서 저장하는 필드들에 대해서 정의한다.

 

설정(settings)의 색인(index)에서는 형태소 분석기(노리-Nori) 사용에 대한 설정을 하고,

복합어 처리(decompound_mode), 필터, 불용어 처리 등을 지정한다.

이 중에서 nori_analyzer는 형태소 분석기(노리-Nori) 사용에 대한 설정으로,

설정(settings)의 색인(index) 관련 부분을 모두 모아서 지정한다.

"analyzer": {
  "nori_analyzer": {
	"type": "custom",
	"tokenizer": "nori_user_dict",
	"char_filter": ["html_strip"],            
	"filter": [
	  "nori_posfilter", "stop_filtering", "lowercase", "stemmer", "nori_readingform", "synonym_filter"
	]
  }
},

index_board.json

내용에서 HTML 태그는 제거하고(html_strip),

품사로 불용어를 제거하거나(nori_posfilter) 불용어 사전으로 제거하는 (stop_filtering)등의 색인하는 과정을 정의한다.

보다 자세한 것은 인터넷 자료를 참고하고 여기서는 넘어간다.

 

구조(mappings)는 속성(properties)만 있고, 그 하위에는 저장하는 필드들에 대해서 정의한다.

"bgno": {"type": "long"},
"brdno": {"type": "keyword"},
"brddate": {"type": "date"},
생략 ~~    
"brdtitle": {"type": "text","analyzer":"nori_analyzer"},
"brdmemo": {"type": "text","analyzer":"nori_analyzer"},
"brdreply": {
    "type": "nested",
    "properties": {
        "reno": {"type": "text"} ,
        "redate": {"type": "text"} ,
        "rememo": {"type": "text", "analyzer":"nori_analyzer"} ,
        "usernm": {"type": "text"} ,
        "userno": {"type": "text"} 
    }
생략 ~~    

index_board.json

필드 타입(type)이 long, keyword, date 형 등은 값 그대로 색인하는 필드이고,

nori_analyzer로 지정된 필드는 앞서 정의한 nori_analyzer 과정데로 형태소 분석을 하고 색인한다.

댓글(brdreply)과 첨부 파일(brdfiles)은 RDBMS의 부모/자식 관계에 있는 데이터를 저장하기 위한 구조로 nested 형으로 지정한다.

nested형으로 지정된 데이터는 문서내의 문서로 배열처럼 저장된다.

문서내의 문서이기 때문에 속성(properties)키 하위에 reno, rememo등 다시 문서에 대한 필드 정의를 한다.

개인적으로 Solr보다 직관적이고 사용하기 쉬운 것 같다.

 

데이터를 수집하는 부분은 Solr의 경우 DIH로 개발자는 SQL문만 작성하면 되지만 (Solr의 장점?)

Elasticsearch에서는 Elasticsearch가 제공하는 Java High Level REST Client로 색인을 구현한다.

gu.search.IndexingCtr에 다음과 같은 구조로 작성한다.

@Scheduled(cron="0 */1 * * * ?")
public void indexingFile() {
    RestHighLevelClient client = createConnection();

    게시글 이전 색인시 마지막 번호 로드
    
    색인할 게시글 리스트 
    게시글을 Json으로 작성후 색인 - INSERT

    마지막 게시글 번호 저장

    ------------------        
    댓글 이전 색인시 마지막 번호 로드
    
    색인할 댓글 리스트 
    댓글을 Json으로 작성후 색인 - UPDATE

    마지막 댓글 번호 저장

    ------------------        
    첨부파일 이전 색인시 마지막 번호 로드
    
    색인할 첨부파일 리스트 
    첨부파일에서 텍스트 추출(Tika)
    첨부파일 정보를 Json으로 작성후 색인 - UPDATE

    마지막 첨부파일 번호 저장
}

IndexingCtr.java

게시글, 댓글, 첨부파일  모두 동일한 방식으로 작성하였고,

첨부파일은  파일에서 색인할 텍스트를 추출하는 Tika를 사용한다.

셋 모두 이전에 마지막으로 색인한 글 번호 (Pk-Primary Key) 이후의 데이터들을 조회한다.

Solr에서는 날짜(brddat)로 구현했지만, Elasticsearch에서는 글번호로 구현했다.

고유한 값을 가지는 필드면 어떤 것이라도 사용할 수 있다.

마지막 색인 값으로 마지막으로 색인한 이후의 증가된 데이터만 색인하고,

마지막 색인 값이 없는(NULL) 경우는 모든 데이터가 대상이 되어 풀색인을 한다.

SQL문을 그렇게 하도록 다음과 같이 작성하였다.

<select id="selectBoards4Indexing" resultType="gu.board.BoardVO" parameterType="String">
    SELECT BGNO, BRDNO, CU.USERNM, TB.USERNO, BRDTITLE, BRDMEMO
           , LEFT(BRDDATE, 10) BRDDATE, RIGHT(BRDDATE, 8) BRDTIME 
     FROM TBL_BOARD TB
     INNER JOIN COM_USER CU ON CU.USERNO=TB.USERNO 
    WHERE TB.BRDDELETEFLAG='N' 
       AND BRDNO > #{brdno}
    ORDER BY BRDNO 
      LIMIT 20
</select>
    

board.xml

추출한 색인 대상들은 Java의 VO개체에 담겨서 동적 배열(List)형태로 관리되는데,

List boardlist = (List) boardSvc.selectBoards4Indexing(brdno);
List replylist  = (List) boardSvc.selectBoardReply4Indexing(lastVO);
List filelist     = (List) boardSvc.selectBoardFiles4Indexing(lastVO);

IndexingCtr.java

반환 된 개수 만큼 반복하면서 (for), VO에 있는 필드의 값을 Json 형태로(키:값) 저장한다.

게시판은 source로 지정해서 작성했고,

IndexRequest indexRequest = new IndexRequest(INDEX_NAME)
      .id(el.getBrdno())
      .source("bgno", el.getBgno(),
              "brdno", brdno,
              "brdtitle", el.getBrdtitle(),
              "brdmemo", el.getBrdmemo(),
              "brdwriter", el.getUsernm(),
              "userno", el.getUserno(),
              "brddate", el.getBrddate(),
              "brdtime", el.getBrdtime()
              ); 

IndexingCtr.java

댓글과 첨부 파일은 Map 구조로 작성했다.

            Map<String, Object> replyMap = new HashMap<String, Object>();
            replyMap.put("reno", reno);
            replyMap.put("redate", el.getRedate());
            replyMap.put("rememo", el.getRememo());
            replyMap.put("usernm", el.getUsernm());
            replyMap.put("userno", el.getUserno());

            Map<String, Object> singletonMap = Collections.singletonMap("reply", replyMap);

UpdateRequest updateRequest = new UpdateRequest()
             .index(INDEX_NAME)
             .id(el.getBrdno())
             .script(new Script(ScriptType.INLINE, "painless", 생략~

IndexingCtr.java

모두 키와 값으로 구성된 Json 방식으로 둘 중 어느 방식을 사용해도 된다.

 

가장 중요한 처리로 게시글은 IndexRequest로 색인하고(저장하고), 댓글과 첨부 파일은 UpdateRequest로 색인한다.

게시글을 먼저 처리해서 저장하고(Insert)

댓글과 첨부 파일은 해당 게시글을 찾아서 수정하는(Update) 방식으로 구현한다.

 

더우기 RDBMS의 부모/자식, 1 : n (일대다)의 관계,

즉 하나의 게시물이 여러개의 댓글과 첨부 파일을 가지기 때문에 다음과 같이 다소 복잡한 코드로 저장한다.

.script(new Script(ScriptType.INLINE, "painless",

      "if (ctx._source.brdreply == null) {ctx._source.brdreply=[]} ctx._source.brdreply.add(params.reply)", singletonMap));

painless라는 Elasticsearch에서 제공하는 스크립트 언어로

게시물 문서에서 brdreply 라는 필드를 배열( [ ] )로 초기화하고,

생성된 댓글이나 첨부 파일 하나(Json)를 다음과 같이 배열에 추가(add)하는 방식으로 작성한다.

{
                "_index": "project9",
                "_type": "_doc",
                "_id": "38",
                "_score": 1,
                "_source": {
                    "bgno": "3",
                    "brdno": "38",
                    "brdtitle": "거침없이 배우는 Jboss",
                    "brdmemo": "거침없이 배우는 Jboss",
                    "brdwriter": "이종무",
                    "userno": "24",
                    "brddate": "2020-03-01",
                    "brdtime": "17:42:42",
                    "brdreply": [
                        {
                            "reno": "1",
                            "usernm": "관리자",
                            "redate": "2020-03-08 09:43:27.0",
                            "rememo": "윈도우",
                            "userno": "1"
                        }
                    ]
                }
},

이 코드 덕분인지 Solr보다 직관적인 모습으로 색인되어 저장된다 (Solr Nested 참고).

 

이렇게 작성된 코드는 스프링 스케쥴러를 이용해서 1분마다 실행되게 작성했다.

            @Scheduled(cron="0 */1 * * * ?")

실제로 사용할 경우에는 사용자가 없는 새벽 1시에 일괄 색인하는 경우가 많다.

웹 브라우저에서 게시판을 접속해서 게시글을 추가하거나 댓글을 작성하면 잠시 뒤에 검색된다.

 

검색

 

검색도 기존 Prohect9 프로젝트에 구현되어 있으며

하나의 Java파일(gu.search.SearchCtr)과 JSP(search.jsp) 파일로 작성하였다.

search.jsp 파일은 검색 결과를 Json으로 받아서 적절하게 보여주는 기능을 하고, 여기서 정리하지 않는다.

SearchCtr.java 파일은 High Level REST Client를 기반으로 작성하였고, 

검색식을 만드는 부분과 Elasticsearch에 질의하는 부분으로 구분할 수 있다.

반환 된 결과는 Json으로 작성되어 있어서 그데로 jsp로 전달하고 종료한다.

 

검색식을 만드는 기본 코드는 다음과 같이 makeQuery()함수에서 작성한다.

private BoolQueryBuilder makeQuery (String[] fields, String[] words, FullTextSearchVO searchVO) {
	BoolQueryBuilder qb = QueryBuilders.boolQuery();
	
	String searchType = searchVO.getSearchType();
	if (searchType!=null & !"".equals(searchType)) {
		qb.must(QueryBuilders.termQuery("bgno", searchType));
	}
	
	if (!"a".equals(searchVO.getSearchTerm())) {							// 기간 검색
		qb.must(QueryBuilders.rangeQuery("brddate").from( searchVO.getSearchTerm1()).to( searchVO.getSearchTerm2()) );	
	}

	for( String word : words) {			// 검색 키워드
		word = word.trim().toLowerCase();
		if ("".equals(word)) continue;
		
		BoolQueryBuilder qb1 = QueryBuilders.boolQuery();
		for( String fld : fields) {							// 입력한 키워드가 지정된 모든 필드에 있는지 조회
			if ("brdreply".equals(fld)) {		// 댓글은 nested로 저장되어 있어 별도로 작성
				 qb1.should(QueryBuilders.nestedQuery("brdreply", QueryBuilders.boolQuery().must(QueryBuilders.termQuery("brdreply.rememo", word)), ScoreMode.None));
			} else 
			if ("brdfiles".equals(fld)) {		// 첨부 파일은  nested로 저장되어 있어 별도로 작성
				 qb1.should(QueryBuilders.nestedQuery("brdfiles", QueryBuilders.boolQuery().must(QueryBuilders.termQuery("brdfiles.filememo", word)), ScoreMode.None));
			} else {
				qb1.should(QueryBuilders.boolQuery().must(QueryBuilders.termQuery(fld, word)));
			}
		}
		
		qb.must(qb1);			// 검색 키워드가 여러개일 경우 and로 검색
	}
	
	return qb;
}

SearchCtr.java

Elasticsearch이 기본적인 검색은 필드:키워드 구조로 Solr와 유사하지만,

다음과 같이 보다 더 Json 구조로 직관적인 구조로 작성한다.

{  
   "query":{  
      "match":{  
         "brdtitle":"js"
      }
   }
}

Solr는 필드:키워드가 and, or, ()로 표현되자만,

Elasticsearch은 위와 같은  Json 구조에 must(and), should(or), []로 표현된다.

따라서 위 코드의 마지막 Else문이 기본 검색으로, 이상의 json을 표현한 것이다.

    qb1.should(QueryBuilders.boolQuery().must(QueryBuilders.termQuery(fld, word)));

댓글(brdreply)과 첨부파일(brdfiles)은 해당 필드에 값이 있으면 게시글이 조회 되어야 하기 때문에 nestedQuery 를 이용해서 따로 작성한다.

QueryBuilders.nestedQuery("brdreply", 
           QueryBuilders.boolQuery().must(QueryBuilders.termQuery("brdreply.rememo", word)), ScoreMode.None)
QueryBuilders.nestedQuery("brdfiles",
           QueryBuilders.boolQuery().must(QueryBuilders.termQuery("brdfiles.filememo", word)), ScoreMode.None)

사용자가 여러개의 키워드를 입력할 수 있기 때문에 공백 단위로 잘라서 개수 만큼 반복하고

이 키워드들을 조회할 필드들 개수 만큼 반복해서 검색식을 만들기 때문에 두개의 반복문을 실행한다.

for ( String word : words) {
     for ( String fld : fields) {

날짜를 기간으로 검색하기 위해 rangeQuery를 사용하고,

공지사항과 일반 게시판 중 하나를 선택해서 조회할 수 있도록 게시판 종류(bgno)를 지정할 수 있게 하였다.

 

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.highlighter( makeHighlightField ( searchRange ) );		// 검색 대상 필드에 대해서 하이라이트(댓글, 첨부파일은 제외)
searchSourceBuilder.fetchSource(INCLUDE_FIELDS, null);
searchSourceBuilder.from( (searchVO.getPage()-1) * DISPLAY_COUNT);							// 페이징
searchSourceBuilder.size(DISPLAY_COUNT); 
searchSourceBuilder.sort(new FieldSortBuilder("_score").order(SortOrder.DESC));				// 정렬
searchSourceBuilder.sort(new FieldSortBuilder("brdno").order(SortOrder.DESC));

TermsAggregationBuilder aggregation = AggregationBuilders.terms("gujc").field("bgno");		// 그룹별(게시판) 개수
searchSourceBuilder.aggregation(aggregation);

searchSourceBuilder.query( makeQuery ( searchRange, searchVO.getSearchKeyword().split(" "),  searchVO ));	// 검색식 작성

SearchRequest searchRequest = new SearchRequest();
searchRequest.indices(INDEX_NAME);
searchRequest.source(searchSourceBuilder);

RestHighLevelClient client = null;
SearchResponse searchResponse = null;

client =  createConnection();
searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

SearchCtr.java

이렇게 작성한 검색식은 query로 실행하지만

하이라이팅(highlighter), 페이징(from, size), 정렬(sort)의 설정을 추가해서 실행한다.

특히, 다양한 데이터를 조회하는 통합 검색 기능을 흉내내기 위해

여기서는 공지사항과 일반 게시판의 게시판 종류(bgno) 기능을 구현하였고,

게시판 종류(bgno)별 개수를 확인하기 위해 Elasticsearch에서 제공하는 집계(aggregation) 기능을 이용한다.

TermsAggregationBuilder aggregation = AggregationBuilders.terms("gujc").field("bgno"); // 그룹별(게시판) 개수
searchSourceBuilder.aggregation(aggregation);

이렇게 실행된 결과는 Json으로 반환되고, 이 값은 그대로 JSP로 전달해서 처리한다.

 

JavaScript가 Json을 쉽게 사용할 수 있기 때문이지만,

JSP까지 정리하면 검색과 관계없는 코드로 너무 길어질 것 같아서 여기까지 정리한다.

 

이상의 예제는 게시글/댓글,파일에 대한 색인과 검색에 대한 것으로

글이 수정되거나 삭제 된 경우에 대한 것도 처리가 필요하다.

게시판 프로그램 자체를 수정해야 해서 구현하지 않았다.

이런 부분까지 구현하면 실사용에서도 무난하게 사용할 수 있을 것이다.

 

 

 

 

 

구글에서 제공하는 챗봇 플랫폼인 Dialogflow는 챗봇을 쉽게 만들수 있도록 도와준다.

Dialogflow 가이드 문서와 인터넷 검색을 통해 다양한 예제와 설명을 구할 수 있지만

처음 시작하는 사람에게는 조금 부담되는 개념들이 많다.

더우기, 대부분의 블로그나 설명이 챗봇 생성을 따라 하면서 익히도록 되어 있는데 ,

여기에서는 챗봇 생성을 따라 하면서 익히는 것이 아니고

구글이 제공하는 챗봇을 실행 시켜보면서 전체 개념을 잡을 수 있도록 정리하였다.

간단하게 체험하면서 전체 개념을 머릿속에 그려 보고, 인터넷 자료나 서적을 보면 좀 더 쉽게 접근 할 수 있을 것이다.

 

구글에서는 호텔 방 예약 예제를 제공하고, 사용법은 다음과 같이 단순하다.

다음 문장은 구글 [API로 인텐트 관리]에 있는 문장을 그대로 가져온 것이다.

에이전트로 예제 파일 가져오기

가져오기를 수행하면 에이전트에 인텐트와 개체가 추가됩니다. 기존 인텐트 또는 개체가 가져온 파일에 있는 인텐트 또는 개체와 이름이 같으면 대체됩니다.

파일을 가져오려면 다음 단계를 따르세요.

    1. RoomReservation.zip 파일을 다운로드합니다.

    2. Dialogflow 콘솔로 이동합니다.

    3. 에이전트를 선택합니다.

    4. 에이전트 이름 옆의 설정 settings 버튼을 클릭합니다.

    5. Export and Import(내보내기 및 가져오기) 탭을 선택합니다.

    6. ZIP 파일에서 가져오기를 선택하고 다운로드한 ZIP 파일을 가져옵니다.

Dialogflow를 사용해 본 사람은 위 순서를 따라서 쉽게 실행해 볼 수 있다.

여기에서는 처음 사용하는 사람을 위해 위 내용을 그림과 함께 조금 더 상세하게 정리하였다.

영어라 조금 아쉽지만, 영어로 제작하나  한글로 제작하나 기본적인 사용법은 동일하다.

 

먼저, Dialogflow에 접속한다.

화면 중앙에 있는 [Sign up for free]을 눌러서 회원 가입을 한다.

다음과 같이 구글 Gmail 계정으로(Sign-in with Google) 한번에 가입할 수 있다.

 

 

 

회원 가입후 로그인 하면 시작 화면은 다음과 같다.

챗봇을 만드는 하나의 프로젝트를 Agent라고 하는데,

챗봇 생성을 위해 왼쪽 메뉴에서 [Create Agent]를 눌러서 새로운 Agent를 생성한다.

 

챗봇 Agent 이름을 RoomReservation으로 입력하고, 생성(Create) 버튼을 클릭한다.

언어는 영어가 기본 설정인데, 예제가 영어라 그냥 넘어간다.

 

조금의 시간이 흐른뒤, RoomReservation이 생성된다.

왼쪽 메뉴에서 톱니 바퀴 모양의 설정 아이콘을 클릭한다.

참고: Agent가 생성되면서 왼쪽 메뉴에 Intents가 기본 선택되고,

화면 중앙에는 기본 제공되는 두개의 Intents가 생성된다. 

하나는 사용자의 말을 이해하지 못했을 때, 다시 말해달라는 것이고 (Default Fallback Intent)

다른 하나는 대화를 시작하면서 환영 메시지를 출력하기 위한 것이다 (Default Welcome Intent).

챗봇은 이 Intent의 집합 또는 흐름이라고 볼 수 있다.

 

[Export and Import] 탭에서 [IMPORT FROM ZIP] 버튼을 클릭한다.

 

예제 파일 다운로드는 구글 [API로 인텐트 관리]에서 다운로드 받거나

호텔 예제 파일 다운로드를 클릭해서 RoomReservation.zip 파일을 직접 다운로드 받는다.

 

다음과 같이 파일 업로드 팝업 창에서 다운로드 받은 호텔 예약 챗봇 예제 파일(RoomReservation.zip)을 추가한다.

주의 : 하단 입력창에 "IMPORT"라고 입력하지 않으면 [IMPORT] 버튼이 그림과 같이 활성화 되지 않는다.

[IMPORT] 버튼을 클릭해서 가져오기 작업을 마무리한다.

 

가져오기 작업을 한 후, 왼쪽에 있는 Intents 메뉴를 클릭하면 추가된 Intents을 볼 수 있다.

Intents에 대한 설명은 따로 정리되어 있다.

여기서는 우측에 있는 마이크의 입력창[Try it now]에서 테스트를 시작한다.

호텔방 예약 챗봇을 실행해 본다.

 

예의바르게 인사부터 하면 (hi), 챗봇이 자신의 역할에 대해서 소개한다 (왼쪽).

내가 입력한 문장은 [USER SAYS]에 출력되고, 챗봇의 대답은 [DEFAULT RESPONSE]에 출력된다.

예약하께(reserve a meeting room)라고 하니, 언제(What date?)라고 챗봇이 대답한다 (위 그림 중앙).

내일(tomorrow)이라고 하자 몇시냐고(What time will the meeting start)라고 묻는다 (위 그림 오른쪽).

 

3시에(3pm) 예약한다고 하자,  며칠 있을 거냐고(How long will it last?)라고 묻고 (아래 그림 왼쪽)

3일이라고(3day) 하자, 몇명이 참석하냐고 물어본다 (How many people are attending?) (아래 그림 중앙).

3명이라고(3) 하자, 방을 고르라고(Choose a room please) 한다 (아래 그림 오른쪽).

 

스위트 룸(A)라고 하자, 예약에 대한 총 상황을 설명하고 맞는지 다시(confirm below) 물어본다 (아래 그림 왼쪽).

맞다고 하니(OK), 예약이 완료(All set!) 된다 (아래 그림 오른쪽).

이상으로 챗봇으로 간단하게 호텔 예약을 진행했다.

테스트 대화창에서 좀더 대화를 하면서 실행창의 [DEFAULT RESPONSE] 하단의 CONTEXTS, INTENT, ACTION 등을 잘 살펴 보기 바란다.

 

이상의 방법은 개발하는 과정에 대화하는 테스트를 정리한 것이고,

이것을 실제 챗봇처럼 기존 SNS에 연동해서 서비스 할 수 있다.

Integrations 메뉴를 선택하면 다양한 연동 리스트들이 출력되는데,

여기에서는 [Web Demo]를 선택해서 간단하게 테스트 한다.

연동 방법등을 정리하는 것은 이 글의 범위를 벗어나므로, 다른 자료를 참고 하면 된다.

 

[Web Demo]를 선택하면, 생성한 챗봇을 다른 웹 사이트등에서 사용할 수 있는 URL등이 제공된다.

URL을 클릭하거나, 복사해서 웹 브라우저에서 실행하면

다음 그림과 같이 그럴듯한 채팅창이 실행된다.

앞서의 대화를 그대로 진행하면서 테스트 할 수 있다.

 

단순한 클릭 몇 번으로 그럴 듯한 챗봇을 제작(?)해 봤다.

챗봇과 이런 저런 다른 대화를 나눠보면, 조금의 신기함과 답답함이 느껴질 수 있다.

이 챗봇을 만드는 방법에 대한 조금 더 상세한 설명은 다른 페이지에서 정리하였다.

 

 

 

 

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

5분만에 둘러보는 챗봇 만들기 - Dialogflow 조금 더  (1) 2020.03.01

+ Recent posts