Reranker와 Metadata Filter
앞 글에서 Hybrid Retriever로 top-20 후보를 만들었다. 근데 여기서 그대로 LLM에 다 넣으면 두 가지 문제가 생긴다.
1. 컨텍스트 윈도우 / 비용이 폭발한다
(top-20을 다 넣으면 토큰이 너무 많아짐)
2. 진짜 중요한 문서가 뒤에 묻힌다
(Retriever 점수만으로는 정확도가 부족함)
이 두 문제를 풀어주는 게 Reranker다. 그리고 권한, 날짜, 부서 같은 운영 요구는 Metadata Filter로 처리한다.
이번 글에서는 둘을 다룬다.
Reranker란 뭔가
Retriever는 빠르게 후보를 추리는 단계다. Reranker는 그 후보를 더 비싼 모델로 다시 정렬하는 단계다.
글로 풀면 이렇다.
원본 100만 문서
↓ Retriever (Hybrid) ← 빠름. top-50~1000 추림
50~1000개 후보
↓ Reranker ← 느림/비쌈. top-5~10 정밀 정렬
최종 top-5
↓ LLM
왜 두 단계로 나누나? 한 단계로 다 하면 너무 비싸기 때문이다.
Retriever : 문서 100만 개 다 계산해도 빨라야 함
→ 가벼운 모델로 점수 매김
Reranker : 후보 50개만 계산하면 됨
→ 무거운 모델로 정밀하게 점수 매김
Reranker는 어떤 모델인가
대표적인 옵션 3종.
1. Cross Encoder (오픈소스)
입력: (질문, 문서) 한 쌍
출력: 0~1 사이의 관련도 점수
질문과 문서를 같이 모델에 넣어서 점수를 매긴다. 임베딩 모델(질문 따로, 문서 따로 벡터화)보다 정확하지만 그만큼 느리다.
from sentence_transformers import CrossEncoder
model = CrossEncoder("BAAI/bge-reranker-v2-m3")
pairs = [(query, doc) for doc in candidates]
scores = model.predict(pairs)대표 모델:
- BAAI/bge-reranker-v2-m3 (한국어 포함 다국어)
- BAAI/bge-reranker-large
- jinaai/jina-reranker-v2-base-multilingual
2. Cohere Rerank (API)
import cohere
co = cohere.Client(api_key="...")
result = co.rerank(
query="연차 얼마나 있어?",
documents=candidate_texts,
model="rerank-multilingual-v3.0",
top_n=5,
)API라 운영 부담이 적고, 다국어 품질이 좋아 한국어 RAG에서 인기. 단점은 비용과 외부 의존성.
3. LLM as Reranker
GPT-4o나 Claude에게 후보를 보여주고 "관련도 순으로 정렬해줘"라고 시키는 방식.
- 가장 비쌈
- 가장 유연함 (의도, 맥락까지 고려)
- top-N이 작을 때만 (5~20개)
품질은 가장 높지만 latency와 비용이 큰 문제라, 마지막 단계의 한 번만 쓰는 게 보통이다.
어느 걸 골라야 하나
대충 이렇게 시작하면 된다.
1. 처음에는 Reranker 없이 시작 (Hybrid만)
→ 평가 지표(MRR, nDCG) 측정
2. 품질이 부족하면 bge-reranker-v2-m3 부터 시도
(자체 호스팅, 비용 없음)
3. 한국어 품질이 부족하거나 운영이 부담이면 Cohere Rerank
(API, 월 비용 발생)
4. 마지막 한 끗을 위해 LLM Rerank 검토
(top-5 안에서 순서만 바꾸는 용도)
LTR (Learning To Rank)는 왜 잘 안 가는가
검색 책을 보면 "LTR로 검색 품질을 끌어올린다"는 말이 자주 나온다. 사용자 클릭/피드백 로그를 학습 데이터로 써서 랭킹을 개선하는 기법이다.
사용자가 1번 결과를 클릭함 → 1번이 정답
사용자가 1번을 보고 2번을 클릭함 → 2번이 더 정답
이걸 모아서 모델을 학습시키면 랭킹이 좋아진다.
그런데 대부분의 회사/프로젝트에서 RAG에 LTR까지 가는 경우는 드물다. 이유는 단순하다.
1. 충분한 트래픽이 필요함 (수만~수십만 쿼리/일)
2. 사용자 피드백을 받을 UI가 필요함
3. 정답 라벨링이 필요함
4. 평가 체계가 잡혀 있어야 ROI 측정 가능
5. Cohere Rerank로도 대부분 충분히 좋음
그래서 실무 RAG의 80%는 Hybrid + Reranker(Cohere or bge) 에서 멈춘다. LTR은 검색 서비스(쇼핑, 뉴스, 채용) 회사에서 의미가 크지, 사내 RAG에서는 보통 ROI가 안 나온다.
Metadata Filter: 운영의 절반이 여기서 일어난다
Retriever만 잘 만들면 끝일 것 같지만, 실무에서는 검색 결과를 잘라내는 일이 절반이다. 대표적인 시나리오.
1. 사용자별 권한
→ "이 사용자는 인사팀이 아니므로 인사 문서는 제외"
2. 날짜
→ "최근 6개월 문서만"
→ "은퇴한 정책 문서는 제외"
3. 문서 타입
→ "FAQ만 검색"
→ "이번 시즌 매뉴얼만"
4. 프로젝트 / 부서 / 채널
→ "내가 참여 중인 프로젝트 문서만"
이걸 못 막으면 유출 사고가 난다. LLM은 "이건 인사 문서니까 답하지 마"라고 시켜도 무시할 수 있다. 가져오기 전에 자르는 게 안전하다.
Filter는 어디서 거는가
세 가지 위치가 가능하다.
1. Retriever 안에서 (Pre-filter)
2. 후보가 나온 뒤 (Post-filter)
3. LLM 프롬프트 안에서 (Soft-filter)
추천은 단호하다.
권한, 보안 관련 필터 → 무조건 1번 (Pre-filter)
운영 편의 필터 → 1번 또는 2번
프롬프트로 거르기 → 거의 안 됨, 절대 권한 필터로 쓰지 말 것
Elasticsearch / OpenSearch는 BM25 쿼리에 필터를 같이 넣으면 된다.
{
"query": {
"bool": {
"must": [
{ "match": { "content": "휴가" } }
],
"filter": [
{ "terms": { "allowed_user_ids": ["u123"] } },
{ "range": { "updated_at": { "gte": "2026-01-01" } } },
{ "term": { "doc_type": "policy" } }
]
}
}
}pgvector도 SQL의 WHERE 절을 그대로 쓸 수 있다.
SELECT id, content, embedding <=> $query_vec AS distance
FROM documents
WHERE 'u123' = ANY(allowed_user_ids)
AND updated_at >= '2026-01-01'
AND doc_type = 'policy'
ORDER BY distance
LIMIT 50;Pinecone, Qdrant 같은 전용 벡터 DB도 metadata filter를 지원한다.
권한 필터 설계 팁
권한 필터는 한 번 잘못 설계하면 평생 따라다닌다. 짧게 핵심만.
1. 문서마다 "허용된 주체 목록"을 박아둔다
allowed_user_ids: [u123, u456]
allowed_group_ids: [g_eng, g_sec]
visibility: "public" | "internal" | "private"
2. 검색 시점에 사용자 컨텍스트를 동적으로 만든다
사용자가 속한 그룹: [g_eng, g_payments]
사용자 ID: u123
→ 필터: allowed_user_ids contains u123
OR allowed_group_ids overlaps [g_eng, g_payments]
OR visibility = "public"
3. 삭제가 아니라 숨김으로 관리
문서가 비공개로 바뀌면 인덱스에서 지우지 말고 visibility만 바꾸자.
나중에 권한이 다시 풀릴 수 있고, 삭제는 인덱스 재구축 비용이 크다.
4. 권한 캐시는 짧게
사용자 그룹 캐시는 1~5분 정도만
어제 부서가 바뀐 사람이 어제 문서를 못 봐도 곤란하니까
Reranker와 Filter는 어디 들어가나
전체 파이프라인에서 위치는 이렇다.
질문
↓
[Sparse: BM25 + Metadata Filter (권한, 날짜, 타입)]
↓ top-50
[Dense: Vector Search + Metadata Filter]
↓ top-50
[RRF 합산]
↓ top-20
[Reranker]
↓ top-5
[LLM에 컨텍스트로 주입]
핵심 원칙은 이거다.
- Filter는 가장 앞에 (안전성, 검색 효율)
- Reranker는 가장 뒤에 (비싼 정밀 정렬은 후보가 적어진 뒤)
- 두 단계 사이에서 점수를 그대로 노출해야 디버깅 가능
정리
- Reranker는 후보를 더 비싼 모델로 다시 정렬하는 단계다
- 옵션: bge-reranker(자체 호스팅), Cohere Rerank(API), LLM Rerank(가장 비쌈)
- LTR까지 가는 건 트래픽/피드백/평가 체계가 갖춰진 회사에서나 의미 있다
- Metadata Filter는 권한/날짜/타입/부서 등 운영의 핵심이다
- 권한 필터는 무조건 Pre-filter, 프롬프트로 거르지 말 것
다음 글에서는 "왜 RAG 검색 품질이 60~70%에서 멈추는가" 를 9가지 원인으로 정리하고, 실무 검색 프로세스 13단계와 함께 RAG Evaluation을 어떻게 운영하는지 다룬다.