RAG 시스템을 처음부터 제대로 구축하기: 성패를 결정짓는 6가지 결정 사항
요약
성공적인 RAG 시스템 구축을 위해 반드시 고려해야 할 6가지 핵심 결정 사항을 다룹니다. 특히 생성 단계보다 인덱싱 파이프라인의 중요성을 강조하며, 임베딩 모델, 청크 크기, 인덱스 유형 등의 최적화 방법을 제시합니다.
핵심 포인트
- RAG 문제의 80%는 생성보다 인덱싱 단계에서 발생함
- 임베딩 모델은 언어 특성에 맞춰 선택하고 인덱싱/쿼리 모델을 일치시켜야 함
- 고정 길이보다는 재귀적 분할(Recursive Splitting) 방식을 권장함
- 데이터 규모와 특성에 따라 HNSW 또는 IVF 인덱스 유형을 선택해야 함
- 메타데이터 필터링은 검색 효율성과 정확도를 높이는 필수 요소임
20개 이상의 고장 난 RAG 시스템을 디버깅한 끝에, 저는 여러분의 시스템이 제대로 작동할지 여부를 결정하는 6가지 결정 사항을 식별했습니다. 각 사항을 올바르게 수행하는 방법을 소개합니다.
RAG 개발자의 함정
모든 RAG 개발자는 동일한 함정에 빠집니다. 기본적인 파이프라인을 구축하고 그것이 어느 정도 작동하면, 실제 문제는 인덱싱 파이프라인 (indexing pipeline)에 그대로 방치된 채 몇 주 동안 프롬프트 템플릿 (prompt templates)을 수정하는 데 시간을 허비합니다.
80/20 법칙: RAG 문제의 80%는 생성 (generation)이 아니라 인덱싱 (indexing)에서 발생합니다. 하지만 디버깅 노력의 80%는 생성 단계에 투입됩니다.
이를 바로잡아 봅시다.
결정 사항 1: 임베딩 모델 (Embedding Model) — 가장 큰 영향력을 가진 요소
실수: 모든 튜토리얼의 기본값이라는 이유로 중국어 문서에 all-MiniLM-L6-v2를 사용하는 것.
잘못된 이유: 이 모델은 영어로 학습되었습니다. 중국어 텍스트에 적용하면 의미론적 충실도 (semantic fidelity)를 30-50% 상실합니다.
| 언어 | 권장 모델 |
|---|---|
| 중국어 | BAAI/bge-large-zh-v1.5 (1024-dim) |
| ... | |
| 타협 불가 사항: 인덱싱 모델 (indexing model)과 쿼리 모델 (query model)은 바이트 단위로 완전히 동일해야 합니다. 모델을 변경하면 전체 인덱스를 다시 구축해야 합니다. |
영향: 중국어 RAG의 Recall@10이 15-40% 향상됩니다.
결정 사항 2: 청크 크기 (Chunk Size) — 마법의 숫자는 없다
물리학적 관점: 너무 작으면 (< 100 tokens) = 의미론적 파편화 (semantic fragmentation). 너무 크면 (> 1000 tokens) = 노이즈 주입 (noise injection).
| 문서 유형 | 최적의 범위 | 오버랩 (Overlap) |
|---|---|---|
| FAQ / 단문 | 128-256 | 20 |
| ... | ||
| 크기보다 방법이 더 중요합니다. 고정 길이 대신 재귀적 분할 (recursive splitting)을 사용하세요: |
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
...
영향: Recall@10이 5-15% 향상됩니다.
결정 사항 3: 인덱스 유형 (Index Type) — HNSW vs IVF
| 규모 | 권장 사항 | 이유 |
|---|---|---|
| < 1M 벡터 | HNSW | Recall > 0.95 |
| ... | ||
| 핵심적인 미묘한 차이: HNSW는 삽입 비용 (insertion cost)이 높습니다. 문서를 스트리밍 (streaming)하는 경우, 작은 규모에서도 IVF가 더 나을 수 있습니다. |
영향: Recall이 5-15% 향상되거나 지연 시간 (latency)이 2-5배 개선됩니다.
결정 사항 4: 메타데이터 (Metadata) — 선택 사항이 아님
메타데이터 필터링 (metadata filtering)이 없다면, 모든 쿼리가 모든 벡터를 스캔하게 됩니다. department=engineering AND date > 2024-01-01을 추가하면 500만(5M) 개의 벡터에서 5만(50K) 개로 줄어듭니다.
{"source": "internal_wiki", "doc_type": "design_doc",
"publish_date": "2024-06-01", "department": "engineering"}
보너스: 프롬프트 (prompts)에 메타데이터를 주입하세요. LLM (대규모 언어 모델)은 출처와 최신성을 바탕으로 신뢰도에 가중치를 둡니다.
영향 (Impact): 필터링만으로 정밀도 (precision) 10-25% 향상.
결정 사항 5: 중복 제거 (Deduplication) — 두 번 수행하라
엔터프라이즈 지식 베이스 (knowledge bases)는 중복된 내용으로 가득 차 있습니다. 중복 제거 (dedup)가 없다면, 상위 10개 결과가 동일한 문서의 7개 복사본일 수도 있습니다.
- 문서 수준 (Document-level) (청킹 (chunking) 전): MinHash + LSH, 임계값 (threshold) 0.85
- 청크 수준 (Chunk-level) (청킹 후): SimHash, 임계값 (threshold) 0.95
영향 (Impact): 유효 재현율 (effective recall) 10-20% 향상.
결정 사항 6: 쿼리 처리 (Query Processing) — 나머지 절반
| 기술 (Technique) | 시점 (When) | 비용 (Cost) |
|---|---|---|
| 쿼리 재작성 (Query rewriting) | 짧거나 모호한 쿼리 | 낮음 |
| ... |
최소 기능 스택 (Minimum viable stack): 쿼리 재작성 (Query rewriting) + 교차 인코더 재순위화 (Cross-Encoder rerank).
최적화 우선순위 스택 (The Optimization Priority Stack)
우선순위 (가장 큰 영향):
- 임베딩 모델 (Embedding model)이 언어에 적합한가?
- 청크 크기 (Chunk size)가 적절한가 (256-768)?
...
문제를 해결했는지 확인하는 방법
추측하지 마세요. 측정하세요. 50개의 (쿼리, 정답 (ground_truth)) 쌍을 사용하여 Recall@10과 MRR을 추적하세요.
def recall_at_k(results, ground_truth, k=10):
return int(ground_truth in [r.id for r in results[:k]])
...
만약 이 내용이 당신의 디버깅 시간을 오후 내내 아껴주었다면, 유니콘을 선물하고 아직도 청크 크기를 조정하며 고군분투 중인 누군가에게 이 글을 공유해 주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기