들어가기 전에
RAG가 사용자의 질문과 관련된 문서를 검색했다고 가정해보겠습니다.정답이 들어 있는 문서도 검색 결과에 포함되어 있습니다. 그런데 최종 답변은 틀렸습니다.
검색에 성공했는데 왜 답변은 틀린 걸까요? 검색 결과가 다음과 같은 순서로 나왔을 수 있습니다.
1위: 2024년 2분기 실적 보고서
2위: 2025년 1분기 실적 보고서
3위: 회사 소개 자료
4위: 2025년 2분기 실적 보고서
5위: 채용 공고
사용자의 질문은 다음과 같습니다.
A사의 2025년 2분기 영업이익은 얼마인가요?
정답은 4위 문서에 들어 있습니다.
하지만 RAG가 상위 3개의 문서만 LLM에 전달한다면 정답 문서는 검색되었어도 답변을 만드는 데 사용되지 못합니다.
검색 결과에는 정답 문서가 있음
하지만 LLM에 전달된 문서에는 없음
이 때 정답 문서를 더 높은 순위로 올려주는 역할을 하는 것이 Reranker입니다.
Reranker를 적용한 뒤 검색 결과가 다음처럼 바뀔 수 있습니다.
1위: 2025년 2분기 실적 보고서
2위: 2025년 1분기 실적 보고서
3위: 2024년 2분기 실적 보고서
4위: 회사 소개 자료
5위: 채용 공고
검색된 문서는 그대로입니다. 달라진 것은 문서의 순서뿐입니다.
하지만 이제 정답 문서가 LLM에 전달되므로 최종 답변도 달라질 수 있습니다.
이번 글에서는 Reranker가 문서의 순서를 어떻게 다시 정하는지, 적용 전후에 어떤 점수를 비교해야 하는지, 후보 문서 수와 응답 시간은 어떻게 조절해야 하는지 살펴보겠습니다.
검색은 됐는데 정답이 LLM까지 가지 못할 수 있습니다
RAG에서는 검색된 모든 문서를 LLM에 전달하지 않습니다. 검색 결과가 수십 개나 수백 개일 수 있지만, 실제 답변을 만들 때는 그중 일부만 사용합니다.
전체 문서
→ 관련 있을 만한 문서 20개 검색
→ 상위 5개만 선택
→ LLM에 전달
→ 답변 생성
여기에는 서로 다른 두 개의 숫자가 있습니다.
후보 문서 수: 20개
LLM에 전달하는 문서 수: 5개
후보 문서 수를 candidate_k, LLM에 전달하는 문서 수를 context_k라고 표현할 수 있습니다.
candidate_k = 20
context_k = 5
1차 검색은 정답일 가능성이 있는 문서를 후보 안에 포함하는 역할을 합니다.
Reranker는 그 후보들을 다시 살펴보고, 질문에 더 잘 맞는 문서를 위로 올리는 역할을 합니다.
1차 검색
→ 정답일 가능성이 있는 문서를 넓게 찾기
Reranking
→ 찾은 문서 중 중요한 문서를 위로 올리기
최종 문서 선택
→ 상위 몇 개만 LLM에 전달하기
정답 문서가 후보 20개 안에 있더라도 17위에 머물러 있다면, 상위 5개만 사용하는 RAG에서는 사실상 검색하지 못한 것과 같은 결과가 됩니다.
그래서 RAG에서는 문서를 찾았는지만 보는 것으로 충분하지 않습니다. 찾은 문서가 최종적으로 사용할 범위 안에 들어왔는지도 확인해야 합니다.
Reranker는 새로운 문서를 찾는 기능이 아닙니다
Reranker는 보통 1차 검색이 가져온 후보 문서를 입력으로 받습니다.
1차 검색 결과
문서 A
문서 B
문서 C
문서 D
문서 E
Reranker는 각 문서가 질문과 얼마나 관련 있는지 다시 점수를 매깁니다.
질문 + 문서 A → 관련도 점수 0.21
질문 + 문서 B → 관련도 점수 0.84
질문 + 문서 C → 관련도 점수 0.43
질문 + 문서 D → 관련도 점수 0.96
질문 + 문서 E → 관련도 점수 0.12
그다음 점수가 높은 순서로 문서를 다시 배치합니다.
1위: 문서 D → 0.96
2위: 문서 B → 0.84
3위: 문서 C → 0.43
4위: 문서 A → 0.21
5위: 문서 E → 0.12
중요한 점은 Reranker가 후보에 없던 문서를 새로 가져오지는 못한다는 것입니다.
정답 문서가 1차 검색 후보에 있음
→ Reranker가 위로 올릴 수 있음
정답 문서가 1차 검색 후보에 없음
→ Reranker도 찾을 수 없음
따라서 Reranker를 적용하기 전에 먼저 확인해야 할 것이 있습니다.
정답 문서가 1차 검색 후보 안에는 들어오고 있는가?
정답 문서가 후보에 없다면 Reranker보다 먼저 검색 방식, 검색 후보 수, 질문 변환, 필터 조건 등을 살펴봐야 합니다.
빠르게 찾는 모델과 꼼꼼히 비교하는 모델은 역할이 다릅니다
Reranking을 설명할 때 자주 등장하는 용어가 Bi-encoder와 Cross-encoder입니다.
이름은 어렵지만 두 모델의 역할 차이는 단순합니다.
Bi-encoder는 질문과 문서를 따로 읽습니다
Bi-encoder는 질문과 문서를 각각 벡터로 바꿉니다.
질문
→ 질문 벡터
문서
→ 문서 벡터
그다음 두 벡터가 얼마나 가까운지 비교합니다.
질문 벡터와 문서 A 벡터의 거리
질문 벡터와 문서 B 벡터의 거리
질문 벡터와 문서 C 벡터의 거리
문서 벡터는 미리 계산해 저장해둘 수 있습니다. 새로운 질문이 들어오면 질문 벡터만 만든 뒤 가까운 문서 벡터를 빠르게 찾으면 됩니다.
그래서 수십만 개나 수백만 개의 문서 중 후보를 찾는 1차 검색에 적합합니다. 다만 질문과 문서를 따로 읽기 때문에, 질문의 세부 조건과 문서 내용을 정밀하게 비교하는 데에는 한계가 있을 수 있습니다.
Cross-encoder는 질문과 문서를 함께 읽습니다
Cross-encoder는 질문과 문서를 하나의 입력으로 묶어서 읽습니다.
질문:
A사의 2025년 2분기 영업이익은 얼마인가요?
문서:
A사의 2024년 2분기 영업이익은 53억 원입니다.
질문과 문서가 같은 회사와 같은 분기를 언급하지만 연도가 다르다는 점을 함께 살펴볼 수 있습니다.
다른 문서도 같은 방식으로 비교합니다.
질문 + 문서 A
질문 + 문서 B
질문 + 문서 C
질문과 문서를 함께 읽으므로 더 세밀한 관련도 판단을 기대할 수 있습니다. 하지만 문서마다 질문과 짝을 만들어 모델을 실행해야 합니다.
문서가 100만 개라면 질문과 문서의 조합도 100만 개를 확인해야 하므로, 전체 문서를 대상으로 사용하기에는 느리고 비용이 많이 들 수 있습니다. 그래서 보통 두 단계를 결합합니다.
Bi-encoder 또는 BM25
→ 넓은 문서에서 후보를 빠르게 찾음
Cross-encoder
→ 후보 문서만 꼼꼼하게 비교함
도서관에 비유하면 이해하기 쉽습니다.
1차 검색은 도서관 전체에서 제목과 분류를 보고 관련 있어 보이는 책 20권을 빠르게 가져오는 과정입니다.
Reranker는 그 20권의 목차와 내용을 질문과 다시 비교해서, 실제로 필요한 책을 위에 놓는 과정입니다.
모든 Reranker가 Cross-encoder인 것은 아닙니다
Cross-encoder는 Reranker를 구현하는 대표적인 방법이지만, 모든 Reranker가 Cross-encoder인 것은 아닙니다.
다음처럼 여러 방식이 사용될 수 있습니다.
- Cross-encoder 기반 Reranker
- 생성형 모델 기반 Reranker
- 여러 문서를 한 번에 비교하는 Listwise Reranker
- 검색 점수를 다시 조합하는 규칙 기반 Reranker
- Late Interaction 기반 모델
이번 글에서는 이해하기 쉽고 많이 사용되는 Cross-encoder 방식의 Reranker를 기준으로 설명하겠습니다.
구현 방식이 달라져도 평가할 때 확인해야 할 핵심은 같습니다.
- 정답 문서의 순위가 실제로 올라갔는가?
- 최종 문서 안에 정답 근거가 더 자주 포함되는가?
- 답변 품질도 함께 좋아졌는가?
- 추가된 시간과 비용은 감당할 수 있는가?
같은 검색 결과를 다시 정렬해보겠습니다
사용자가 다음과 같이 질문했다고 가정해보겠습니다.
질문: A사의 2025년 2분기 영업이익은 얼마인가요?
1차 검색 결과는 다음과 같습니다.
| 순위 | 문서 정답 | 근거인가? |
| 1위 | 2024년 2분기 실적 보고서 | 아니요 |
| 2위 | 2025년 1분기 실적 보고서 | 아니요 |
| 3위 | 2025년 연간 사업 계획 | 아니요 |
| 4위 | 2025년 2분기 실적 보고서 | 예 |
| 5위 | 회사 소개 자료 | 아니요 |
정답 문서는 4위에 있습니다. 이 RAG가 상위 3개 문서만 LLM에 전달한다면 정답 문서는 제외됩니다.
LLM에 전달되는 문서
1위: 2024년 2분기 실적 보고서
2위: 2025년 1분기 실적 보고서
3위: 2025년 연간 사업 계획
Reranker를 적용한 결과가 다음과 같다고 해보겠습니다.
| 순위 | 문서 | 정답 근거인가 |
| 1위 | 2025년 2분기 실적 보고서 | 예 |
| 2위 | 2025년 1분기 실적 보고서 | 아니요 |
| 3위 | 2024년 2분기 실적 보고서 | 아니요 |
| 4위 | 2025년 연간 사업 계획 | 아니요 |
| 5위 | 회사 소개 자료 | 아니요 |
문서가 새로 추가되거나 삭제된 것은 아닙니다. 4위에 있던 정답 문서가 1위로 이동했습니다.
이제 LLM에 전달되는 상위 3개 문서 안에 정답이 포함됩니다.
Reranking 전
→ 정답 문서가 최종 문맥에 없음
Reranking 후
→ 정답 문서가 최종 문맥에 있음
이 차이만으로도 최종 답변이 좋아질 수 있습니다.
Recall이 그대로인데 답변은 좋아질 수 있습니다
Reranker를 평가할 때 가장 헷갈리기 쉬운 부분입니다. 후보 문서 5개를 기준으로 보면 정답 문서는 Reranking 전에도 검색되었습니다.
Reranking 전 Recall@5 = 1
Reranking 후 Recall@5 = 1
두 점수가 같습니다.
Reranker는 후보 문서를 새로 찾은 것이 아니라 기존 후보의 순서만 바꿨기 때문입니다. 하지만 LLM에 전달하는 문서가 상위 3개라면 결과가 달라집니다.
Reranking 전 Recall@3 = 0
Reranking 후 Recall@3 = 1
이를 구분해서 봐야 합니다.
후보 단계의 Recall@5
→ 1차 검색이 정답 문서를 후보에 포함했는가?
최종 문서 단계의 Recall@3
→ Reranking 후 정답 문서가 LLM에 전달되는 범위에 들어왔는가?
따라서 단순히 “Reranker를 적용해도 Recall은 변하지 않는다”고 말하면 정확하지 않습니다.
같은 후보 문서 전체를 기준으로 한 Recall은 그대로일 수 있지만, 최종적으로 선택하는 상위 K개의 Recall은 달라질 수 있습니다.
이것이 검색 결과의 순서만 바꿨는데 답변이 좋아질 수 있는 이유입니다.
Reranker를 평가할 때 어떤 점수를 봐야 할까요?
Reranker의 효과를 확인하려면 순위 지표와 최종 답변 지표를 함께 봐야 합니다.
후보 단계의 Recall@K
먼저 1차 검색이 Reranker가 사용할 후보 안에 정답 문서를 포함했는지 확인해야 합니다.
후보 문서 수: 20개
확인할 지표:
Recall@20
Recall@20이 낮다면 Reranker가 아무리 좋아도 정답 문서를 위로 올릴 수 없습니다.
Reranker를 평가하기 전에 1차 검색의 후보 Recall이 충분한지 확인하는 것이 좋습니다.
Hit@K
Hit@K는 상위 K개 안에 정답 문서가 하나라도 있는지를 확인합니다.
Hit@5 = 1
→ 상위 5개 안에 정답 문서가 있음
Hit@5 = 0
→ 상위 5개 안에 정답 문서가 없음
질문마다 핵심 정답 문서가 하나이고, 그 문서가 최종 문맥 안에 들어오기만 하면 되는 경우 이해하기 쉬운 지표입니다.
MRR
MRR은 첫 번째 정답 문서가 얼마나 높은 순위에 있는지를 봅니다.
정답 문서가 1위라면 점수는 1입니다.
정답 문서가 1위
→ 1 / 1 = 1
정답 문서가 4위라면 점수는 0.25입니다.
정답 문서가 4위
→ 1 / 4 = 0.25
여러 질문에서 계산한 값을 평균 내면 MRR이 됩니다.
Reranking 전후에 첫 번째 정답 문서의 위치가 얼마나 좋아졌는지 확인하기 좋습니다.
다만 여러 정답 문서의 순서를 모두 평가하기보다는, 가장 먼저 등장한 정답 문서에 집중하는 지표입니다.
NDCG@K
NDCG@K는 관련성이 높은 문서가 상위에 배치되었는지를 평가합니다.
모든 관련 문서가 같은 중요도를 가지지 않을 때 특히 유용합니다.
예를 들어 관련도 점수를 다음처럼 정할 수 있습니다.
3점: 질문의 정답을 직접 포함한 문서
2점: 답변에 필요한 보조 근거
1점: 같은 주제를 설명하지만 직접적인 근거는 아닌 문서
0점: 질문과 관련 없는 문서
Reranker가 3점 문서를 1위에 올리고 2점 문서를 2위에 배치했다면 높은 평가를 받을 수 있습니다.
반대로 관련 문서는 모두 검색했지만 3점짜리 핵심 근거를 아래에 배치하면 점수가 낮아집니다.
여러 문서가 필요한 질문이나, 문서마다 중요도가 다른 RAG에서는 MRR보다 NDCG@K가 더 많은 정보를 보여줄 수 있습니다.
Precision@K
Precision@K는 상위 K개 문서 중 실제로 관련 있는 문서가 얼마나 되는지 확인합니다.
상위 5개 중 관련 문서 4개
Precision@5
= 4 / 5
= 0.8
Reranker를 적용한 뒤 Precision@5가 높아졌다면 LLM에 전달되는 문서에서 불필요한 내용이 줄었다고 볼 수 있습니다.
RAG에서는 정답 문서가 포함되는 것만큼 관련 없는 문서를 줄이는 것도 중요합니다.
잘못된 연도, 다른 제품 버전, 적용 대상이 다른 정책이 함께 들어오면 LLM이 문서를 혼동할 수 있기 때문입니다.
최종 답변 정확도
순위가 좋아졌다고 해서 최종 답변도 반드시 좋아지는 것은 아닙니다.
따라서 Reranking 전후의 답변 정확도도 비교해야 합니다.
Reranking 전 답변 정확도: 68%
Reranking 후 답변 정확도: 76%
검색 순위의 개선이 실제 답변까지 이어졌는지를 확인하는 지표입니다.
가능하다면 답변이 전달된 근거와 일치하는지도 함께 확인하는 것이 좋습니다.
추가된 응답 시간과 비용
Reranker는 후보 문서마다 질문과의 관련도를 계산합니다.
후보가 많거나 모델이 크면 처리 시간이 늘어날 수 있습니다.
따라서 다음 항목도 함께 기록해야 합니다.
평균 Reranking 시간
p95 Reranking 시간
질문 한 건당 비용
사용한 후보 문서 수
Reranker 오류율
평균 응답 시간만 보면 일부 느린 요청이 가려질 수 있으므로 실제 서비스에서는 p95처럼 대부분의 사용자가 경험하는 느린 구간도 함께 보는 것이 좋습니다.
평가 항목을 정리하면 다음과 같습니다
Reranker를 추가한 뒤에는 다음 항목을 비교할 수 있습니다.
1. 후보 단계의 Recall@K
2. 최종 문서 단계의 Hit@K 또는 Recall@K
3. MRR
4. NDCG@K
5. Precision@K
6. 최종 답변 정확도
7. 답변과 근거의 일치 여부
8. 추가된 응답 시간과 비용
정답 문서의 평균 순위도 기록할 수 있습니다. 다만 일부 질문에서 정답 문서가 매우 낮은 순위에 있으면 평균이 크게 흔들릴 수 있고 검색되지 않은 문서를 몇 위로 처리할지도 정해야 합니다.
따라서 평균 순위는 주요 점수라기보다 결과를 이해하기 위한 보조 정보로 활용하는 편이 좋습니다.
적용 전후 결과를 비교해보겠습니다
앞의 예시를 기준으로 점수를 계산해보겠습니다. 정답 문서는 doc-q2-2025 하나라고 가정합니다.
Reranking 전 순서는 다음과 같습니다.
before_reranking = [
"doc-q2-2024",
"doc-q1-2025",
"doc-plan-2025",
"doc-q2-2025",
"doc-company-intro",
]
Reranking 후 순서는 다음과 같습니다.
after_reranking = [
"doc-q2-2025",
"doc-q1-2025",
"doc-q2-2024",
"doc-plan-2025",
"doc-company-intro",
]
정답 문서를 표시합니다.
relevant_ids = {"doc-q2-2025"}
결과는 다음과 같습니다.
| 지표 | 적용 전 | 적용 후 |
| 후보 Recall@5 | 1.00 | 1.00 |
| Hit@3 | 0.00 | 1.00 |
| Recall@3 | 0.00 | 1.00 |
| Precision@3 | 0.00 | 약 0.33 |
| MRR | 0.25 | 1.00 |
| NDCG@3 | 0.00 | 1.00 |
후보 Recall@5는 달라지지 않았습니다. 정답 문서는 처음부터 후보 안에 있었기 때문입니다.
하지만 최종적으로 LLM에 전달하는 상위 3개를 기준으로 하면 모든 순위 지표가 좋아졌습니다.
이 표를 통해 Reranker의 역할을 구체적으로 설명할 수 있습니다.
새로운 정답 문서를 찾은 것은 아닙니다. 기존 후보에 있던 정답 문서를 4위에서 1위로 올렸습니다.
그 결과 정답 문서가 최종 문맥에 포함되었습니다.
Python으로 순위 지표 계산하기
간단한 함수로 적용 전후의 점수를 계산할 수 있습니다.
from math import log2
def hit_at_k(
ranked_ids: list[str],
relevant_ids: set[str],
k: int,
) -> float:
top_k = ranked_ids[:k]
return float(
any(document_id in relevant_ids for document_id in top_k)
)
def precision_at_k(
ranked_ids: list[str],
relevant_ids: set[str],
k: int,
) -> float:
top_k = ranked_ids[:k]
if not top_k:
return 0.0
relevant_count = sum(
document_id in relevant_ids
for document_id in top_k
)
return relevant_count / len(top_k)
def recall_at_k(
ranked_ids: list[str],
relevant_ids: set[str],
k: int,
) -> float:
if not relevant_ids:
return 0.0
top_k = ranked_ids[:k]
relevant_count = sum(
document_id in relevant_ids
for document_id in top_k
)
return relevant_count / len(relevant_ids)
def reciprocal_rank(
ranked_ids: list[str],
relevant_ids: set[str],
) -> float:
for rank, document_id in enumerate(ranked_ids, start=1):
if document_id in relevant_ids:
return 1 / rank
return 0.0
NDCG@K도 계산해보겠습니다.
def dcg(gains: list[int]) -> float:
return sum(
(2 ** gain - 1) / log2(rank + 1)
for rank, gain in enumerate(gains, start=1)
)
def ndcg_at_k(
ranked_ids: list[str],
relevance_scores: dict[str, int],
k: int,
) -> float:
actual_gains = [
relevance_scores.get(document_id, 0)
for document_id in ranked_ids[:k]
]
ideal_gains = sorted(
relevance_scores.values(),
reverse=True,
)[:k]
ideal_dcg = dcg(ideal_gains)
if ideal_dcg == 0:
return 0.0
return dcg(actual_gains) / ideal_dcg
예시 데이터를 넣어보겠습니다.
before_reranking = [
"doc-q2-2024",
"doc-q1-2025",
"doc-plan-2025",
"doc-q2-2025",
"doc-company-intro",
]
after_reranking = [
"doc-q2-2025",
"doc-q1-2025",
"doc-q2-2024",
"doc-plan-2025",
"doc-company-intro",
]
relevant_ids = {"doc-q2-2025"}
relevance_scores = {
"doc-q2-2025": 3,
"doc-q1-2025": 0,
"doc-q2-2024": 0,
"doc-plan-2025": 0,
"doc-company-intro": 0,
}
지표를 출력합니다.
for name, ranked_ids in [
("적용 전", before_reranking),
("적용 후", after_reranking),
]:
print(name)
print("Hit@3:", hit_at_k(ranked_ids, relevant_ids, 3))
print(
"Precision@3:",
precision_at_k(ranked_ids, relevant_ids, 3),
)
print(
"Recall@3:",
recall_at_k(ranked_ids, relevant_ids, 3),
)
print("MRR:", reciprocal_rank(ranked_ids, relevant_ids))
print(
"NDCG@3:",
ndcg_at_k(ranked_ids, relevance_scores, 3),
)
이처럼 같은 후보 목록의 순서만 바꾸고 지표를 다시 계산하면 Reranker가 실제로 어떤 영향을 주었는지 확인할 수 있습니다.
Cross-encoder로 문서를 다시 정렬해보기
Sentence Transformers의 Cross-encoder를 사용하면 질문과 후보 문서의 관련도 점수를 계산할 수 있습니다.
from sentence_transformers import CrossEncoder
model = CrossEncoder(
"cross-encoder/ms-marco-MiniLM-L6-v2"
)
query = "A사의 2025년 2분기 영업이익은 얼마인가요?"
candidates = [
{
"id": "doc-q2-2024",
"text": "A사의 2024년 2분기 영업이익은 53억 원입니다.",
},
{
"id": "doc-q1-2025",
"text": "A사의 2025년 1분기 영업이익은 31억 원입니다.",
},
{
"id": "doc-plan-2025",
"text": "A사는 2025년 영업이익 개선을 주요 목표로 세웠습니다.",
},
{
"id": "doc-q2-2025",
"text": "A사의 2025년 2분기 영업이익은 35억 원입니다.",
},
]
질문과 각 문서를 하나의 쌍으로 만듭니다.
pairs = [
(query, candidate["text"])
for candidate in candidates
]
각 문서의 점수를 계산한 뒤 높은 순서로 정렬합니다.
scores = model.predict(pairs)
reranked = sorted(
[
{
**candidate,
"rerank_score": float(score),
}
for candidate, score in zip(
candidates,
scores,
strict=True,
)
],
key=lambda item: item["rerank_score"],
reverse=True,
)
결과를 확인합니다.
for rank, document in enumerate(reranked, start=1):
print(
rank,
document["id"],
document["rerank_score"],
)
실제 서비스에서는 Reranker의 점수 자체보다 정렬된 순서를 사용하는 경우가 많습니다.
모델마다 점수의 범위와 의미가 다를 수 있으므로, 서로 다른 모델의 원점수를 그대로 비교하기보다는 같은 평가 데이터에서 순위 지표를 비교하는 편이 안전합니다.
후보 문서를 많이 넘기면 더 좋아질까요?
Reranker는 후보 문서 안에서만 순서를 바꿀 수 있습니다. 따라서 후보 문서 수가 너무 적으면 정답 문서가 빠질 가능성이 커집니다.
후보 문서 5개
→ 정답 문서가 8위에 있었다면 Reranker에 전달되지 않음
후보 문서 수를 늘리면 정답을 포함할 가능성이 높아질 수 있습니다.
후보 문서 20개
→ 8위에 있던 정답 문서가 Reranker에 전달됨
→ Reranker가 1위로 올릴 수 있음
그렇다고 후보 문서 수를 계속 늘리는 것이 항상 좋은 것은 아닙니다. 후보가 늘어날수록 Reranker가 비교해야 하는 질문과 문서 쌍도 늘어납니다.
후보 10개
→ 질문과 문서 쌍 10개 평가
후보 50개
→ 질문과 문서 쌍 50개 평가
후보 100개
→ 질문과 문서 쌍 100개 평가
처리 시간과 비용이 함께 증가할 수 있습니다. 또한 후보 문서 수를 크게 늘려도 새로운 정답 문서가 거의 추가되지 않는 구간이 생길 수 있습니다.
예를 들어 다음과 같은 가상의 결과가 있다고 하겠습니다.
| 후보 문서 수 | 후보 Recall | NDCG@5 | Reranking 시간 |
| 5개 | 0.68 | 0.65 | 40ms |
| 10개 | 0.79 | 0.73 | 70ms |
| 20개 | 0.87 | 0.80 | 130ms |
| 50개 | 0.89 | 0.81 | 310ms |
후보를 5개에서 20개로 늘렸을 때는 Recall과 NDCG가 크게 좋아졌습니다.
하지만 20개에서 50개로 늘렸을 때는 점수가 거의 변하지 않았고 처리 시간만 크게 늘었습니다. 이 경우에는 후보 20개가 더 현실적인 선택일 수 있습니다.
위 숫자는 설명을 위한 예시입니다. 실제 값은 사용하는 모델, 문서 길이, 서버 성능과 배치 처리 방식에 따라 달라지므로 직접 측정해야 합니다.
후보 문서 수와 최종 문서 수는 따로 실험해야 합니다
후보 문서 수와 LLM에 전달할 문서 수는 서로 다른 설정입니다.
candidate_k
→ Reranker가 살펴볼 후보 문서 수
context_k
→ Reranking 후 LLM에 전달할 문서 수
다음과 같이 구성할 수 있습니다.
1차 검색 후보: 20개
Reranking 대상: 20개
최종 전달 문서: 5개
후보 문서 수를 늘리는 실험과 최종 문서 수를 늘리는 실험을 동시에 진행하면 결과를 해석하기 어렵습니다.
먼저 후보 문서 수만 비교합니다.
후보 10개 → 최종 5개
후보 20개 → 최종 5개
후보 50개 → 최종 5개
그다음 최종 문서 수만 비교합니다.
후보 20개 → 최종 3개
후보 20개 → 최종 5개
후보 20개 → 최종 10개
이렇게 해야 후보 Recall의 변화와 LLM에 전달되는 문서 수의 영향을 구분할 수 있습니다.
순위가 좋아졌다고 답변도 반드시 좋아지는 것은 아닙니다
Reranker를 적용한 뒤 MRR과 NDCG가 높아졌지만 최종 답변은 그대로일 수 있습니다.
여러 이유가 있습니다.
원래도 정답 문서가 전달되고 있었습니다
정답 문서가 3위에서 1위로 올라왔지만, 원래 상위 5개를 모두 전달하고 있었다면 LLM이 보는 문서 집합은 달라지지 않습니다.
적용 전 정답 문서: 3위
적용 후 정답 문서: 1위
최종 전달 범위: 상위 5개
순위 지표는 좋아졌지만 최종 답변에는 변화가 없을 수 있습니다.
Reranking 뒤에 다시 문서를 재배치했습니다
Reranking 이후 중복 제거, 문서 압축, 길이 제한 등의 과정이 추가로 적용될 수 있습니다.
Reranking 결과
→ 정답 문서 1위
중복 제거 과정
→ 정답 문서가 잘못 제거됨
최종 문맥
→ 정답 문서 없음
따라서 Reranking 결과뿐 아니라 LLM에 실제로 전달된 최종 문맥도 확인해야 합니다.
LLM이 근거를 제대로 사용하지 못했습니다
정답 문서가 1위로 전달되었더라도 LLM이 내용을 잘못 해석할 수 있습니다.
정답 문서 전달 성공
하지만 최종 답변은 오답
이 경우에는 순위 문제가 아니라 답변 생성 단계의 문제입니다.
정답 문서의 일부만 포함되었습니다
검색된 청크에 숫자는 있지만 적용 대상이나 기간이 빠져 있을 수 있습니다.
순위는 높아졌지만 청킹으로 문맥이 끊겼다면 정확한 답변을 만들기 어렵습니다.
그래서 Reranker의 효과는 검색 순위와 최종 답변을 함께 평가해야 합니다.
Reranker가 고칠 수 없는 문제도 있습니다
다음과 같은 문제는 문서 순서를 다시 정하는 것만으로 해결하기 어렵습니다.
- 지식 베이스에 정답 문서가 없음
- 문서를 읽는 과정에서 정답이 사라짐
- 청킹으로 조건과 결론이 분리됨
- 정답 문서가 1차 검색 후보에 포함되지 않음
- 권한 필터가 정답 문서를 제외함
- 잘못된 문서 버전만 등록됨
- 최종 문맥 구성 과정에서 정답이 다시 제거됨
- 근거가 있는데도 LLM이 잘못 답함
특히 정답 문서가 후보에 없는 경우가 중요합니다.
Reranker 적용 결과가 좋지 않다고 해서 곧바로 Reranker 모델을 교체하기 전에, 정답 문서가 입력 후보에 들어왔는지부터 확인해야 합니다.
정답 문서가 후보에 없음
→ 1차 검색 문제
정답 문서는 후보에 있지만 순위가 낮음
→ Reranking으로 개선할 수 있는 문제
정답 문서가 상위에 있지만 최종 문맥에서 빠짐
→ 문맥 구성 문제
정답 문서가 전달되었지만 오답
→ 답변 생성 문제
Reranker는 검색 파이프라인의 모든 문제를 해결하는 기능이 아닙니다. 후보 안에 있는 좋은 문서를 더 잘 고르는 역할에 가깝습니다.
같은 조건에서 켜고 꺼봐야 합니다
Reranker의 효과를 비교할 때는 Reranker 사용 여부를 제외한 나머지 조건을 같게 유지해야 합니다.
기준 구성
하이브리드 검색
후보 문서 수: 20개
Reranker: 사용 안 함
최종 문서 수: 5개
청크 크기: 500
같은 LLM과 프롬프트 사용
비교 구성
하이브리드 검색
후보 문서 수: 20개
Reranker: 사용
최종 문서 수: 5개
청크 크기: 500
같은 LLM과 프롬프트 사용
두 구성에서 달라진 것은 Reranker뿐입니다.
다음 조건도 동일해야 합니다.
| 항목 | 동일하게 유지할 내용 |
| 평가 질문 | 같은 질문 세트 |
| 정답 데이터 | 같은 정답 문서와 관련도 |
| 문서 | 같은 지식 베이스 버전 |
| 청킹 | 같은 크기와 겹치는 범위 |
| 1차 검색 | 같은 검색 방식과 설정 |
| 후보 문서 수 | 같은 candidate_k |
| 최종 문서 수 | 같은 context_k |
| 생성 모델 | 같은 LLM과 설정 |
| 프롬프트 | 같은 답변 지시문 |
Reranker를 추가하면서 후보 문서 수나 청크 크기까지 함께 바꾸면 어떤 변경이 효과를 냈는지 알 수 없습니다.
평균 점수만 보지 말고 질문 유형도 나눠봐야 합니다
Reranker가 모든 질문에서 같은 효과를 내는 것은 아닙니다.
예를 들어 다음과 같은 질문에서 효과가 크게 나타날 수 있습니다.
- 회사명은 같지만 연도가 다른 문서를 구분해야 하는 질문
- 제품명은 같지만 버전이 다른 질문
- 적용 대상이 다른 정책을 구분해야 하는 질문
- 비슷한 단어가 많은 문서 중 정확한 조건을 찾아야 하는 질문
반면 정답 문서가 애초에 검색되지 않는 질문에서는 효과가 없습니다. 질문 유형별 결과가 다음과 같을 수 있습니다.
| 질문 유형 | 적용 전 NDCG@5 | 적용 후 NDCG@5 |
| 단순 사실 질문 | 0.82 | 0.84 |
| 숫자·날짜 질문 | 0.58 | 0.76 |
| 고유명사 질문 | 0.66 | 0.74 |
| 조건이 많은 질문 | 0.51 | 0.71 |
| 여러 문서가 필요한 질문 | 0.63 | 0.65 |
전체 점수만 보면 Reranker가 조금 좋아진 것처럼 보일 수 있습니다.
하지만 질문 유형별로 보면 숫자와 날짜, 복잡한 조건을 구분하는 질문에서 주로 효과가 나타났다는 것을 알 수 있습니다.
이렇게 분석해야 어떤 상황에서 Reranker를 사용할 가치가 있는지 설명할 수 있습니다.
모든 질문에 적용하지 않아도 됩니다
Reranking이 효과적이지만 응답 시간이 부담된다면 일부 질문에만 적용하는 방식도 생각해볼 수 있습니다.
예를 들어 다음과 같은 경우에만 Reranker를 실행할 수 있습니다.
- 1차 검색 점수 차이가 작을 때
- 검색 결과에 비슷한 문서가 많을 때
- 연도나 버전 같은 조건이 포함된 질문일 때
- 정확도가 특히 중요한 질문일 때
- 사용자가 상세 검색을 요청했을 때
반대로 1위 문서의 검색 점수가 매우 높고 다른 후보와 차이가 크다면 Reranking을 생략할 수도 있습니다.
다만 이런 조건부 적용 방식도 실제 평가를 통해 검증해야 합니다. Reranker를 항상 사용하는 구성과 선택적으로 사용하는 구성을 비교해서 정확도 응답 시간과 비용이 어떻게 달라지는지 확인하는 것이 좋습니다.
중간 정리
| 구분 | 역할 | 확인할 점 |
| 1차 검색 | 넓은 문서에서 후보를 빠르게 찾음 | 후보 Recall@K |
| Reranker | 후보 문서의 순서를 다시 정함 | MRR, NDCG@K |
| 최종 문서 선택 | 상위 일부 문서를 LLM에 전달 | Hit@K, Recall@K, Precision@K |
| 답변 생성 | 전달된 근거를 이용해 답변 | 답변 정확도, 근거 일치 여부 |
| 운영 성능 | 실제 서비스 적용 가능성 확인 | 응답 시간, 비용, 오류율 |
Reranker를 평가할 때 기억해야 할 핵심은 다음과 같습니다.
- Reranker는 후보에 없는 문서를 찾을 수 없습니다.
- 후보 전체의 Recall은 그대로일 수 있습니다.
- 최종 상위 K개의 Recall과 Precision은 달라질 수 있습니다.
- 순위 점수가 좋아져도 답변이 반드시 좋아지는 것은 아닙니다.
- 후보 수를 늘리면 기회와 처리 비용이 함께 늘어납니다.
- 검색 지표와 답변 지표를 함께 확인해야 합니다.
마치며
검색 결과의 순서만 바꿔도 RAG의 답변은 좋아질 수 있습니다. RAG가 검색된 모든 문서를 사용하는 것이 아니라 상위 몇 개만 골라 LLM에 전달하기 때문입니다.
정답 문서가 후보에는 있지만 최종 선택 범위 밖에 있다면 LLM은 그 문서를 볼 수 없습니다.
Reranker는 이런 문서를 질문과 다시 비교해서 더 높은 순위로 올립니다.
1차 검색
→ 정답 문서가 후보에 들어오도록 넓게 찾기
Reranking
→ 정답 문서가 최종 선택 범위에 들어오도록 순서 정하기
두 단계는 역할이 다릅니다. Reranker를 적용한 뒤 후보 Recall@20이 그대로라고 해서 효과가 없다고 단정하면 안 됩니다.
상위 5개만 LLM에 전달한다면 MRR, NDCG@5, Precision@5와 Recall@5가 어떻게 변했는지 확인해야 합니다.
최종 답변의 정확도와 근거 일치 여부도 함께 봐야 하죠.
반대로 순위 지표가 좋아졌다고 무조건 성공한 것도 아닙니다.
정답 문서가 원래도 LLM에 전달되고 있었다면 답변은 달라지지 않을 수 있습니다. 문서를 다시 고르는 과정에서 정답이 빠지거나LLM이 주어진 근거를 잘못 사용할 수도 있습니다. Reranker를 평가할 때는 다음 질문에 답할 수 있어야 합니다.
- 정답 문서는 1차 후보 안에 있었는가?
- Reranker가 정답 문서의 순위를 올렸는가?
- 정답 문서가 최종 문맥에 더 자주 포함되었는가?
- 불필요한 문서는 줄었는가?
- 최종 답변도 실제로 좋아졌는가?
- 추가된 시간과 비용은 감당할 수 있는가?
이 질문에 답해야 Reranker를 추가할 가치가 있었는지 판단할 수 있습니다. 검색 결과의 순서를 바꾸는 일은 사소해 보일 수 있습니다.
하지만 LLM이 어떤 문서를 보게 될지를 결정한다는 점에서 그 순서는 최종 답변을 바꾸는 중요한 요소가 될 수 있습니다.
참고 자료
- Sentence Transformers 공식 문서 - Retrieve & Re-Rank
- Sentence Transformers 공식 문서 - Cross-Encoder Usage
- Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks
- Augmented SBERT: Data Augmentation Method for Improving Bi-Encoders for Pairwise Sentence Scoring Tasks
- Passage Re-ranking with BERT
- Introduction to Information Retrieval - Evaluation of Ranked Retrieval Results
- Searching for Best Practices in Retrieval-Augmented Generation
'AI' 카테고리의 다른 글
| [바미] RAG는 어떤 질문에 약할까? (0) | 2026.07.30 |
|---|---|
| [바미] 문서를 잘게 쪼갰더니 오히려 답을 못 찾았습니다 (0) | 2026.07.29 |
| [바미] RAG가 좋아진 이유가 정말 그 설정 때문일까? (0) | 2026.07.27 |
| [바미] RAG는 왜 틀린 답을 할까? (0) | 2026.07.26 |
| [바미] 검색 결과는 몇 개까지 봐야 할까? - 상위 K개 평가 이해하기 (0) | 2026.07.25 |