Skip to main content

3. Kiwi 기반 BM25 Retriever

기본 BM25는 공백 기반으로 텍스트를 분절하므로, 한국어에서는 조사가 붙은 채로 토큰화됩니다. Kiwi로 형태소 분석 후 명사/동사/외국어만 추출 하면 검색 품질이 크게 향상됩니다.
from kiwipiepy import Kiwi
from langchain_community.retrievers import BM25Retriever

kiwi = Kiwi()

def kiwi_tokenize(text: str) -> list[str]:
    """Kiwi 형태소 분석기로 명사/동사/형용사/외국어만 추출"""
    tokens = kiwi.tokenize(text)
    # NNG(일반명사), NNP(고유명사), VV(동사), VA(형용사), SL(외국어)
    return [t.form for t in tokens if t.tag in ('NNG', 'NNP', 'VV', 'VA', 'SL')]

# Kiwi 토크나이저를 사용하는 BM25 Retriever
bm25_retriever = BM25Retriever.from_documents(
    documents,
    preprocess_func=kiwi_tokenize,
    k=5
)

# "데이터브릭스에서 RAG를 구축하는 방법" →
# kiwi_tokenize → ["데이터브릭스", "RAG", "구축", "방법"]
results = bm25_retriever.invoke("데이터브릭스에서 RAG를 구축하는 방법")

Kiwi + Ensemble Retriever

한국어 RAG에서 가장 효과적인 조합은 Kiwi BM25 + Dense (Vector Search) 앙상블입니다:
from langchain.retrievers import EnsembleRetriever
from langchain_databricks import DatabricksVectorSearch

# Kiwi 기반 BM25
bm25_retriever = BM25Retriever.from_documents(
    documents, preprocess_func=kiwi_tokenize, k=5
)

# Databricks Vector Search (다국어 임베딩)
vs_retriever = DatabricksVectorSearch(
    endpoint="vs-endpoint",
    index_name="catalog.schema.ko_docs_index",
    columns=["content", "source"]
).as_retriever(search_kwargs={"k": 5})

# 앙상블
ensemble = EnsembleRetriever(
    retrievers=[bm25_retriever, vs_retriever],
    weights=[0.4, 0.6]
)
한국어 전문 용어가 많은 도메인(법률, 의료 등)에서는 BM25 가중치를 0.5~0.6으로 높이면 정확한 용어 매칭이 강화됩니다.

4. 한국어 청킹 전략

전략설명장점단점
문장 기반 (KSS)한국어 문장 경계 인식자연스러운 분절문장이 짧으면 청크가 너무 작음
형태소 기반Kiwi로 의미 단위 분절정확한 의미 보존구현 복잡
Semantic 청킹임베딩 유사도 기반 경계 결정의미 전환점 자동 감지연산 비용 높음
Recursive + 한국어 구분자한국어 종결어미 기반 분절범용적, 구현 간단구분자 설계 필요

KSS (Korean Sentence Splitter) 활용

# 설치: pip install kss
import kss

text = """Databricks는 데이터와 AI를 위한 통합 플랫폼입니다.
Delta Lake를 기반으로 데이터 레이크하우스 아키텍처를 제공합니다.
Unity Catalog로 데이터 거버넌스를 통합 관리할 수 있습니다."""

sentences = kss.split_sentences(text)
for s in sentences:
    print(s)
# Databricks는 데이터와 AI를 위한 통합 플랫폼입니다.
# Delta Lake를 기반으로 데이터 레이크하우스 아키텍처를 제공합니다.
# Unity Catalog로 데이터 거버넌스를 통합 관리할 수 있습니다.

RecursiveCharacterTextSplitter + 한국어 구분자

from langchain.text_splitter import RecursiveCharacterTextSplitter

korean_splitter = RecursiveCharacterTextSplitter(
    separators=[
        "\n\n",    # 문단 구분
        "\n",      # 줄바꿈
        "다. ",    # 평서문 종결
        "요. ",    # 존댓말 종결
        "까? ",    # 의문문 종결
        ". ",      # 일반 마침표
        " ",       # 공백
    ],
    chunk_size=500,
    chunk_overlap=50,
    length_function=len,
)

chunks = korean_splitter.split_text(long_korean_text)
참고 한국어에서 RecursiveCharacterTextSplitter를 사용할 때는 종결어미(다. , 요. )를 구분자에 추가하면 문장 중간에서 잘리는 것을 방지할 수 있습니다.

5. 한국어 임베딩 모델 선택 가이드

임베딩 모델 선택은 RAG 검색 품질에 직접적인 영향을 미칩니다. 한국어 환경에서는 다국어 모델한국어 특화 모델 중 사용 환경에 맞는 것을 선택해야 합니다.

multilingual-e5 vs KoSimCSE: 언제 어떤 모델을 선택할 것인가

기준multilingual-e5-large-instructKoSimCSE (SKT)bge-m3 (BAAI)
한영 혼용 문서최적영어 성능 약함우수
순수 한국어 문서우수최적우수
Databricks 기본 제공가능 (Foundation Model API)불가 (직접 배포 필요)불가 (직접 배포 필요)
운영 복잡도낮음높음 (Model Serving 배포)높음 (Model Serving 배포)
추론 속도보통빠름 (768차원)보통
Dense + SparseDense만Dense만둘 다 지원
권장 선택 기준:
  1. 빠른 시작 + 한영 혼용: multilingual-e5-large-instruct (Databricks 기본 제공, 추가 배포 불필요)
  2. 순수 한국어 + 최고 품질: KoSimCSE-roberta-multitask (한국어 STS 벤치마크 최상위)
  3. 하이브리드 검색 내장: bge-m3 (Dense + Sparse 벡터를 하나의 모델에서 동시 생성)
  4. 비용 최적화: gte-multilingual-base (768차원, 빠른 추론, 스토리지 절약)

모델 비교 테이블

모델차원한국어 성능Databricks 지원비고
multilingual-e5-large-instruct1024우수Foundation Model API다국어, Databricks 기본 제공
bge-m3(BAAI)1024우수Model Serving 배포Dense + Sparse 하이브리드 지원
KoSimCSE(SKT)768매우 우수Model Serving 배포한국어 특화, STS 벤치마크 상위
gte-multilingual-base(Alibaba)768우수Model Serving 배포경량, 빠른 추론 속도

KoSimCSE를 Model Serving에 배포하는 방법

한국어 특화 모델을 사용하려면 Databricks Model Serving에 직접 배포해야 합니다.
import mlflow
from sentence_transformers import SentenceTransformer

# 1. 모델 로드 및 MLflow에 로깅
model = SentenceTransformer("BM-K/KoSimCSE-roberta-multitask")

with mlflow.start_run():
    mlflow.sentence_transformers.log_model(
        model=model,
        artifact_path="kosimcse",
        registered_model_name="catalog.schema.kosimcse_embedding",
        input_example=["한국어 임베딩 테스트"],
    )

# 2. Model Serving Endpoint 생성
from databricks.sdk import WorkspaceClient
from databricks.sdk.service.serving import EndpointCoreConfigInput, ServedEntityInput

w = WorkspaceClient()
w.serving_endpoints.create_and_wait(
    name="kosimcse-embedding",
    config=EndpointCoreConfigInput(
        served_entities=[
            ServedEntityInput(
                entity_name="catalog.schema.kosimcse_embedding",
                entity_version="1",
                workload_size="Small",
                scale_to_zero_enabled=True,
            )
        ]
    ),
)

Databricks Foundation Model API 활용

from langchain_databricks import DatabricksEmbeddings

# Databricks에서 기본 제공하는 다국어 임베딩
embeddings = DatabricksEmbeddings(
    endpoint="databricks-gte-large-en"  # 또는 커스텀 배포 엔드포인트
)

# 한국어 텍스트 임베딩
vectors = embeddings.embed_documents([
    "Databricks에서 RAG 파이프라인을 구축합니다",
    "데이터 레이크하우스 아키텍처 개요"
])
한국어 전용 임베딩 모델(KoSimCSE 등)은 한국어 내부 유사도 측정에서는 뛰어나지만, 한영 혼용 문서가 많은 환경에서는 다국어 모델(multilingual-e5-large-instruct, bge-m3)이 더 적합합니다.

6. 한국어 RAG 베스트 프랙티스

권장 구성

단계권장 도구/전략이유
토크나이저Kiwi + KSS 조합형태소 분석 + 문장 분리
청킹Recursive + 한국어 구분자종결어미 기반 자연스러운 분절
임베딩multilingual-e5-large-instructDatabricks 기본 제공, 한영 혼용 지원
검색Hybrid (Kiwi BM25 + Vector)키워드 + 의미 검색 결합
재정렬bge-reranker-v2-m3다국어 Reranker, 한국어 지원

왜 Re-ranking이 한국어 RAG에서 특히 중요한가

위 테이블에서 재정렬(Re-ranking) 을 권장하는 이유를 깊이 살펴봅니다. Re-ranking은 단순히 “검색 결과를 더 잘 정렬하는 것”이 아니라, LLM이 최종 답변을 생성하는 품질에 직접적으로 영향 을 미칩니다. “Lost in the Middle” 문제: LLM은 컨텍스트의 위치에 민감하다 2023년 스탠포드 연구(“Lost in the Middle: How Language Models Use Long Contexts”)에서 밝혀진 핵심 발견은, LLM이 긴 컨텍스트를 받았을 때 처음과 끝에 있는 정보는 잘 활용하지만, 중간에 있는 정보는 간과하는 경향 이 있다는 것입니다.
LLM에 전달되는 컨텍스트 (5개 문서):

  [문서 1] ← 높은 활용도 ✅  (컨텍스트의 시작)
  [문서 2] ← 보통
  [문서 3] ← 가장 낮은 활용도 ❌  (컨텍스트의 중간 = "사각지대")
  [문서 4] ← 보통
  [문서 5] ← 높은 활용도 ✅  (컨텍스트의 끝)
이것이 의미하는 바는 명확합니다: 가장 관련성 높은 문서가 상위(1~2위)에 위치해야 LLM이 이를 최대한 활용하여 정확한 답변을 생성 합니다. 만약 1차 검색에서 가장 관련 있는 문서가 3위나 4위에 있다면, LLM은 그 정보를 놓칠 수 있습니다. Re-ranking 전후 비교 예시:
질문: "Databricks에서 Delta Lake 테이블의 OPTIMIZE 명령 실행 주기는?"

── Re-ranking 없이 (1차 검색 결과 그대로) ──
1위: Delta Lake 개요 및 특징 소개          ← 주제는 맞지만 답변 없음
2위: OPTIMIZE와 VACUUM 명령어 상세 가이드   ← 정답이 여기에! (2위)
3위: Delta Lake 트랜잭션 로그 구조          ← 관련은 있지만 답변 없음
4위: Databricks SQL로 테이블 관리하기       ← 간접 관련
5위: 파티셔닝 전략과 Z-ORDER               ← 간접 관련

── Re-ranking 후 (Cross-encoder가 재정렬) ──
1위: OPTIMIZE와 VACUUM 명령어 상세 가이드   ← 정답! 최상위로 올라옴 ✅
2위: 파티셔닝 전략과 Z-ORDER               ← OPTIMIZE와 직접 연관
3위: Delta Lake 개요 및 특징 소개
4위: Databricks SQL로 테이블 관리하기
5위: Delta Lake 트랜잭션 로그 구조
Cross-encoder(질문과 문서를 하나의 입력으로 결합하여 Transformer에 통째로 넣는 방식)는 질문과 각 문서를 함께 읽고 교차 비교하기 때문에, “OPTIMIZE 실행 주기”라는 질문의 의도와 문서 내용의 대응 관계를 Bi-encoder(질문과 문서를 각각 독립적으로 벡터화하여 비교하는 방식)보다 훨씬 정밀하게 파악합니다. 한국어에서 Re-ranking이 특히 효과적인 이유: 한국어는 앞서 설명한 교착어 특성, 띄어쓰기 불규칙성, 한영 혼용 등의 이유로 1차 검색(Bi-encoder + BM25)의 순위 정확도가 영어보다 낮은 경향 이 있습니다. 구체적으로:
  1. 조사 변형에 의한 노이즈: 형태소 분석을 적용해도, 1차 검색 단계의 임베딩 모델은 “데이터브릭스에서”와 “데이터브릭스를”에 미세하게 다른 벡터를 부여할 수 있습니다. Cross-encoder는 이런 표면적 차이를 무시하고 핵심 의미만으로 판단합니다.
  2. 한영 혼용 매칭: “벡터 검색”과 “Vector Search”가 동일 개념임을 Bi-encoder는 불완전하게 포착하지만, Cross-encoder는 질문-문서 쌍을 통째로 읽으므로 교차 언어 의미 매칭 이 더 정확합니다.
  3. 전문 용어 문맥 이해: “Unity Catalog 권한 설정”이라는 질문에서, Bi-encoder는 “권한”이라는 키워드와 매칭되는 모든 문서를 비슷한 점수로 반환하지만, Cross-encoder는 “Unity Catalog의 권한”이라는 구체적 문맥 을 이해하여 정밀하게 순위를 매깁니다.
참고 실전 수치: 한국어 기술 문서 RAG에서 Re-ranking을 추가하면 Top-5 Precision이 일반적으로 10~25%p 향상 됩니다. 특히 유사한 주제의 문서가 많은 도메인(예: Databricks 공식 문서처럼 여러 기능이 비슷한 키워드를 공유하는 경우)에서 효과가 두드러집니다. Re-ranking의 구체적인 구현 방법과 모델 비교는 Re-ranking 개념Reranking 전략 페이지를 참조하세요.

한국어 특화 전처리

import re
from kiwipiepy import Kiwi

kiwi = Kiwi()

def preprocess_korean(text: str) -> str:
    """한국어 RAG를 위한 텍스트 전처리"""
    # 1. 불필요한 공백 정리
    text = re.sub(r'\s+', ' ', text).strip()

    # 2. 특수문자 정리 (문장부호는 유지)
    text = re.sub(r'[^\w\s가-힣a-zA-Z0-9.,!?·\-()]', '', text)

    # 3. 한자 → 한글 변환 (Kiwi 내장 기능)
    # Kiwi는 분석 시 자동으로 한자를 한글로 매핑

    return text

평가 시 주의사항

  • 한국어 평가 데이터셋을 직접 구축 해야 합니다. 영어 벤치마크 결과가 한국어 성능을 보장하지 않습니다.
  • 평가 지표: Retrieval에는 Recall@K (상위 K개 결과 중 정답이 포함된 비율), MRR(Mean Reciprocal Rank, 정답 문서가 몇 번째에 위치하는지의 역수 평균), 생성에는 정확성, 근거 충실도(Faithfulness, 답변이 제공된 컨텍스트에 근거하는 정도) 를 측정합니다.
  • MLflow Evaluate를 활용한 평가 방법은 RAG 평가 가이드를 참조하세요.
주의 한국어 RAG 시스템을 평가할 때, LLM-as-Judge를 사용한다면 평가 프롬프트도 한국어로 작성하거나, 한국어 이해도가 높은 모델(Claude, GPT-4 등)을 Judge로 사용해야 합니다.

7. 한국어 RAG 실전 트러블슈팅

자주 발생하는 문제와 해결법

문제원인해결 방법
”데이터브릭스”를 검색하면 “데이터브릭스에서”가 포함된 문서가 안 나옴공백 기반 BM25에서 조사 결합 형태를 다른 단어로 인식Kiwi 토크나이저를 적용한 BM25 사용
한영 혼용 문서에서 영어 키워드 검색이 안 됨한국어 특화 임베딩 모델이 영어 표현을 잘 이해하지 못함다국어 모델(multilingual-e5, bge-m3) 사용
청크 중간에서 문장이 잘림영어 기반 구분자만 사용한국어 종결어미 구분자 추가 (“다. ”, “요. “)
토큰 비용이 예상보다 높음한국어가 영어 대비 2~3배 많은 토큰 소비청크 크기를 문자 수로 관리, 컨텍스트 길이 최적화
검색 결과는 좋은데 답변이 부자연스러움LLM의 한국어 생성 품질 문제Claude 또는 GPT-4 등 한국어 성능이 좋은 모델 사용
동의어/유의어 검색이 안 됨”자동차”와 “차량” 등 동의어의 임베딩 거리가 멀 수 있음Query Expansion 또는 Multi-Query Retriever 적용

성능 벤치마크 참고값

한국어 기술 문서 기반 RAG 시스템의 일반적인 성능 범위입니다 (참고용):
지표양호우수최적화 목표
Recall@5> 0.75> 0.85> 0.90
Faithfulness> 0.80> 0.90> 0.95
Answer Relevancy> 0.80> 0.85> 0.90
검색 지연< 500ms< 200ms< 100ms
전체 응답 시간< 8초< 5초< 3초
참고 위 수치는 한국어 기술 문서 기준의 참고값입니다. 도메인(법률, 의료 등)에 따라 기대치가 다를 수 있으며, 반드시 해당 도메인의 평가 데이터셋으로 측정해야 합니다.

참고 문서