Section 1
1. RAG의 본질과 한계
왜 RAG인가?
LLM은 파라미터에 지식을 "압축 저장"합니다. 그래서 두 가지 약점이 있습니다:
- 최신성 부족 — 학습 시점 이후 정보 모름 (Claude는 2026-01 cutoff)
- 개별 문서 부재 — 내 회사 내부 문서, 특정 PDF 내용 모름
- 환각(Hallucination) — 모르는 걸 아는 척
RAG는 검색(Retrieval)으로 관련 문서를 찾고, 생성(Generation)할 때 근거로 제시합니다.
CORE INSIGHT
RAG = 외부 기억 + 검색 + 생성
LLM을 "완벽한 기억의 천재"가 아닌 "검색된 자료를 잘 읽는 연구원"으로 활용하는 아키텍처. 정확성·최신성·검증가능성이 근본적으로 개선됩니다.
단순 RAG의 한계
Vector 검색 하나로는 부족합니다. 이유:
- 의미 유사도 ≠ 질문에 답하는 능력 — "애플 실적"과 "Apple earnings"는 의미상 비슷해도 원하는 답이 다를 수 있음
- 문맥 손실 — 청크 경계에서 중요 정보 잘림
- Bi-encoder 한계 — 쿼리·문서를 독립적으로 임베딩, 세밀한 매칭 불가
🎯 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
쿼리+문서를
함께 입력해 점수 계산
사전 저장 불가, 매번 계산
복잡한 관련도 판단 가능
느림 (수백 개가 한계)
정확도: 높음
왜 둘을 합치는가?
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 같은 구조화 데이터에 적합 |
256~512 ⭐ | 500~1000자 | 기본 권장. 대부분 도메인에 최적 |
512~1024 | 1000~2000자 | 의미 단위 보존. 문학·논문에 적합하지만 검색 정밀도 낮음 |
1024+ | 2000자+ | 임베딩이 희석됨. "모든 걸 담은 무의미한 벡터". 지양 |
오버랩 (Overlap)
| 비율 | 용도 |
0% | 청킹 경계에서 정보 손실 위험. 비추천 |
10% | 최소 안전판. 대부분 경우 부족 |
20~30% ⭐ | 표준. 경계에 걸친 정보 거의 완벽 보존 |
50%+ | 중복 과도. 저장 비용 낭비 + 검색 결과에 같은 내용 중복 |
강의 기본 설정 (권장값)
W6 Lab 기본 설정
RecursiveCharacterTextSplitter(
chunk_size=1000, # 약 500 토큰 (한국어 기준)
chunk_overlap=200, # 20% 오버랩
separators=['\n\n', '\n', '. ', ' ', '']
)
Recursive Splitter의 우선순위
separators 리스트는 위에서 아래로 순차 시도:
\n\n (빈 줄) — 문단 경계. 가장 자연스러운 분할점
\n (줄바꿈) — 문단 내부 분할이 필요할 때
. (마침표+공백) — 문장 단위. 한국어는 다. 도 추가 고려
' ' (공백) — 단어 단위. 어쩔 수 없을 때
'' (빈 문자열) — 글자 단위. 마지막 수단
한국어 문서 청킹 특수 팁
한국어는 . 이 영어만큼 일관되지 않습니다. 다. , 니다. , 요. 같은 어미까지 고려. 혹은 의미 단위 청킹(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-small | 1536 | $0.02 | 강의 기본. 저렴+강력 |
text-embedding-3-large | 3072 | $0.13 | 최고 정확도. 비용 7배 |
Cohere embed-v3.0 | 1024 | $0.10 | 다국어(한국어↑) |
Voyage voyage-3 | 1024 | $0.06 | RAG 최적화 · 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-v3.5 ⭐ | 4096 | $2/1k쿼리 | 강의 기본. 100개 언어 |
Cohere rerank-v4.0-pro | 32K | $2/1k쿼리 | 긴 문서 Q&A |
Voyage rerank-2 | 16K | $5/1k쿼리 | 고정확 |
BGE reranker | 512 | 무료 | 오픈소스, 자체 호스팅 |
Reranker 호출 규칙
⚠ Reranker 효과를 극대화하려면:
1. 1차 Retrieval은 충분히 넓게 — 20~50개 후보 (적으면 Reranker 할 일 없음)
2. 1차에서 10개 미만이면 Reranker 의미 없음 (단순 정렬)
3. Top-N은 보통 3~10. LLM 컨텍스트 윈도우 고려
4. 도메인 용어가 많으면 Reranker가 더 큰 효과
최적의 Top-K 결정
RRF (Reciprocal Rank Fusion) — Advanced
Dense(벡터) + Sparse(BM25/키워드) 검색을 결합하는 기법:
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. 운영 체크리스트
구축 전
- □ 예상 문서 수 + 평균 크기 추정 (벡터 DB 용량 예측)
- □ 쿼리 유형 분석 (factoid / summarization / comparison)
- □ 타겟 메트릭 결정 (Recall@3 80%+? MRR 0.7+?)
- □ 테스트 세트 구축 (최소 20개 질문+정답)
구축 중
- □ 청킹 설정 기본값으로 시작 (1000자 + 200 overlap)
- □ 저장 시 metadata 필수 (source·page·chunk_id·file_hash·timestamp)
- □ 1차 검색 top_k=20 이상
- □ Reranker top_n=3~5
- □ 컨텍스트 인용 프롬프트 엄격 작성
- □ 환각 방지 Guardrail 추가 ("출처 없으면 모름")
배포 후
- □ 첫 주 Recall@K 측정 → 목표치 미달이면 청크 크기부터 조정
- □ 사용자 피드백 수집 ("좋아요/싫어요" 버튼)
- □ 실패 케이스 로그 → 주간 리뷰
- □ 신규 문서 주기적 reindex (월 1회)
- □ Embedding / Reranker 모델 업데이트 추적
- □ 비용 모니터링 (임베딩 · 검색 · 생성 각각)
🎯 최종 목표: 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