핵심 요약
RAG를 처음 만들면 데모는 잘 된다. 운영 트래픽에 풀면 그럴듯한 거짓말이 늘어난다. 사내 문서 검색 RAG를 1년 운영하며 잡은 5가지 망가짐 패턴과 해법.
1. 패턴 ① 청크가 너무 크다 또는 작다
청크 200 토큰: 의미 파편화. 청크 2,000 토큰: 검색은 되는데 LLM이 핵심 못 찾음.
해법: 시맨틱 청킹 + 부모-자식
// langchain 예
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter'
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 600, chunkOverlap: 80,
separators: ['\n## ', '\n### ', '\n\n', '\n', ' '],
})
또는 부모-자식 청킹: 검색은 작게(300), LLM 전달은 부모 단락(1500). retrieval은 정확도, generation은 컨텍스트.
2. 패턴 ② 임베딩 유사도만 믿는다
"사용자 패스워드 정책"이라는 질문에 "비밀번호 정책 문서"가 1순위가 아니라 "패스워드 매니저 추천 글"이 1순위. 어휘 차이 + 의미 유사도 충돌.
해법: 하이브리드 (BM25 + 임베딩) + Reciprocal Rank Fusion
function rrf(rankings, k=60) {
const scores = new Map()
for (const rs of rankings) {
rs.forEach((id, idx) => {
scores.set(id, (scores.get(id) ?? 0) + 1/(k + idx))
})
}
return [...scores].sort(([,a],[,b]) => b - a)
}
const merged = rrf([bm25Results, embedResults])
실측: top-5 hit-rate +14%p.
3. 패턴 ③ Top-K 그대로 LLM에 던진다
"50개 청크를 통째로 1M 컨텍스트에 박는다." → "lost in the middle" 발생. 중간 청크는 회상률 떨어짐.
해법: Cross-Encoder Reranker
// 1차: dense+sparse로 50개 후보
// 2차: 작은 cross-encoder로 재정렬 → 5~10개만 LLM
import { CohereRerank } from '@langchain/cohere'
const reranker = new CohereRerank({ topN: 8, model: 'rerank-multilingual-v4' })
const top = await reranker.compressDocuments(docs, query)
실측: hallucination -38%, 응답 토큰 -52% (입력 줄어 비용↓).
4. 패턴 ④ 메타데이터 필터 누락
전체 컬렉션에서 임베딩 유사도. 사용자가 "내 팀 문서만" 원하는데 다른 팀 문서가 섞임.
해법: pre-filter
const results = await collection.query({
queryEmbeddings: [embedding],
nResults: 30,
where: { team_id: { $eq: user.team_id }, archived: false },
})
Qdrant·pgvector·Pinecone·Chroma 모두 metadata filter 제공. 임베딩 검색 후 후처리 필터링은 정확도 더 떨어짐.
5. 패턴 ⑤ 도메인 임베딩 아님
OpenAI text-embedding-3-large가 좋지만 의료·법률·자체 코드베이스에선 부족.
해법: 도메인 파인튜닝 또는 매칭 학습
- 질문-정답 페어 1,000개 모으기 (사용자 피드백 활용)
- contrastive 학습으로 임베딩 미세 조정
- 또는 cross-encoder reranker만 도메인 데이터로 파인튜닝 (더 저렴, 효과 비슷)
6. 진단 방법 — 체계적 분석
"답이 이상하다"를 5가지로 분류:
- 관련 문서가 top-K에 존재하나? → 없으면 청킹·임베딩 문제
- top-K에 있지만 상위 5위 밖? → 리랭킹 문제
- top-5 안에 있는데 LLM이 무시? → prompting·컨텍스트 길이 문제
- top-5 안에 있고 LLM이 봤지만 잘못 종합? → 모델·프롬프트
- 완전 다른 청크 보고 만들어냄? → hallucination, "I don't know" 강화 필요
각 단계 hit-rate를 따로 측정하면 어디서 새는지 보임.
7. 평가 — 진단 가능하게
| 지표 | 의미 |
|---|---|
| Retrieval Hit@5 | 정답 청크가 top5에 있나 |
| Rerank NDCG | 정답이 상위에 있나 |
| Faithfulness | 답이 컨텍스트에 근거? |
| Answer Relevance | 질문에 맞는 답? |
8. 실측 — 1년 운영 개선
| 버전 | Hit@5 | Faithful | 비용/쿼리 |
|---|---|---|---|
| v1 (단순 dense) | 61% | 74% | $0.012 |
| v2 (hybrid) | 76% | 82% | $0.013 |
| v3 (+ rerank) | 88% | 91% | $0.011 ← 출력 줄어 절감 |
| v4 (+ 메타필터) | 93% | 94% | $0.010 |
9. 안티패턴
- "임베딩 모델만 좋은 것 바꾸면 해결" — 그것 하나로 5%p가 한계
- "청크 1만 토큰 크게" — LLM 비용↑, 정확도↓
- "hallucination 100% 없애기" — 불가능. "I don't know" 옵션 + 출처 표시로
참고
- arxiv "Lost in the Middle" (Liu et al.)
- cohere.com/blog/rerank — Reranker 비교

댓글 0