Appendix B · RAG Hybrid RAG 심화 가이드 0%
📘 APPENDIX B

Hybrid RAG 심화 가이드

원리 · 파라미터 튜닝 · 성능 벤치마크 · 트러블슈팅

Section 1

1. RAG의 본질과 한계

왜 RAG인가?

LLM은 파라미터에 지식을 "압축 저장"합니다. 그래서 두 가지 약점이 있습니다:

  1. 최신성 부족 — 학습 시점 이후 정보 모름 (Claude는 2026-01 cutoff)
  2. 개별 문서 부재 — 내 회사 내부 문서, 특정 PDF 내용 모름
  3. 환각(Hallucination) — 모르는 걸 아는 척

RAG는 검색(Retrieval)으로 관련 문서를 찾고, 생성(Generation)할 때 근거로 제시합니다.

CORE INSIGHT
RAG = 외부 기억 + 검색 + 생성
LLM을 "완벽한 기억의 천재"가 아닌 "검색된 자료를 잘 읽는 연구원"으로 활용하는 아키텍처. 정확성·최신성·검증가능성이 근본적으로 개선됩니다.

단순 RAG의 한계

Vector 검색 하나로는 부족합니다. 이유:

🎯 Hybrid RAG의 핵심: "넓게 긷고 좁게 거른다" — Vector로 20개 후보 수집 → Cross-encoder로 3개 정밀 선별 → LLM에 전달. 이 구조가 정답률을 40%→88%로 끌어올립니다.
Section 2

2. Bi-encoder vs Cross-encoder 원리

Hybrid RAG의 구조를 이해하려면 두 모델 패러다임의 차이를 알아야 합니다.

✓ Bi-encoder (임베딩 모델)
OpenAI text-embedding-3
쿼리·문서를 각각 독립적으로 벡터화
저장 가능, 사전 계산 가능
코사인 유사도 계산
빠름 (수백만 문서도 밀리초)
정확도: 중간
⚠ Cross-encoder (Reranker)
Cohere rerank-v3.5
쿼리+문서를 함께 입력해 점수 계산
사전 저장 불가, 매번 계산
복잡한 관련도 판단 가능
느림 (수백 개가 한계)
정확도: 높음
BI-ENCODER
sim(q, d) = cos(E(q), E(d))
쿼리 q와 문서 d를 각각 임베딩 후 코사인 유사도
CROSS-ENCODER
score(q, d) = Model(q, d)
쿼리 q와 문서 d를 함께 입력해 관련도 점수 직접 출력

왜 둘을 합치는가?

Cross-encoder로 100만 문서를 매번 계산하면 너무 느림. Bi-encoder로 빠르게 20개 거르고, 그 20개만 Cross-encoder로 정밀 판정. 속도와 정확도의 타협.

직관적 비유
Bi-encoder = 도서관 검색 시스템: 서가에서 20권 뽑아옴.
Cross-encoder = 사서 본인: 20권을 직접 훑어보고 "이거랑 이거가 답"이라 짚어줌.
사서에게 만 권을 모두 훑어달라고 하면 평생 걸리지만, 20권 훑기는 금방입니다.
Section 3

3. 청킹 전략 — Size & Overlap 튜닝

청킹은 RAG 성능의 가장 큰 레버입니다. 잘못하면 정답률이 반 토막.

청크 크기 (Chunk Size)

크기 (토큰)한국어 글자수특징
100~200~200자정밀하지만 맥락 부족. FAQ 같은 구조화 데이터에 적합
512~10241000~2000자의미 단위 보존. 문학·논문에 적합하지만 검색 정밀도 낮음
1024+2000자+임베딩이 희석됨. "모든 걸 담은 무의미한 벡터". 지양

오버랩 (Overlap)

비율용도
0%청킹 경계에서 정보 손실 위험. 비추천
10%최소 안전판. 대부분 경우 부족
50%+중복 과도. 저장 비용 낭비 + 검색 결과에 같은 내용 중복

강의 기본 설정 (권장값)

W6 Lab 기본 설정
RecursiveCharacterTextSplitter(
    chunk_size=1000,       # 약 500 토큰 (한국어 기준)
    chunk_overlap=200,      # 20% 오버랩
    separators=['\n\n', '\n', '. ', ' ', '']
)

Recursive Splitter의 우선순위

separators 리스트는 위에서 아래로 순차 시도:

  1. \n\n (빈 줄) — 문단 경계. 가장 자연스러운 분할점
  2. \n (줄바꿈) — 문단 내부 분할이 필요할 때
  3. . (마침표+공백) — 문장 단위. 한국어는 다. 도 추가 고려
  4. ' ' (공백) — 단어 단위. 어쩔 수 없을 때
  5. '' (빈 문자열) — 글자 단위. 마지막 수단
한국어 문서 청킹 특수 팁
한국어는 . 이 영어만큼 일관되지 않습니다. 다. , 니다. , 요. 같은 어미까지 고려. 혹은 의미 단위 청킹(semantic chunking)을 사용하면 더 자연스러움.

Advanced: 계층적 청킹 (Hierarchical Chunking)

큰 문서의 경우 parent 청크(2000자) + child 청크(500자)를 함께 관리. 검색은 child로 하고 맥락은 parent를 반환.

계층 청킹 예시 코드
# Parent chunks (맥락용, 임베딩 안 함)
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
parents = parent_splitter.split_text(doc)

# Child chunks (검색용, 임베딩)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)
for parent_id, parent_text in enumerate(parents):
    children = child_splitter.split_text(parent_text)
    for child in children:
        embedding = embed(child)
        store(child, embedding, metadata={'parent_id': parent_id})

# 검색 시: child로 찾고 parent 반환
results = vector_search(query, top_k=5)
parent_ids = [r.metadata.parent_id for r in results]
final_context = [parents[pid] for pid in set(parent_ids)]
Section 4

4. Embedding 모델 선택

주요 Embedding 모델 비교 (2026-04 기준)

모델차원비용 (1M토큰)강점
text-embedding-3-large3072$0.13최고 정확도. 비용 7배
Cohere embed-v3.01024$0.10다국어(한국어↑)
Voyage voyage-31024$0.06RAG 최적화 · Anthropic 공식 추천
BGE-M3 (open)1024무료오픈소스, 자체 호스팅

언제 어떤 모델?

의사결정 트리:
학습·프로토타입 → text-embedding-3-small ✓
한국어 중심 문서 → Cohere embed-v3.0 또는 multilingual-v3
최고 정확도 필요 (의료·법률) → text-embedding-3-large 또는 Voyage
보안·비용 민감 → BGE-M3 self-hosted

차원 축소 옵션

OpenAI text-embedding-3는 Matryoshka 기법으로 차원을 줄일 수 있습니다:

차원 축소로 저장 공간 절약
response = openai.embeddings.create(
    model='text-embedding-3-small',
    input=text,
    dimensions=512   # 1536 → 512 (1/3 크기)
)
# 정확도는 ~3% 하락, 저장·검색 속도 3배 향상
Embedding 모델 교체 시 주의
모델 변경하면 기존 벡터 전체 재임베딩 필요. 차원 수도 달라짐. 프로젝트 초기에 신중히 선택하고, 변경 시 전체 reindex 계획 수립.
Section 5

5. Reranker 딥다이브

Reranker 모델 비교

모델컨텍스트비용특징
Cohere rerank-v4.0-pro32K$2/1k쿼리긴 문서 Q&A
Voyage rerank-216K$5/1k쿼리고정확
BGE reranker512무료오픈소스, 자체 호스팅

Reranker 호출 규칙

⚠ Reranker 효과를 극대화하려면:
1. 1차 Retrieval은 충분히 넓게 — 20~50개 후보 (적으면 Reranker 할 일 없음)
2. 1차에서 10개 미만이면 Reranker 의미 없음 (단순 정렬)
3. Top-N은 보통 3~10. LLM 컨텍스트 윈도우 고려
4. 도메인 용어가 많으면 Reranker가 더 큰 효과

최적의 Top-K 결정

권장 공식
initial_k = 4 × final_k
최종 3개 필요 → 초기 12~20개 수집. 최종 5개 → 초기 20~30개.

RRF (Reciprocal Rank Fusion) — Advanced

Dense(벡터) + Sparse(BM25/키워드) 검색을 결합하는 기법:

RRF SCORE
RRF(d) = Σ 1 / (k + rank(d))
k=60 표준. 두 검색 결과의 순위 역수 합
RRF 구현
def rrf_fusion(vector_results, bm25_results, k=60):
    """두 검색 결과를 RRF로 융합."""
    scores = {}
    
    for rank, doc in enumerate(vector_results):
        scores[doc.id] = scores.get(doc.id, 0) + 1 / (k + rank + 1)
    
    for rank, doc in enumerate(bm25_results):
        scores[doc.id] = scores.get(doc.id, 0) + 1 / (k + rank + 1)
    
    # 점수 높은 순 정렬
    return sorted(scores.items(), key=lambda x: -x[1])

8주 강의의 RAG 구조 재확인

📊 Hybrid RAG 4단계 파이프라인
1
임베딩 저장PDF 청킹 → OpenAI 임베딩 → Supabase 저장
Offline
2
1차 검색쿼리 임베딩 → pgvector 유사도 Top 20
Bi-encoder
3
2차 재정렬Cohere rerank-v3.5로 Top 20 → Top 3
Cross-encoder
4
답변 생성Claude Sonnet에 Top 3 청크 컨텍스트로 전달 → 출처 인용 답변
LLM
Section 6

6. 성능 측정 + 벤치마크

핵심 메트릭 3종

METRIC 1
Recall@K
Top-K 결과 안에 정답 문서가 포함된 비율. "10개 중 정답이 몇 번 들어오는가"
METRIC 2
MRR (Mean Reciprocal Rank)
정답이 몇 번째에 있는지의 역수 평균. 1등에 있으면 1.0, 2등이면 0.5. 순위까지 평가.
METRIC 3
Faithfulness (Ragas)
생성된 답변이 실제로 검색된 문서에 근거하는지 측정. 환각 여부 판정.

강의 구조의 실측 성능

📊 W6 실습 기준 Recall@3 (FOMC 의사록 Q&A)
Vector만 (단순 RAG) 42%
+ 청크 크기 최적화 58%
+ Cohere Reranker 81%
+ 계층 청킹 + RRF 91%

간단한 벤치마크 코드

Recall@K 측정
# 평가 세트: 질문 + 정답 문서 ID
test_set = [
    {'q': 'FOMC 점도표 2026년 중간값', 'answer_ids': ['doc_42']},
    {'q': '인플레이션 전망', 'answer_ids': ['doc_17', 'doc_23']},
    # ... 20~50개 이상 준비
]

def recall_at_k(retrieval_fn, test_set, k=3):
    hits = 0
    for t in test_set:
        results = retrieval_fn(t['q'], top_k=k)
        result_ids = {r['id'] for r in results}
        if set(t['answer_ids']) & result_ids:
            hits += 1
    return hits / len(test_set)

# 측정
r3_vector = recall_at_k(vector_search, test_set, k=3)
r3_hybrid = recall_at_k(hybrid_search, test_set, k=3)

print(f'Vector Recall@3: {r3_vector:.1%}')
print(f'Hybrid Recall@3: {r3_hybrid:.1%}')
Section 7

7. RAG 실패 패턴 + 해결

실패 패턴 Top 7

❌ Pattern 1 — 청크가 너무 큼
한 청크가 2000자. 애플 IR도 있고 삼성 실적도 있고... 임베딩이 뒤죽박죽.
✓ 해결
청크 500~1000자로 축소. RecursiveCharacterTextSplitter 기본값으로.
❌ Pattern 2 — 출처 메타데이터 누락
청크만 저장하고 어느 PDF 몇 페이지인지 모름. 인용 불가능.
✓ 해결
metadata에 source·page·chunk_id 반드시 포함. W6 실습 기준 코드 참조.
❌ Pattern 3 — Reranker에 너무 적은 후보
Vector Top 3만 주고 Rerank 요청. Reranker는 3개 내에서만 정렬, 효과 없음.
✓ 해결
Vector Top 20~50 → Rerank → Top 3~5로 축소. "넓게 수집, 좁게 필터".
❌ Pattern 4 — 중복 청크
같은 PDF가 두 번 업로드되어 똑같은 청크 중복. Top 3가 같은 내용.
✓ 해결
저장 전 content hash 체크, metadata에 file_hash 추가. 중복 자동 차단.
❌ Pattern 5 — 도메인 용어 미스매치
"RSI 30 이하"를 "relative strength index below 30"으로 임베딩. 검색 실패.
✓ 해결
쿼리 확장(query expansion): LLM에게 쿼리를 3가지 표현으로 바꿔달라 요청.
❌ Pattern 6 — LLM이 컨텍스트 무시
"Top 3 청크 주어도 자기 지식으로만 답함". 프롬프트 약함.
✓ 해결
시스템 프롬프트에 "제공된 출처만 사용, 없으면 '모름'이라 답" 강제.
❌ Pattern 7 — Embedding 모델 교체 후 섞임
처음엔 text-embedding-3-small(1536차원)로 저장, 나중에 다른 모델 추가. 벡터 차원 다름.
✓ 해결
모델 변경 시 전체 DB reindex. metadata에 embedding_model 명시.
Section 8

8. 운영 체크리스트

구축 전

구축 중

배포 후

🎯 최종 목표: Recall@3 ≥ 85% · MRR ≥ 0.75 · Faithfulness ≥ 0.90
이 수치 도달하면 프로덕션 품질. 데모에서는 60~70%로도 "신기해 보이지만", 실전은 다릅니다.
📘 APPENDIX B COMPLETE
Hybrid RAG 심화 가이드
8 sections · 3 formulas · 7 failure patterns
2026 · ZeroOneAI Education