검색 정확도를 더 높이는 방법 - Reranking
지난 글에서 LangChain으로 RAG 파이프라인을 처음부터 끝까지 구축해봤습니다. 그런데 실제로 서비스를 운영하다 보면, 벡터 검색만으로는 항상 "가장 적절한" 문서가 상위에 오지는 않는다는 걸 발견하게 됩니다. 이번 글에서는 이 문제를 보완하는 Reranking(재정렬) 기법을 정리합니다.
1. 벡터 검색만으로 충분하지 않은 이유
벡터 검색은 코사인 유사도라는 하나의 기준으로만 문서를 정렬합니다. 그런데 유사도가 높다고 해서 항상 질문에 가장 적합한 답인 것은 아닙니다.
질문: "재택근무 승인은 누가 하나요?"
벡터 검색 결과 (유사도 순)
1위 (0.81) "재택근무는 팀장 승인 하에 주 2회까지 가능합니다."
2위 (0.79) "재택근무 신청은 인사 시스템을 통해 이루어집니다."
3위 (0.76) "원격근무 제도는 2020년부터 시행되었습니다."
1위 문서가 질문의 "승인 주체"에 정확히 답하고 있지만, 2위나 3위 문서도 유사도 점수 차이가 크지 않아 애매하게 함께 검색되는 경우가 많습니다. 벡터 검색은 "의미적으로 비슷한 문서"를 찾는 데는 강하지만, "질문에 가장 정확히 답하는 문서가 무엇인지" 세밀하게 순위를 매기는 데는 한계가 있습니다.
2. Reranking이란
Reranking은 벡터 검색으로 1차 후보군(예: 상위 20개)을 넉넉하게 뽑은 뒤, 이 후보들을 질문과의 관련성 기준으로 다시 한번 정밀하게 채점해서 순위를 재조정하는 단계입니다.
[벡터 검색만 사용]
질문 → 벡터 검색 → Top 3 → LLM 전달
[Reranking 추가]
질문 → 벡터 검색 → Top 20(넉넉하게) → Reranker로 재채점 → Top 3 → LLM 전달
핵심 아이디어는 "1차로 넓게 걸러내고, 2차로 정밀하게 추린다"는 2단계 검색 구조입니다.
3. 벡터 검색과 Reranker는 무엇이 다른가
| 구분 | 벡터 검색 (Bi-encoder) | Reranker (Cross-encoder) |
|---|---|---|
| 방식 | 질문과 문서를 각각 따로 벡터로 변환 후 거리 비교 | 질문과 문서를 함께 입력해서 관련성 점수를 직접 계산 |
| 속도 | 매우 빠름 (미리 계산된 벡터끼리 비교) | 상대적으로 느림 (쌍마다 모델을 새로 실행) |
| 정확도 | 대략적인 유사도 파악에 강함 | 질문-문서 관계를 세밀하게 파악하는 데 강함 |
| 대상 규모 | 수백만 건에도 적용 가능 | 소수의 후보군(수십 건)에만 적용 |
벡터 검색이 빠르지만 다소 거친 1차 필터라면, Reranker는 느리지만 정밀한 2차 정밀 심사라고 이해하면 됩니다. 그래서 전체 문서에 Reranker를 바로 적용하지 않고, 벡터 검색으로 후보를 좁힌 다음에만 사용합니다.
4. Cohere Rerank API로 적용하기
Reranker도 직접 모델을 구축하지 않고, Cohere 같은 API를 가져다 쓰는 것이 일반적입니다.
import cohere
co = cohere.Client("YOUR_API_KEY")
query = "재택근무 승인은 누가 하나요?"
candidates = [
"재택근무는 팀장 승인 하에 주 2회까지 가능합니다.",
"재택근무 신청은 인사 시스템을 통해 이루어집니다.",
"원격근무 제도는 2020년부터 시행되었습니다.",
]
response = co.rerank(
model="rerank-multilingual-v3.0",
query=query,
documents=candidates,
top_n=3
)
for result in response.results:
print(f"{result.relevance_score:.4f} | {candidates[result.index]}")
[출력 예시]
0.9123 | 재택근무는 팀장 승인 하에 주 2회까지 가능합니다.
0.3841 | 재택근무 신청은 인사 시스템을 통해 이루어집니다.
0.0526 | 원격근무 제도는 2020년부터 시행되었습니다.
벡터 검색 단계에서는 유사도 차이가 크지 않던 문서들이, Reranker를 거치면서 질문의 핵심("승인 주체")과 직접 관련된 문서와 그렇지 않은 문서 사이의 점수 차이가 훨씬 뚜렷하게 벌어지는 걸 확인할 수 있습니다.
5. LangChain 파이프라인에 Reranker 추가하기
지난 글에서 구성한 Retriever 뒤에 Reranker 단계를 이어 붙이면 됩니다.
from langchain.retrievers import ContextualCompressionRetriever
from langchain_cohere import CohereRerank
base_retriever = vectorstore.as_retriever(search_kwargs={"k": 20})
reranker = CohereRerank(model="rerank-multilingual-v3.0", top_n=3)
compression_retriever = ContextualCompressionRetriever(
base_compressor=reranker,
base_retriever=base_retriever
)
기존 retriever 자리에 compression_retriever를 그대로 넣으면, 벡터 검색으로 20개를 넉넉히 가져온 뒤 Reranker가 그중 가장 관련성 높은 3개만 추려서 LLM에게 전달하는 구조로 바뀝니다.
6. Reranking 적용 전후 비교
| 항목 | Reranking 미적용 | Reranking 적용 |
|---|---|---|
| 검색 방식 | 벡터 유사도 Top-K 그대로 사용 | 벡터 검색 후보군 재정렬 |
| 관련 없는 문서 혼입 가능성 | 상대적으로 높음 | 낮음 |
| 응답 지연 | 짧음 | Reranker 호출만큼 소폭 증가 |
| 답변 정확도 | 보통 | 향상 |
Reranking은 응답 속도를 약간 희생하는 대신 답변 품질을 끌어올리는 트레이드오프입니다. 그래서 모든 요청에 무조건 적용하기보다는, 답변 정확도가 특히 중요한 도메인(사내 규정, 법률, 의료 등)에서 우선적으로 검토하는 것이 좋습니다.
7. 정리
- 벡터 검색만으로는 유사도 점수가 비슷한 문서들 사이에서 세밀한 관련성 순위를 매기기 어렵습니다.
- Reranking은 벡터 검색으로 후보군을 넉넉히 뽑은 뒤, Cross-encoder 방식의 Reranker로 관련성을 재채점하는 2단계 검색 구조입니다.
- LangChain에서는
ContextualCompressionRetriever로 기존 Retriever에 Reranker를 손쉽게 추가할 수 있습니다.
지금까지는 "질문에 맞는 문서를 얼마나 잘 찾아오는가"에 집중했다면, 다음 글에서는 이렇게 만든 RAG 시스템이 실제로 잘 동작하고 있는지 판단하는 RAG 평가(Evaluation) 방법을 다뤄보겠습니다.
'RAG를 활용한 LLM 서비스 구축' 카테고리의 다른 글
| STEP 12 - 프롬프트 엔지니어링 (0) | 2026.07.09 |
|---|---|
| STEP 11 - 답변 품질을 측정하는 방법 (0) | 2026.07.09 |
| STEP 9 - LangChain으로 RAG 파이프라인 처음부터 구축하기 (0) | 2026.07.08 |
| STEP 8 - 문서 청킹 전략 (0) | 2026.07.07 |
| STEP 7 - pgvector 실습 (0) | 2026.07.07 |