본문 바로가기
AI2025년 8월 28일6분 읽기

RAG 심화 — 청킹과 리랭킹 전략

YS
김영삼
조회 237

RAG 파이프라인 개선의 핵심

Retrieval-Augmented Generation(RAG)의 품질은 검색 정확도에 크게 좌우됩니다. 문서를 어떻게 쪼개고(청킹), 검색 결과를 어떻게 재정렬하느냐(리랭킹)가 최종 답변 품질을 결정합니다.

청킹 전략 비교

전략방법장점단점
고정 크기토큰/글자 수 기준단순, 균일의미 단위 파괴
문단 기반줄바꿈 구분자연스러운 단위크기 불균일
재귀 분할계층적 구분자유연한 크기 조절구현 복잡
시맨틱임베딩 유사도의미 보존 최적처리 비용 높음

LangChain 청킹 구현

from langchain.text_splitter import RecursiveCharacterTextSplitter

# 재귀 분할 — 가장 범용적
splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    separators=["\n\n", "\n", ". ", " ", ""],
    length_function=len,
)

chunks = splitter.split_text(document_text)

# 시맨틱 청킹 — 의미 단위 보존
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings

semantic_splitter = SemanticChunker(
    OpenAIEmbeddings(),
    breakpoint_threshold_type="percentile",
    breakpoint_threshold_amount=95,
)

semantic_chunks = semantic_splitter.split_text(document_text)

청킹 최적화 팁

# 메타데이터 보존 청킹
def chunk_with_metadata(doc, chunk_size=500, overlap=50):
    chunks = []
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=overlap,
    )
    for i, chunk in enumerate(splitter.split_text(doc['content'])):
        chunks.append({
            'text': chunk,
            'metadata': {
                'source': doc['source'],
                'title': doc['title'],
                'chunk_index': i,
                'total_chunks': None,
            }
        })
    for c in chunks:
        c['metadata']['total_chunks'] = len(chunks)
    return chunks

# 부모-자식 청킹 (Parent Document Retriever)
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore

parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)

retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=InMemoryStore(),
    parent_splitter=parent_splitter,
    child_splitter=child_splitter,
)

리랭킹 구현

# Cross-Encoder 리랭킹
from sentence_transformers import CrossEncoder

reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')

def rerank_results(query, documents, top_k=5):
    pairs = [(query, doc['text']) for doc in documents]
    scores = reranker.predict(pairs)

    ranked = sorted(
        zip(documents, scores),
        key=lambda x: x[1],
        reverse=True
    )
    return [doc for doc, score in ranked[:top_k]]

# Cohere Rerank API 활용
import cohere
co = cohere.Client('your-api-key')

results = co.rerank(
    model='rerank-multilingual-v3.0',
    query=query,
    documents=[doc['text'] for doc in candidates],
    top_n=5,
)

최적 파이프라인 구성

  • 청크 크기: 300-500 토큰이 일반적 최적 범위, 도메인에 따라 조절
  • 오버랩: 청크 크기의 10-20%가 적절 (문맥 연결 유지)
  • 하이브리드 검색: BM25(키워드) + 벡터 검색 결합으로 recall 향상
  • 리랭킹 배치: 초기 검색 20-50개 → 리랭킹 → 상위 3-5개를 LLM에 전달
  • 평가: RAGAS 프레임워크로 faithfulness, relevancy 측정

RAG 품질 개선은 단일 기법이 아닌 파이프라인 전체의 최적화입니다. 청킹 → 임베딩 → 검색 → 리랭킹 각 단계를 독립적으로 실험하고 측정하여 최적 조합을 찾아야 합니다.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록