RAG가 틀렸을 때 제일 먼저 듣는 말이 있습니다. "임베딩을 바꾸자."
물론 그게 답일 때도 있습니다. 하지만 제 경험에는 범인이 다른 곳에 있을 때가 더 많습니다. 문서 파싱, 자르기, 검색 방식, 그리고 "모른다"고 말할 줄 아는지까지. 이 글은 RAG 파이프라인을 5단계로 나눠 어디서 망가졌는지 찾는 순서를 정리합니다.
기본 개념은 RAG란 글과 Classic·Graph·Agentic RAG 글에 있습니다. 여기는 고장 수리 편입니다.
전체 지도: 답이 나오기까지 5개 관문
[1. 파싱] 문서 읽기 (PDF·표·스캔)
→ [2. 청킹] 자르기 (단위·겹침·메타데이터)
→ [3. 검색] 찾기 (벡터·키워드·하이브리드)
→ [4. 재순위] 고르기 (상위 후보 재정렬)
→ [5. 생성] 답하기 (인용·거절 포함)
사용자는 5번만 봅니다. "답이 틀렸네." 하지만 원인은 1~4번 어디에나 있을 수 있습니다. 순서대로 의심하는 게 핵심입니다. 뒤부터 고치면 돈과 시간만 듭니다.
1단계 파싱: 검색이 틀리기 전에 읽기가 틀린다
Mistral OCR 글의 핵심 문장을 다시 가져옵니다. 검색이 틀리기 전에 문서 파싱부터 틀릴 수 있다.
대표 증상들입니다.
| 증상 | 파싱 의심 신호 |
|---|---|
| 표 내용이 답에 안 나옴 | 표가 텍스트로 깨져서 들어감 |
| "문서에 없는데요"가 자주 나옴 | 스캔·이미지 페이지가 통째로 비어 있음 |
| 페이지 번호·조항 인용이 어긋남 | 머리말·꼬리말·각주가 본문에 섞임 |
점검은 간단합니다. 검색을 거치지 말고 파싱 결과 원문을 눈으로 읽어보세요. 사람도 못 찾을 문서에서 검색이 찾을 리 없습니다.
점검 ①: 질문 5개의 근거 페이지를 파싱 원문에서 Ctrl+F로 찾기
→ 여기서 안 나오면 파싱 문제 확정. 임베딩을 만지지 마라.
2단계 청킹: 자르는 게 답의 단위를 정한다
파싱이 깨끗해도 자르기를 잘못하면 답이 안 나옵니다.
너무 크게 자르면: 검색은 맞았는데 쓰레기 정보까지 딸려와서 답이 흐려짐
너무 잘게 자르면: 맥락이 잘려서 "그래서 어쩌라는 거지" 상태가 됨
겹침 없이 자르면: 경계에 걸린 내용이 양쪽에서 다 사라짐
메타데이터 없이 자르면: "2024년 규정"과 "2026년 규정"이 섞임
실전 규칙 세 개만 기억하세요.
규칙 1: 자르는 단위를 문서 구조에 맞춘다 (조항·섹션·표 단위)
규칙 2: 겹침을 준다 (보통 10~20%, 경계 손실 방지)
규칙 3: 메타데이터를 붙인다 (문서명·날짜·버전·페이지)
특히 버전 섞임이 흔합니다. 규정·매뉴얼·약관은 날짜가 답의 일부입니다. 날짜 메타데이터 없이 벡터 유사도만 믿으면 구버전이 버젓이 1등으로 옵니다.
3단계 검색: 벡터만 쓰면 지는 게임이 있다
벡터 검색은 의미가 비슷하면 잘 찾습니다. 반대로 고유명사·제품 코드·조항 번호·숫자 같은 정확한 일치는 키워드(BM25 같은)가 더 잘 찾습니다.
벡터가 강한 경우: "환불 규정 알려줘" 같은 의미 질문
키워드가 강한 경우: "제14조 3항", "모델명 XB-200", "2026년 3월 공지"
실무 질문: 둘 다 섞여 있음 → 하이브리드가 기본값
하이브리드 구조는 이렇습니다.
질문 ──┬──→ 벡터 검색 상위 20개 ──┐
└──→ 키워드 검색 상위 20개 ─┴──→ 합치기 → [4단계 재순위] → 상위 5개 → 생성
개념 스케치로 흐름만 적어보면 이렇습니다.
# hybrid_search.py — 개념 스케치
def hybrid_search(query, top_n=5):
dense_hits = vector_search(query, k=20) # 의미 기반
sparse_hits = keyword_search(query, k=20) # 정확 일치 기반
merged = reciprocal_rank_fusion(dense_hits, sparse_hits)
reranked = rerank(query, merged[:20]) # 4단계
return reranked[:top_n]
합치는 방법(가중합, RRF 등)과 가중치는 내 데이터로 정합니다. 남의 블로그 숫자를 그대로 가져오면 안 됩니다. 질문 30개짜리 평가 셋으로 재현하는 게 미니 eval 글의 5문항과 같은 원리입니다.
4~5단계 재순위와 생성: 마지막에 "모른다"가 있어야 한다
재순위는 후보 20개를 질문과의 관련도로 다시 줄 세워 상위 5개만 생성에 넘기는 일입니다. 비용은 조금 들지만 답 품질이 확 올라가는 구간이라 가성비가 좋습니다.
생성 단계에서는 환각 vs 답변 거부 글의 원칙이 들어옵니다. 근거가 없으면 "모른다"고 말하게 하세요. 최신 측정도 이 방향을 가리킵니다.
- Gemini 4 Argon: 환각률 15%, 선도 모델 중 최저 (정확도는 다소 낮아도 overall은 대등)
- GPT-6 Astra: 환각률 92% → 51%로 개선, 정확도도 같이 오름
→ "거절을 잘하는 모델"이 지식 작업에서 유리해지는 추세
Hebbia 사례도 있습니다. Opus 5.5로 인용 회수율 역대 최고를 찍으면서 토큰 효율도 챙겼다고 합니다. 인용을 강제하는 게 환각을 줄이는 실전 장치입니다.
생성 프롬프트에 넣을 세 줄:
1. 근거 청크 번호를 답 문장마다 표기할 것
2. 근거에 없으면 "문서에서 확인되지 않았습니다"라고 답할 것
3. 확신 없는 수치·날짜는 쓰지 말 것
CodeBridge Mini Lab: 실패 20개를 분류하기
① 틀린 질문 20개를 모은다 (사용자 로그·테스트에서)
② 답이 아니라 단계를 찾는다:
파싱? → 파싱 원문에 근거가 있는가 (없으면 파싱)
청킹? → 근거 청크를 읽으면 답이 나오는가 (맥락 잘림이면 청킹)
검색? → 상위 20개 안에 근거가 있는가 (없으면 검색)
재순위? → 20개 안에는 있는데 5개 안에 없는가 (있으면 재순위)
생성? → 5개 안에 있는데 답이 틀렸는가 (있으면 생성·프롬프트)
③ 분포를 센다:
예) 파싱 8, 청킹 5, 검색 4, 재순위 2, 생성 1
→ 1등부터 고친다. 임베딩은 검색이 1등일 때만 만진다.
이 분류가 끝나면 "임베딩을 바꾸자" 같은 막연한 처방이 사라집니다. 숫자가 다음 할 일을 정해줍니다.
결론: 앞에서부터 의심하세요
정리하면 순서가 이렇습니다.
파싱 원문 확인 → 청킹·메타데이터 점검 → 하이브리드 검색 → 재순위 → 인용 강제와 거절 허용
RAG는 모델이 아니라 파이프라인입니다. 사내 문서 챗봇 글에서 그린 구조도에 이 글의 점검 순서를 겹쳐보세요. 어디가 비어 있는지 바로 보일 겁니다. 그리고 기억하세요. "모른다"는 답도 성능입니다.
함께 읽으면 좋은 글
참고 자료
- Artificial Analysis: Gemini 4 Argon
- Artificial Analysis: Benchmarking GPT-6 Astra
- Anthropic: Introducing Claude Opus 5.5
이 주제를 직접 따라가며 배우고 싶다면
파싱·청킹·검색·생성을 한 구조로 묶어 실무 챗봇까지 완성해보고 싶다면, Classic RAG에서 GraphRAG·Agentic RAG로 확장하는 설계 과정이 이 글의 5단계 점검과 바로 이어집니다.