콘텐츠로 이동

개요

라이브러리

바른의 기능을 사용하려면, 개발 환경에서 라이브러리를 이용해야 합니다. 바른은 현재 4가지 개발 언어를 위한 라이브러리를 지원합니다. 이 모든 라이브러리는 각 언어별 표준 패키지 배포 환경에 맞춰서 개발되었습니다.

  • 파이썬: PIP 패키지 bareunpy
  • 자바: maven 패키지 bareun
  • 자바스크립트(nodejs): npm 패키지 bareun
  • R: RBareun

이들 모두는 깃허브에 공개되어 있습니다.

각 언어별로 설치하는 방법은 다음과 같습니다.

pip install bareunpy
<dependency>
  <groupId>ai.bareun.tagger</groupId>
  <artifactId>bareun</artifactId>
  <version>1.3.0</version>
</dependency>
npm install bareun -save
library(devtools)
devtools::install_github("bareun-nlp/RBareun")

라이브러리는 어떻게 동작하나요?

라이브러리를 사용하시려면, 반드시 TCP/IP 네트워크 환경에 연결되어 있어야 합니다. 모든 라이브러리는 클라이언트로서 서버에 연결할 수 있어야 합니다. 바른은 TCP 포트 5656을 통해서 서버로 연결합니다. 서버가 사용자의 PC에 설치되어 있다면 아무런 문제가 없습니다.

바른의 라이브러리는 gRPC를 통해서 서버와 HTTP/2 프로토콜을 사용하여 통신을 합니다. 이때 사용하는 인터페이스는 gRPC를 통해서 정의하였습니다.

라이브러리를 사용하려면, 또한 API키를 필요로 합니다. API키는 gRPC의 메타데이터를 통해서 전달됩니다.

  • TCP/IP 또는 인터넷이 활성화되어 있는 환경
  • API키가 등록된 서버, 이미 실행 중이어야 합니다.
  • API키: 라이브러리를 호출할 때 사용합니다.

정리하면, 라이브러리를 사용하려면 API키를 사용하여, 실행 중인 바른에 접속해야 합니다. 라이브러리에서 요청을 보내면, 서버는 API키를 점검해서 문제가 없으면 요청에 대한 응답을 보냅니다. 라이브러리는 응답을 다룰 수 있는 다양한 함수들을 가지고 있습니다.

sequenceDiagram
    바른 라이브러리 ->>+ 바른 서비스: 요청 전송
    바른 서비스 ->>+ 바른 인터셉터: 요청 점검
    alt       권한 오류
    Note right of 바른 인터셉터: API키 점검
    바른 인터셉터 --x 바른 라이브러리: ERROR
    else      권한 정상
    바른 인터셉터 ->>- 바른 서비스: 사용자 정보
    바른 서비스 ->>- 바른 라이브러리: 응답 전달
    end
    바른 라이브러리 -->+ 바른 라이브러리: 응답 내용 출력 또는 사용

각 언어별 샘플 코드

각 언어별 라이브러리는 비슷한 사용 방법을 가지도록 구성하였습니다. 아래는 각 언어별 샘플 코드를 간단하게 살펴보도록 하겠습니다.

형태소 분석

형태소 분석을 위한 기능은 간단하게 문장을 입력하면 됩니다. 문장을 입력하는 것 이외에도 3가지 옵션을 사용할 수 있습니다.

  • auto_split_sentence: 문장을 자동으로 분리합니다. 기본값으로 false입니다.
  • auto_spacing: 자동으로 띄어쓰기를 수행합니다. 기본값으로 true입니다.
  • auto_jointing: 자동으로 붙여쓰기를 수행합니다. 기본값으로 false입니다.
import sys
import google.protobuf.text_format as tf
from bareunpy import Tagger

#
# you can API-KEY from https://bareun.ai/
# 아래에 "https://bareun.ai/"에서 이메일 인증 후 발급받은 API KEY("koba-...")를 입력해주세요. "로그인-내정보 확인"
API_KEY="koba-ABCDEFG-1234567-LMNOPQR-7654321" # <- 본인의 API KEY로 교체 

# If you have your own localhost bareun.
tagger = Tagger(API_KEY, 'localhost')
# or if you have your own bareun which is running on 10.8.3.211:15656.
tagger = Tagger(API_KEY, '10.8.3.211', 15656)


# print results.
res = tagger.tags(["안녕하세요.", "반가워요!"])

# get protobuf message.
m = res.msg()
tf.PrintMessage(m, out=sys.stdout, as_utf8=True)
print(tf.MessageToString(m, as_utf8=True))
print(f'length of sentences is {len(m.sentences)}')
## output : 2
print(f'length of tokens in sentences[0] is {len(m.sentences[0].tokens)}')
print(f'length of morphemes of first token in sentences[0] is {len(m.sentences[0].tokens[0].morphemes)}')
print(f'lemma of first token in sentences[0] is {m.sentences[0].tokens[0].lemma}')
print(f'first morph of first token in sentences[0] is {m.sentences[0].tokens[0].morphemes[0]}')
print(f'tag of first morph of first token in sentences[0] is {m.sentences[0].tokens[0].morphemes[0].tag}')

## Advanced usage.
for sent in m.sentences:
    for token in sent.tokens:
        for m in token.morphemes:
            print(f'{m.text.content}/{m.tag}:{m.probability}:{m.out_of_vocab}')

# get json object
jo = res.as_json()
print(jo)

# get tuple of pos tagging.
pa = res.pos()
print(pa)
# another methods
ma = res.morphs()
print(ma)
na = res.nouns()
print(na)
va = res.verbs()
print(va)
import static org.junit.Assert.assertTrue;

import java.nio.file.Path;
import java.util.List;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;

import ai.bareun.tagger.*;
import groovy.ui.Console;
import ai.bareun.protos.AnalyzeSyntaxRequest;
import ai.bareun.protos.AnalyzeSyntaxResponse;
import ai.bareun.protos.CustomDictionary;
import ai.bareun.protos.CustomDictionaryMeta;
import ai.bareun.protos.Document;
import ai.bareun.protos.LanguageServiceGrpc;

/**
* Unit test for simple App.
*/
public class AppTest
{   // 아래에 "https://bareun.ai/"에서 이메일 인증 후 발급받은 API KEY("koba-...")를 입력해주세요. "로그인-내정보 확인"
    private static Logger logger = LoggerFactory.getLogger(AppTest.class.getSimpleName());
    String API_KEY="koba-ABCDEFG-1234567-LMNOPQR-7654321" // <- 본인의 API KEY로 교체 
    void log(Object str){

        if( str instanceof String ) {
            logger.info(str.toString());
        }  else {
            Gson gson = new Gson();
            logger.info(gson.toJson(str));
        }

    }
    /**
    * Rigorous Test :-)
    */
    @Test
    public void shouldAnswerWithTrue()
    {

        LanguageServiceClient conn = new ai.bareun.tagger.LanguageServiceClient("localhost", API_KEY);
        AnalyzeSyntaxResponse response =  conn.analyze_syntax("아버지가 방에 들어가신다.");
        String str = conn.toJson();
        assertTrue( !str.isEmpty() );
        logger.info(str);
    }

    static String TestString = "아버지가 방에 들어가신다.";
    // "윤석열 대통령이 취임 220일차인 오는 15일 대국민 소통의 일환으로 ‘국정과제 점검회의’를 진행한다.\n 취임 후 처음 국민과 대면해 생방송으로 대화를 나눈다는 점에서 취지에 걸맞는 소통이 얼마나 이뤄질지 관심이다.";


    @Test
    public void pos() {
        Tagged tag = new Tagger("localhost", API_KEY).tag(TestString);

        List<?> ret;
        Boolean flatten = true, join = true, detail = true;
        ret = tag.pos(flatten, join, detail);
        assertTrue(!ret.isEmpty());
        log(String.format("flatten = %B, join = %B, detail = %B", flatten , join , detail));
        log(ret);


        flatten = true;
        join = true;
        detail = false;
        ret = tag.pos(flatten, join, detail);
        assertTrue(!ret.isEmpty());
        log(String.format("flatten = %B, join = %B, detail = %B", flatten , join , detail));
        log(ret);

        flatten = true;
        join = false;
        detail = true;
        ret = tag.pos(flatten, join, detail);
        assertTrue(!ret.isEmpty());
        log(String.format("flatten = %B, join = %B, detail = %B", flatten , join , detail));
        log(ret);


        flatten = false;
        join = true;
        detail = true;
        ret = tag.pos(flatten, join, detail);
        assertTrue(!ret.isEmpty());
        log(String.format("flatten = %B, join = %B, detail = %B", flatten , join , detail));
        log(ret);

        flatten = false;
        join = false;
        detail = true;
        ret = tag.pos(flatten, join, detail);
        assertTrue(!ret.isEmpty());
        log(String.format("flatten = %B, join = %B, detail = %B", flatten , join , detail));
        log(ret);
    }


    @Test
    public void morphs() {
        Tagged tag = new Tagger("localhost", API_KEY).tag(TestString);

        List<String> ret = tag.morphs();
        assertTrue(!ret.isEmpty());
        log("morphs()");
        log(ret);

        ret = tag.nouns();
        assertTrue(!ret.isEmpty());
        log("nouns()");
        log(ret);

        ret = tag.verbs();
        assertTrue(!ret.isEmpty());
        log("verbs()");
        log(ret);
    }

    static public void main() {
        AppTest theApp = new AppTest();
        theApp.shouldAnswerWithTrue();
        theApp.pos();
        theApp.morphs();
        theApp.testCustomDict();
    }

}
let host="nlp.bareun.ai"
// 아래에 "https://bareun.ai/"에서 이메일 인증 후 발급받은 API KEY("koba-...")를 입력해주세요. "로그인-내정보 확인"
let API_KEY="koba-ABCDEFG-1234567-LMNOPQR-7654321" // <- 본인의 API KEY로 교체 
let {LanguageServiceClient, Tagger, CustomDict}  = require("bareun");
let language_service_client = new LanguageServiceClient(host, API_KEY);

language_service_client.AnalyzeSyntax("아버지가 방에 들어가신다.",
    (error, res) => {
        console.log('result : language_service_client.AnalyzeSyntax("아버지가 방에 들어가신다.")');
        if( error ) {
            throw error;
            return;
        }
        console.log(JSON.stringify(res));
    }
);

(async () => {
  try {
      let res = await language_service_client.asyncAnalyzeSyntax("아버지가 방에 들어가신다.")
      console.log(JSON.stringify(res));
  } catch(e) {
      console.log(e);
  }
})();


let tagged = await tagger.tag("미친 세상에서 맨정신으로 산다는 건 힘든 일이다.");
(async () => {
    const t=true, f=false;
    let obj;
    obj = tagged.pos(t, t, t);
    console.log("pos(t, t, t)"+JSON.stringify(obj));

    obj = tagged.pos(t, t, f);
    console.log("pos(t, t, f)"+JSON.stringify(obj));

    obj = tagged.pos(t, f, t);
    console.log("pos(t, f, t)"+JSON.stringify(obj));

    obj = tagged.pos(f, t, t);
    console.log("pos(f, t, t)"+JSON.stringify(obj));

    obj = tagged.morphs();
    console.log("morphs()"+JSON.stringify(obj));

    obj = tagged.nouns();
    console.log("nouns()"+JSON.stringify(obj));

    obj = tagged.verbs();
    console.log("verbs()"+JSON.stringify(obj));
})();
> library(RProtoBuf)
> library(bareun)

# 아래에 "https://bareun.ai/"에서 이메일 인증 후 발급받은 API KEY("koba-...")를 입력해주세요. "로그인-내정보 확인"
> set_key("YOUR_API_KEY") # <- 본인의 API KEY로 교체 
> t <- tagger()
> text <- "문장을 입력합니다.\n여러 문장을 넣습니다."
> pos(t, text)

[[1]]
[1] "문장/NNG"  "을/JKO"    "입력하/VV" "ㅂ니다/EF" "./SF"

[[2]]
[1] "여러/MMN"  "문장/NNG"  "을/JKO"    "넣/VV"     "습니다/EF" "./SF"
1번째 문장의 4번째 형태소 출력
> postag(t, text)[[1]][[4]]

$morpheme
[1] "ㅂ니다"

$tag
[1] "EF"
어절, 명사, 동사 출력(해당 없는 경우  문자열 배열 반환)
> morphs(t)

[[1]]
[1] "문장"   "을"     "입력하" "ㅂ니다" "."

[[2]]
[1] "여러"   "문장"   "을"     "넣"     "습니다" "."

> nouns(t)

[[1]]
[1] "문장"

[[2]]
[1] "문장"

> verbs(t)

[[1]]
[1] "입력하"

[[2]]
[1] "넣"

사용자 사전

사용자 사전은 조회, 수정, 삭제가 가능합니다. 사용사 사전은 각 API키 단위로 따로따로 관리됩니다. 다른 사용자랑 같은 이름을 사용하더라도 충돌이 발생하지 않습니다.

사용자 사전은 지정하고 싶은 내용만 정의하면 됩니다.

사용자 사전은 Tagger 클래스를 통해서 접근하도록 구성됩니다. 한번 저장된 사전은 다시 재사용이 가능합니다.

import json

from bareunpy import Tagger

# 설치한 자신의 호스트에 접속합니다.
# 아래에 "https://bareun.ai/"에서 이메일 인증 후 발급받은 API KEY("koba-...")를 입력해주세요. "로그인-내정보 확인"
my_tagger = Tagger('YOUR-API-KEY', 'localhost', port=5656) # <- 본인의 API KEY로 교체 

# 사용자 사전 저장
# 사전 이름으로 영문, 숫자, 기호('-', '_')만 사용 가능
# 사전 단어에 기호는 추가될 수 없습니다(복합명사 분리사전의 ^ 기호 제외).
cust_dic = my_tagger.custom_dict("my_dict_01")
cust_dic.copy_np_set({'내고유명사', '걸어서세계속으로', '크리스토퍼놀란'})
cust_dic.copy_cp_set({'코로나19', '바나나우유'})
cust_dic.copy_cp_caret_set({'코로나^백신', '디지털^인문학'})
cust_dic.copy_vv_set({'카톡하다', '맑내하다'})
cust_dic.copy_va_set({'로맨틱하다', '신박하다', '판타스틱하다'})
cust_dic.update()

# 이전 사용자 사전 불러오기
cust_dict2 = my_tagger.custom_dict("my_dict_01")
cust_dict2.load()

text = '내고유명사로 존재하는 "크리스토퍼놀란"은 코로나19 상황 속에서도 걸어서세계속으로라는\
        TV 프로그램을 통해 바나나우유를 마시며 카톡하다가 \
        떠오른 신박한 아이디어로 판타스틱한 로맨틱 영화를 만들 계획이며, \
        그 과정에서 디지털인문학의 도움을 받아 코로나백신에 대한 사회적 이슈도 함께 다루려 한다.'

# pos 정보만 출력
my_tagger.set_domain('my_dict_01')
response = my_tagger.pos(text)
print(f'형태소 분석 결과: {response}')

# 형태소 분석 결과를 딕셔너리 형태로 저장
response2 = my_tagger.tags([text])
res_dict = json.loads(response2.as_json_str())
idx = 1
for token in res_dict['sentences'][0]['tokens']:
    custom_token = [f"{t['text']['content']}/{t['tag']}, {t['outOfVocab']}" \
                      for t in token['morphemes'] if t['outOfVocab'] == 'IN_CUSTOM_DICT']
    if len(custom_token) != 0:
        print(f"{idx}: {custom_token}")
        idx += 1
import static org.junit.Assert.assertTrue;

import java.nio.file.Path;
import java.util.List;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;

import ai.bareun.tagger.*;
import groovy.ui.Console;
import ai.bareun.protos.AnalyzeSyntaxRequest;
import ai.bareun.protos.AnalyzeSyntaxResponse;
import ai.bareun.protos.CustomDictionary;
import ai.bareun.protos.CustomDictionaryMeta;
import ai.bareun.protos.Document;
import ai.bareun.protos.LanguageServiceGrpc;

/**
* Unit test for simple App.
*/
public class AppTest
{
    // 아래에 "https://bareun.ai/"에서 이메일 인증 후 발급받은 API KEY("koba-...")를 입력해주세요. "로그인-내정보 확인"
    private static Logger logger = LoggerFactory.getLogger(AppTest.class.getSimpleName());
    String API_KEY="koba-ABCDEFG-1234567-LMNOPQR-7654321" // <- 본인의 API KEY로 교체 
    void log(Object str){

        if( str instanceof String ) {
            logger.info(str.toString());
        }  else {
            Gson gson = new Gson();
            logger.info(gson.toJson(str));
        }

    }
    /**
    * Rigorous Test :-)
    */
    @Test
    public void shouldAnswerWithTrue()
    {

        LanguageServiceClient conn = new ai.bareun.tagger.LanguageServiceClient("localhost", API_KEY);
        AnalyzeSyntaxResponse response =  conn.analyze_syntax("아버지가 방에 들어가신다.");
        String str = conn.toJson();
        assertTrue( !str.isEmpty() );
        logger.info(str);
    }

    static String TestString = "아버지가 방에 들어가신다.";
    // "윤석열 대통령이 취임 220일차인 오는 15일 대국민 소통의 일환으로 ‘국정과제 점검회의’를 진행한다.\n 취임 후 처음 국민과 대면해 생방송으로 대화를 나눈다는 점에서 취지에 걸맞는 소통이 얼마나 이뤄질지 관심이다.";

    @Test
    public void testCustomDict() {
        CustomDict dict = new CustomDict("game", "localhost", undefined, API_KEY);

        log("testCustomDict()");
        log("read file.");

        String curdir = System.getProperty("user.dir");
        Path path = Path.of( curdir, "testdict.txt");
        if( dict.read_np_set_from_file(path.toString()) <= 0 ) {
            log("file io error : " + path.toString());
            return ;
        }
        log(dict.getSet("np_set"));

        Boolean r = dict.update();
        assertTrue(r);

        dict.load();
        log("update and load.");
        log(dict.getSet("np_set"));

        log("get list ");
        List<CustomDictionaryMeta> list = dict.get_list();
        for(CustomDictionaryMeta meta: list) {
            log(meta.getDomainName());
        }

    }

    static public void main() {
        AppTest theApp = new AppTest();
        theApp.shouldAnswerWithTrue();
        theApp.testCustomDict();
    }

}
// 아래에 "https://bareun.ai/"에서 이메일 인증 후 발급받은 API KEY("koba-...")를 입력해주세요. "로그인-내정보 확인"
let host="nlp.bareun.ai"
let API_KEY="koba-ABCDEFG-1234567-LMNOPQR-7654321" // <- 본인의 API KEY로 교체 
let {LanguageServiceClient, Tagger, CustomDict}  = require("bareun");
let language_service_client = new LanguageServiceClient(host);

let dict = new CustomDict("game", host, undefined, API_KEY);

(async () => {
    let set = new Set(["지지", "캐리", "던전", "현피", "세계관", "만렙","어그로","치트키","퀘스트","본캐","로밍","방사","딜러","버스","사플" ] );
    dict.copy_cp_set(set);

    let success = await dict.update();
    console.log("result :  dict.update() - " + success);

    await dict.read_np_set_from_file(__dirname + "/game_dict.txt");
    console.log("result :  dict.load() - " + JSON.stringify([...dict.word_sets.np_set]));
    let success = await dict.update();
    console.log("result :  dict.update() - " + success);

    let res = await dict.client.async_get_list();
    console.log("async_get_list() : " + JSON.stringify(res, null, 2));

    await dict.load();

    let res = await dict.clear();
    console.log("clear() : " + JSON.stringify(res, null, 2));
})();
> library(RProtoBuf)
> library(bareun)

# 아래에 "https://bareun.ai/"에서 이메일 인증 후 발급받은 API KEY("koba-...")를 입력해주세요. "로그인-내정보 확인"
> set_key("YOUR_API_KEY") # <- 본인의 API KEY로 교체 
> t <- tagger()
> text <- "문장을 입력합니다.\n여러 문장을 넣습니다."
> pos(t, text)

> np <- c("청하", "트와이스", "티키타카", "TIKITAKA", "오마이걸")
> cp <- c("자유여행", "방역당국", "코로나19", "주술부", "완전주의")
> caret <- c("주어^역할", "주어^술어^구조", "하급^공무원")
> vv <- c("카톡하다", "인스타하다")
> va <- c("혜자스럽다", "창렬하다")
> make_custom_dict(t, "sample", np, cp, caret, vv, va)

[1] "sample : 업데이트 성공"

단어 분리

단어 분리는 형태소 태깅을 하지 않고 분절만은 수행하는 기능입니다. 이 기능은 bareunpy 라이브러리에 있는 tokenize라는 함수로 구현되어 있습니다.

이 기능은 현재 파이쎤에서만 구현되어 있습니다.

tokenize 관련 문서가 아직 부족해요.

아직 이 기능과 관련된 제대로 된 REST 문서를 만들지 못했습니다. 시간이 날 때 만들어 놓을게요. 하지만, [bareun.ai]에서 사용해보기/단어분리를 클릭하여 사용해보실 수 있어요. 이 기능은 거대 언어모델의 한국어 성능 향상을 위해서도 필요한 기능입니다.

import sys
import google.protobuf.text_format as tf
from bareunpy import Tokenizer

# 아래에 "https://bareun.ai/"에서 이메일 인증 후 발급받은 API KEY("koba-...")를 입력해주세요. "로그인-내정보 확인"
API_KEY = 'koba-ABCDEFG-1234567-LMNOPQR-7654321' # <- 본인의 API KEY로 교체 

# If you have your own localhost bareun.
my_tokenizer = Tokenizer(API_KEY, 'localhost')
# or if you have your own bareun which is running on 10.8.3.211:15656.
my_tokenizer = Tagger(API_KEY, '10.8.3.211', 15656)


# print results.
tokenized = my_tokenizer.tokenize_list(["안녕하세요.", "반가워요!"])

# get protobuf message.
m = tokenized.msg()
tf.PrintMessage(m, out=sys.stdout, as_utf8=True)
print(tf.MessageToString(m, as_utf8=True))
print(f'length of sentences is {len(m.sentences)}')
## output : 2
print(f'length of tokens in sentences[0] is {len(m.sentences[0].tokens)}')
print(f'length of segments of first token in sentences[0] is {len(m.sentences[0].tokens[0].segments)}')
print(f'tagged of first token in sentences[0] is {m.sentences[0].tokens[0].tagged}')
print(f'first segment of first token in sentences[0] is {m.sentences[0].tokens[0].segments[0]}')
print(f'hint of first morph of first token in sentences[0] is {m.sentences[0].tokens[0].segments[0].hint}')

## Advanced usage.
for sent in m.sentences:
    for token in sent.tokens:
        for m in token.segments:
            print(f'{m.text.content}/{m.hint}')

# get json object
jo = tokenized.as_json()
print(jo)

# get tuple of segments
ss = tokenized.segments()
print(ss)
ns = tokenized.nouns()
print(ns)
vs = tokenized.verbs()
print(vs)
# postpositions: 조사
ps = tokenized.postpositions()
print(ps)
# Adverbs, 부사
ass = tokenized.adverbs()
print(ass)
ss = tokenized.symbols()
print(ss)

도움이 되었나요?