📚 시리즈 — 한국어 RAG 검색 (Kiwi)
- ① 한국어 BM25·토크나이저 선택과 Kiwi 기본 설정 — 현재 글
- ② 사용자·동의어 사전과 복합명사 처리
- ③ 검색 품질 개선·사전 관리·Vector Search 통합
- ④ 부록 — 동의어 적용 위치·IATA 사전 생성/검증·품사·Nori 비교
1. 개요 및 목적
1.1 본 가이드의 목적
본 가이드는 A항공 매뉴얼 RAG 의 한국어 검색 품질을 일관되게 운영하기 위한 토크나이저·동의어 처리 표준을 정의합니다. 한국어는 어근에 조사·어미·접사가 결합하는 교착어이고 매뉴얼에는 IATA·기재 코드·복합명사가 빈출하므로, 단순한 공백 토큰화로는 BM25 검색의 recall 이 크게 떨어집니다. 본 가이드는 다음 네 가지를 Single Source of Truth (SSOT) 로 다룹니다:
- 한국어 형태소 분석기 Kiwi 가 가진 사용자 사전·검색 품질 개선 기능을 어떻게 활용할지
- 라이브러리가 지원하지 않는 동의어 확장을 어디서 어떻게 구현할지
- 사용자·동의어 사전을 UC Volume 에서 어떻게 관리할지
- LangGraph Retriever Node 와 Databricks Vector Search HYBRID 검색에 어떻게 연결할지
1.2 적용 범위
본 가이드의 Kiwi 호출은 문서 전처리(배치) · 검색(실시간) 두 단계입니다. 그 사이에 끼는 VSI sync 단계는 Vector Search 가 Delta 컬럼을 source 로 BM25·HNSW 색인을 구축하는 단계로, Kiwi 호출이 없습니다.
| 단계 | 동작 흐름 | 컴퓨트 환경 | 비고 |
|---|---|---|---|
| 문서 전처리 (배치) | 원본 chunk → Kiwi 토큰화 + 임베딩 → Delta 컬럼(chunk_tokenized*, chunk_embedding) 적재 |
Databricks Jobs (A항공 채택 = Classic Job Cluster, §10.1) + Spark Pandas UDF | §10.1 Iterator Pandas UDF 패턴 |
| VSI sync (자동) | Delta 컬럼 → create_delta_sync_index(columns_to_sync=[...]) → VSI (BM25 + HNSW) |
Vector Search Delta Sync (관리형) | Kiwi 미호출. self-managed embedding 이므로 임베딩 모델도 미호출 |
| 검색 (실시간) | 사용자 쿼리 → Kiwi 토큰화 (+ 동의어 OR 확장) → similarity_search(query_text, query_vector) → top-K |
Model Serving 에 배포된 RAG 에이전트 (LangGraph) | §10.2 module-global lazy init + rolling update 무중단 (§9.4 hot reload) |
2. 한국어 토큰화와 BM25
2.1 한국어 특성
한국어는 어근에 조사·어미·접사가 차례로 결합하여 한 어절(공백 단위 단어) 안에 여러 형태소가 압축됩니다.
위탁수하물의=위탁(NNG) + 수하물(NNG) + 의(JKG)예약하셨습니다=예약(NNG) + 하(XSV) + 시(EP) + 었(EP) + 습니다(EF)킬로그램까지는=킬로그램(NNB) + 까지(JX) + 는(JX)
세 문제가 동시에 발생합니다.
- 조사·어미 변형 — 같은 명사
수하물이수하물,수하물을,수하물의,수하물로등 수십 가지 표면형으로 등장합니다. 단순 공백 토큰화로는 모두 다른 토큰으로 처리됩니다. - 띄어쓰기 임의성 — 정형 매뉴얼에서도
위탁수하물과위탁 수하물이 혼재합니다. 질의위탁수하물 무게가 본문위탁 수하물의 무게 제한과 매칭되지 않을 수 있습니다. - 복합명사 빈도 —
수하물허용량,초과수하물요금,리튬배터리같은 복합명사가 본문 키워드로 그대로 사용됩니다. 분해 없이는 부분 매칭이 어렵습니다.
2.2 BM25 점수 구조
BM25 는 질의 토큰별로 세 가지 항 — TF(Term Frequency, 문서 내 빈도) · IDF(Inverse Document Frequency, 토큰 희소성) · 문서 길이 가중치 — 의 곱을 합산해 점수를 매깁니다.
score(D, Q) = Σᵢ TF̃(qᵢ, D) × IDF(qᵢ) × LengthNorm(D)
| 항 | 직관 | 본 가이드 정책 연계 |
|---|---|---|
TF̃(qᵢ, D) (Term Frequency) |
문서 안 등장 빈도. k₁ 으로 포화 보정되어 반복해도 점수가 무한히 오르지 않음 |
정책 B (혼합 색인) 가 통째+분해 두 패스 union 으로 의도적 TF 가중 (§7.4) |
IDF(qᵢ) (Inverse Document Frequency) |
토큰의 희소성. 흔한 토큰은 0 에 가깝고 희소할수록 큼 (코퍼스 전체 통계 N·df 로 결정) |
동의어를 인덱스 시 확장하면 df 가 부풀어 IDF 가 평탄해짐 — Case A 금지 근거 (부록 A.2) |
LengthNorm(D) |
문서 길이 가중치. avgdl 보다 길면 페널티, b 가 강도 (0 = 페널티 없음, 1 = 완전 정규화) |
정책 B 도입 후 평균 문서 길이 증가 → b 재튜닝 근거 (정책 B 도입 시) |
Databricks Vector Search 기본값은 k₁ = 1.2, b = 0.75 이며 컬렉션 통계 N(전체 문서 수)·avgdl(평균 길이) 은 인덱스가 자동 계산합니다. 토큰화 정책이 바뀌면 두 통계가 따라 변하므로 정책 B 같이 토큰 수가 늘면 b 재튜닝이 필요합니다.
여기서 핵심은 토큰 정규화가 곧 recall 이라는 점입니다 — 인덱싱 단계에서 수하물의 가 수하물 로 정규화되지 않으면, 질의 수하물 이 들어왔을 때 BM25 는 두 토큰을 무관한 단어로 처리하여 점수가 0 에 가까워집니다.
전체 정밀 공식·로그 형태 IDF·확률론적 유도는 Robertson & Zaragoza (2009) 참조 (Robertson, S. & Zaragoza, H. (2009) — The Probabilistic Relevance Framework: BM25 and Beyond (PDF)).
2.3 토큰화 파이프라인 시각화

킬로그램은 의존명사(NNB) 이므로 기본 화이트리스트에서 제외되지만, 사용자 사전으로NNG승격하면 색인에 포함됩니다 (§5 참조).
3. 토크나이저 선택
§1.1 에서 결과로만 명시한 "토크나이저로 채택한 Kiwi" 의 비교·결정 과정을 짧게 정리합니다. 외부 BM25 벤치마크와 kiwipiepy 공식 매뉴얼을 근거로 활용했습니다.
3.1 한국어 형태소 분석기 후보
| 분석기 | 코어 | Python 바인딩 | 사용자 사전 API | 라이선스 |
|---|---|---|---|---|
| Kiwi | C++ | kiwipiepy (v0.23+) |
풍부 (런타임 5 API) | LGPL |
| Mecab-ko | C++ (mecab fork) | python-mecab-ko / konlpy |
mecab-ko-dic 외부 빌드 | BSD/LGPL |
| Khaiii | C++ (CNN) | khaiii (Kakao) |
YAML 사용자 사전 | Apache 2.0 |
| Komoran | Java | PyKomoran / konlpy |
TXT 외부 사전 | Apache 2.0 |
| Kkma | Java | konlpy |
외부 (수동 빌드) | GPLv2 |
| Okt (구 Twitter) | Scala/Java | konlpy |
없음 | Apache 2.0 |
| Hannanum | Java | konlpy |
없음 | GPL |
라이선스·활성 유지보수 시기는 각 repo 의 일반 알려진 정보 기반 추정입니다. 최종 채택 전 각 GitHub LICENSE·마지막 커밋 시점 재확인을 권장합니다.
3.2 Kiwi 채택 사유
본 가이드는 Databricks 환경 + LangGraph + UC Volume 운영 컨텍스트에서 다음 네 가지 사유로 Kiwi 를 채택합니다.
- 풍부한 사용자 사전 API —
add_user_word·load_user_dictionary·add_pre_analyzed_word·add_rule·add_re_rule5 종 런타임 등록 API. 외부 사전 빌드 없이 즉시 반영되어 hot reload 와 시너지가 큼. JVM 후보·Mecab-ko 의 정적 사전 빌드 사이클과 대비 (kiwipiepy 매뉴얼). - 복합명사 분해 + LM 옵션 —
split_complex가위탁수하물허용량같은 즉석 합성어를 LM 기반으로 분해. KNLM/SBG/CoNg +cong-global실험판으로 정확도·속도 트레이드오프 운영 측 조정 가능 — 후보 중 명시적 LM 옵션 노출은 Kiwi 만. - Databricks 환경 적합 — pure Python 휠 + C++ 코어. Pandas UDF·Model Serving 모두
pip install kiwipiepy한 줄 설치. JVM 후보(Komoran·Kkma·Okt) 대비 컨테이너 부팅·메모리 부담 적음. - 한국어 BM25 벤치마크 — AutoRAG 한국어 BM25 토크나이저 벤치마크에서 Mecab·Kiwi 가 상위 성능 보고. 본 가이드는 사용자 사전 API·LM 옵션 격차 로 Kiwi 채택 Making benchmark of different tokenizer in BM25... (footnote 출처: 부록 A.6).
LGPL 영향 — Kiwi 의 LGPL 은 재배포 시 라이브러리 소스 공개 의무가 따릅니다. Databricks 컨테이너 내부에서
import호출은 재배포가 아니며, 운영팀이 작성한 사용자 사전 TSV·동의어 CSV·LangGraph 노드 코드는 LGPL 영향을 받지 않습니다. 사내 사용·외부 미배포 조건에서 위험은 낮으나, 채택 전 사내 법무 검토를 권장합니다.
본 가이드는 Kiwi (kiwipiepy ≥ 0.23.1) 을 표준으로 채택합니다.
4. Kiwi 기능과 권장 설정
본 절은 Kiwi 토크나이저의 기본 사용 (§4.1) 과 검색 품질 개선 옵션 (§8 — 오타 교정·언어모델·정규화) 을 다룹니다. 사용자 사전 (§5), 동의어 사전 (§6), 복합명사 처리 (§7) 는 별도 chapter 정본. 두 사전의 적용 시점 결정 은 §5.6 (사용자 사전 양쪽 동일) / §6.4 (동의어 검색 시점만). 운영 호출 패턴·공유 모듈 코드는 §10 정본.
4.1 Kiwi 기본 사용
Kiwi (KOrean Intelligent Word Identifier) 는 bab2min 이 개발한 C++ 형태소 분석기이며, Python 바인딩 kiwipiepy 로 제공됩니다. LM(언어모델) 기반 N-best 분석으로 사전 후보를 점수화하기 때문에 사용자 사전·LM 옵션 이 풍부합니다 (§3.2 채택 사유 참조).
from kiwipiepy import Kiwi
kiwi = Kiwi(load_default_dict=True, integrate_allomorph=True)
tokens = kiwi.tokenize("위탁수하물 무게 제한이 23킬로그램입니다.")
for tok in tokens:
print(tok.form, tok.tag, tok.start, tok.len)
# 위탁 NNG 0 2
# 수하물 NNG 2 3
# 무게 NNG 6 2
# ...
| API | 반환 | 용도 |
|---|---|---|
kiwi.tokenize(text, ...) |
list[Token] (top-1) |
가장 흔한 단일 호출 — 인덱싱·검색 양쪽에서 사용 |
kiwi.analyze(text, top_n=N) |
list[(분석결과, score)] (top-N) |
후보 비교가 필요한 디버깅·평가 |
kiwi.split_into_sents(text) |
list[Sentence] |
문장 분할 |
각 Token 객체는 form(표면형)·tag(세종 품사 태그)·start(원문 offset)·len(길이) 을 포함합니다. 품사 태그 정의는 부록 D 참조.
기본 옵션 두 가지가 형태소 분해 결과에 영향을 줍니다.
load_default_dict=True(기본) — Kiwi 내장 사전 로드. 운영에서는 항상 True.integrate_allomorph=True(기본) — 이형태(예:었/았,면서/으면서) 통합. 인덱싱·검색 측 표준화에 유리.
4.2 전성·파생 명사 토큰 보존 (어간+ETN · 명사+XSN 후처리 병합)
Kiwi 는 각 형태소를 form·tag·start·len 으로 돌려주는데, 일부 명사는 어근 형태소가 별도 형태소로 분해되어 BM25 색인에서 누락되거나 문맥에 따라 들쭉날쭉 쪼개집니다. 두 유형이 대표적입니다.
유형 1 — 어간 + 명사형 전성어미(ETN: ㅁ/음/기) — §4.1 예제의 두 번째 출력(운항 지연 알림을 받았습니다.)에서 보듯, 알림 이 알리/VV + ㅁ/ETN 으로 쪼개지고 ㅁ 은 온전한 음절이 아닌 자모 한 글자 가 됩니다. 참고로 OpenSearch Nori(mecab-ko-dic 기반) 표준 분석기도 알림 을 통째 명사로 보유하지 않고 알리(VV 어간)로 분해합니다 — 차이는 POS 필터 정책뿐으로, Nori 기본 stoptags 는 동사 어간을 색인에 남겨 알리 가 들어가는 반면, 본 가이드 Kiwi 화이트리스트는 명사만 남겨 알림 이 0 토큰 이 됩니다. 즉 두 분석기 모두 표면형 알림 을 보존하지 않으므로, 후처리 병합은 양쪽 대비 고유한 가치를 가집니다 (실측 비교: 부록 E.3).
유형 2 — 명사 + 파생접미사(XSN) — 할인권 은 문맥에 따라 할인권/NNG(통째) 또는 할인/NNG + 권/XSN(분해)로 갈립니다. 후자는 권/XSN 이 화이트리스트에 없어 탈락하고 할인 으로만 색인됩니다. 선생님(선생/NNG + 님/XSN)·가능성(가능/NNG + 성/XSN) 등도 같은 구조입니다.
BM25 영향 — §2.3 화이트리스트(NNG·NNP·SL·SN·SH) 에 VV·ETN·XSN 이 없으므로 분해된 어간·어미·접미사는 색인에서 탈락합니다. 더 큰 문제는 문맥 의존적 비일관성입니다 — 문서 할인권 사용 은 할인권 으로 색인되는데 질의 할인권 은 할인 으로 토큰화되거나(또는 그 반대), 인덱스·질의 토큰이 어긋나면 BM25 매칭이 0 이 됩니다.
두 가지 대응
- 사용자 사전 per-word
NNG승격 (§5.2) — 정확하지만 전성·파생 명사가 많아 사전 관리 부담이 큽니다. - 후처리 병합 (사전 0 건, 본 절 권장) —
어간 + ETN·명사 + XSN패턴을start/len으로 원문 표면형을 떠서 단일NNG토큰으로 되붙입니다.Match.JOIN_*옵션은 용언 파생접미사(XSV/XSA)만 합치고ETN·XSN은 미대상이므로, 토큰 리스트 후처리가 가장 단순합니다.
KEEP_TAGS = {"NNG", "NNP", "SL", "SN", "SH"} # §2.3 색인 화이트리스트
STEM_TAGS = {"VV", "VA", "XSV", "XSA", "VX"} # 어간류(용언·파생접미사)
NOUN_TAGS = {"NNG", "NNP"} # 명사
def merge_derived_nouns(text, tokens):
"""파생·전성 명사를 원문 표면형 1토큰(NNG)으로 병합.
- 어간(STEM) + ㅁ/음/기(ETN) → 알림, 찾기 (전성명사)
- 명사(NN) + 파생접미사(XSN) → 할인권, 선생님 (파생명사)
명사 핵 뒤에 이어지는 XSN 은 연속으로 흡수합니다.
"""
out, i, n = [], 0, len(tokens)
while i < n:
t = tokens[i]
j = None
if i + 1 < n and t.tag in STEM_TAGS and tokens[i + 1].tag == "ETN":
j = i + 1 # 어간 + ETN 핵
elif t.tag in NOUN_TAGS:
j = i # 명사 핵
if j is not None:
k = j + 1
while k < n and tokens[k].tag == "XSN": # 뒤따르는 파생접미사 흡수
k += 1
if k > j + 1 or j > i: # 실제 병합 대상이 있을 때만
last = tokens[k - 1]
out.append((text[t.start:last.start + last.len], "NNG")) # start/len = 전 버전 안전
i = k
continue
out.append((t.form, t.tag)); i += 1
return out
Token.span(=(start, end)튜플) 으로text[t.span[0]:last.span[1]]도 동일 결과지만,span은 비교적 최근 추가된 편의 속성이라start/len조합이 전 버전에서 안전합니다.
이 병합을 거치면 알림·찾기(ETN), 할인권·선생님·가능성(XSN) 같은 파생·전성 명사가 표면형으로 색인되며, 이미 NNG 로 잡히는 모임·문제점·장난감 등은 영향을 받지 않습니다.
트레이드오프 — (1) 어휘화 안 된 명사형(그가 옴을 → 옴)까지 병합하지만, BM25 에선 IDF 가 낮아 노이즈가 거의 없고 인덱스·질의가 같은 표면형으로 수렴해 오히려 유리합니다. (2) XSN 병합은 할인권 을 통째 1토큰으로 색인하므로 질의 할인(단독)은 BM25 정확매칭에서 할인권 문서를 놓칠 수 있으나, 권/XSN 은 단독 색인 가치가 없고 부분매칭은 Hybrid 의 dense 채널이 보완합니다 (§7.4 정책 A 와 동일 논리). (3) 들것(= 들/VV + ㄹ/ETM + 것/NNB, ETM+의존명사) 은 본 규칙 밖이며, 문맥이 있으면 Kiwi 가 이미 들것/NNG 로 잡습니다.
운영 코드에서는 이 병합을 §10.3 공유 모듈에 두어 인덱싱·검색 양쪽이 동일하게 거치도록 강제합니다.
📚 시리즈 — 한국어 RAG 검색 (Kiwi)
- ① 한국어 BM25·토크나이저 선택과 Kiwi 기본 설정 — 현재 글
- ② 사용자·동의어 사전과 복합명사 처리
- ③ 검색 품질 개선·사전 관리·Vector Search 통합
- ④ 부록 — 동의어 적용 위치·IATA 사전 생성/검증·품사·Nori 비교
'Databricks > RAG' 카테고리의 다른 글
| RAG 품질 ③ 아키텍처 — LangGraph 기반 Agentic RAG 검색 처리 흐름 (0) | 2026.07.04 |
|---|---|
| RAG 품질 ① 평가 — MLflow Evaluation 기반 검색·생성 품질 정량 측정 (0) | 2026.07.04 |
| 한국어 RAG 검색 (Kiwi) ④ 부록 — 동의어 적용 위치·IATA 사전 생성/검증·품사·Nori 비교 (0) | 2026.06.28 |
| 한국어 RAG 검색 (Kiwi) ③ 검색 품질 개선·사전 관리·Vector Search 통합 (0) | 2026.06.28 |
| 한국어 RAG 검색 (Kiwi) ② 사용자·동의어 사전과 복합명사 처리 (0) | 2026.06.28 |
댓글