Cross-Encoder 리랭킹의 원리, Cohere Rerank API, 오픈소스 리랭커 비교, 그리고 프로덕션 환경에서의 효과적인 리랭킹 전략을 다룹니다.
리랭킹(Reranking)은 1차 검색(BM25, 벡터 검색, 하이브리드 검색)으로 가져온 후보 문서의 순위를 재조정하는 2단계 검색 기법입니다. 1차 검색이 넓은 범위에서 관련 문서를 효율적으로 추려내는 역할을 한다면, 리랭커(Reranker)는 추려진 소수의 후보에 대해 더 정밀한 관련성 판단을 수행합니다.
프로덕션 RAG 검색 패턴:
질문 --> 1차 검색 (top-20) --> 리랭킹 (top-5) --> LLM 생성
1차 검색: 빠르지만 정밀도 낮음 (Bi-Encoder)
리랭킹: 느리지만 정밀도 높음 (Cross-Encoder)이 패턴은 "넓게 가져오고, 정밀하게 선별한다"는 원칙을 따릅니다. 1차 검색에서 상위 20개를 가져온 뒤, 리랭커로 상위 5개를 선별하여 LLM에 전달하는 것이 2026년 기준 프로덕션 RAG의 표준 패턴입니다.
리랭킹의 핵심을 이해하려면, 두 가지 인코딩 방식의 차이를 알아야 합니다.
Bi-Encoder는 쿼리와 문서를 독립적으로 임베딩합니다. 문서 벡터는 미리 계산해두고, 검색 시에는 쿼리 벡터와의 유사도만 계산하면 됩니다. 수백만 문서에 대해서도 밀리초 단위 검색이 가능하지만, 쿼리와 문서 사이의 세밀한 상호작용을 포착하지 못합니다.
Bi-Encoder:
쿼리: "재택근무 신청" --> [0.2, 0.8, ...] --+
|--> 코사인 유사도
문서: "원격 근무 규정" --> [0.3, 0.7, ...] --+
쿼리와 문서를 독립적으로 인코딩 --> 빠르지만 상호작용 없음Cross-Encoder는 쿼리와 문서를 하나의 입력으로 결합하여 함께 처리합니다. 두 텍스트 사이의 토큰 수준 상호작용을 직접 모델링하므로, 훨씬 정밀한 관련성 판단이 가능합니다. 그러나 모든 쿼리-문서 쌍에 대해 추론을 수행해야 하므로, 대규모 코퍼스 전체를 대상으로 사용하기에는 비용이 너무 높습니다.
Cross-Encoder:
입력: "[CLS] 재택근무 신청 [SEP] 원격 근무 규정 [SEP]"
--> Transformer 전체 어텐션 --> 관련성 점수: 0.92
쿼리와 문서를 함께 인코딩 --> 느리지만 정밀이것이 바로 2단계 검색 패턴이 필요한 이유입니다. Bi-Encoder로 수백만 개에서 20개를 빠르게 추리고, Cross-Encoder로 20개에서 5개를 정밀하게 선별합니다.
Cohere는 가장 널리 사용되는 상용 리랭킹 API를 제공합니다. 간단한 API 호출만으로 높은 품질의 리랭킹을 적용할 수 있습니다.
import cohere
co = cohere.Client(api_key="your-api-key")
query = "재택근무는 주 몇 회까지 가능한가요?"
documents = [
"재택근무는 팀장 승인 후 주 2회까지 신청할 수 있습니다.",
"출장 시 교통비는 실비 정산이 원칙입니다.",
"원격 근무 시 VPN 접속이 필수이며, 보안 규정을 준수해야 합니다.",
"연차는 입사일 기준으로 매년 15일이 부여됩니다.",
"재택근무 신청은 전주 금요일까지 팀장에게 제출해야 합니다.",
]
response = co.rerank(
model="rerank-v3.5",
query=query,
documents=documents,
top_n=3,
return_documents=True
)
for result in response.results:
print(f"순위: {result.index + 1}")
print(f"점수: {result.relevance_score:.4f}")
print(f"내용: {result.document.text[:100]}")
print("---")
# 출력:
# 순위: 1, 점수: 0.9821
# 내용: 재택근무는 팀장 승인 후 주 2회까지 신청할 수 있습니다.
#
# 순위: 2, 점수: 0.7234
# 내용: 재택근무 신청은 전주 금요일까지 팀장에게 제출해야 합니다.
#
# 순위: 3, 점수: 0.4102
# 내용: 원격 근무 시 VPN 접속이 필수이며, 보안 규정을 준수해야 합니다.Cohere Rerank API는 다국어를 기본 지원합니다. 한국어 질문과 한국어 문서 조합에서도 별도 설정 없이 잘 동작합니다. 리랭킹 비용은 검색당 수 센트 수준으로, 검색 품질 개선 대비 합리적입니다.
자체 인프라에서 운영하거나 비용을 통제해야 하는 경우, 오픈소스 Cross-Encoder 모델을 사용할 수 있습니다.
다국어 지원 리랭커로, 한국어를 포함한 100개 이상의 언어에서 좋은 성능을 보입니다.
from sentence_transformers import CrossEncoder
# 모델 로딩 (첫 실행 시 다운로드)
reranker = CrossEncoder("BAAI/bge-reranker-v2-m3", max_length=512)
query = "재택근무는 주 몇 회까지 가능한가요?"
documents = [
"재택근무는 팀장 승인 후 주 2회까지 신청할 수 있습니다.",
"출장 시 교통비는 실비 정산이 원칙입니다.",
"원격 근무 시 VPN 접속이 필수입니다.",
"연차는 입사일 기준으로 매년 15일이 부여됩니다.",
]
# 쿼리-문서 쌍으로 점수 계산
pairs = [(query, doc) for doc in documents]
scores = reranker.predict(pairs)
# 점수 기준 정렬
ranked = sorted(
zip(documents, scores),
key=lambda x: x[1],
reverse=True
)
for doc, score in ranked:
print(f"점수: {score:.4f} | {doc[:60]}")| 모델 | 파라미터 | 다국어 | 최대 토큰 | 성능 |
|---|---|---|---|---|
| BAAI/bge-reranker-v2-m3 | 568M | 지원 | 512 | 우수 |
| BAAI/bge-reranker-v2-gemma | 2B | 지원 | 512 | 최상 |
| cross-encoder/ms-marco-MiniLM-L-12-v2 | 33M | 영어만 | 512 | 양호 |
| jinaai/jina-reranker-v2-base-multilingual | 278M | 지원 | 1024 | 우수 |
리랭커 모델의 크기가 클수록 성능은 좋지만, 추론 시간도 증가합니다. bge-reranker-v2-gemma(2B)는 CPU에서 20개 문서를 리랭킹하는 데 수 초가 걸릴 수 있습니다. GPU 가용 여부와 지연 시간 요구사항을 고려하여 모델을 선택하세요.
FlashRank는 별도의 GPU 없이 CPU에서도 빠르게 동작하는 경량 리랭킹 라이브러리입니다. 프로토타이핑이나 리소스가 제한된 환경에서 유용합니다.
from flashrank import Ranker, RerankRequest
ranker = Ranker(model_name="ms-marco-MiniLM-L-12-v2")
request = RerankRequest(
query="재택근무 신청 방법",
passages=[
{"text": "재택근무는 팀장 승인 후 주 2회까지 신청할 수 있습니다."},
{"text": "출장 시 교통비는 실비 정산이 원칙입니다."},
{"text": "재택근무 신청은 전주 금요일까지 팀장에게 제출해야 합니다."},
]
)
results = ranker.rerank(request)
for result in results:
print(f"점수: {result['score']:.4f} | {result['text'][:60]}")이전 장에서 구축한 하이브리드 검색 파이프라인에 리랭킹을 추가합니다.
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
import cohere
class RAGWithReranking:
"""리랭킹이 포함된 RAG 파이프라인"""
def __init__(self, vectorstore, cohere_api_key):
self.vectorstore = vectorstore
self.co = cohere.Client(api_key=cohere_api_key)
self.llm = ChatOpenAI(model="gpt-4o", temperature=0)
self.prompt = ChatPromptTemplate.from_messages([
("system", """주어진 컨텍스트만을 사용하여 질문에 답변하세요.
컨텍스트에 없는 내용은 답변하지 마세요."""),
("human", """컨텍스트:
{context}
질문: {question}""")
])
def query(self, question, initial_k=20, final_k=5):
"""리랭킹 포함 RAG 질의"""
# 1단계: 넓은 범위로 1차 검색 (top-20)
initial_results = self.vectorstore.similarity_search(
question, k=initial_k
)
if not initial_results:
return "관련 문서를 찾을 수 없습니다."
# 2단계: 리랭킹으로 정밀 선별 (top-5)
documents = [doc.page_content for doc in initial_results]
rerank_response = self.co.rerank(
model="rerank-v3.5",
query=question,
documents=documents,
top_n=final_k,
return_documents=True
)
# 3단계: 리랭킹 결과로 컨텍스트 구성
reranked_docs = []
for result in rerank_response.results:
original_doc = initial_results[result.index]
source = original_doc.metadata.get("source", "알 수 없음")
reranked_docs.append(
f"[출처: {source}] (관련도: {result.relevance_score:.2f})\n"
+ result.document.text
)
context = "\n\n---\n\n".join(reranked_docs)
# 4단계: LLM 생성
chain = self.prompt | self.llm | StrOutputParser()
answer = chain.invoke({
"context": context,
"question": question
})
return answer
# 사용
rag = RAGWithReranking(vectorstore, cohere_api_key="your-key")
answer = rag.query("재택근무는 주 몇 회까지 가능한가요?")
print(answer)리랭커에 전달할 후보 수(initial_k)를 적절히 설정하는 것이 중요합니다. 너무 적으면 관련 문서가 후보에 포함되지 않을 수 있고, 너무 많으면 리랭킹 비용과 시간이 증가합니다.
# initial_k에 따른 성능 변화 (예시)
# initial_k=10: Recall@5=0.72, 리랭킹 시간=0.3초
# initial_k=20: Recall@5=0.85, 리랭킹 시간=0.6초
# initial_k=50: Recall@5=0.88, 리랭킹 시간=1.5초
# initial_k=100: Recall@5=0.89, 리랭킹 시간=3.0초
# 일반적으로 initial_k=20~30이 비용 대비 효과가 좋음리랭킹 점수가 일정 기준 이하인 문서를 필터링하여, LLM에 전달되는 노이즈를 줄입니다.
def rerank_with_threshold(co, query, documents, threshold=0.3, top_n=5):
"""관련도 임계값을 적용한 리랭킹"""
response = co.rerank(
model="rerank-v3.5",
query=query,
documents=documents,
top_n=top_n,
return_documents=True
)
# 임계값 이상인 결과만 반환
filtered = [
result for result in response.results
if result.relevance_score >= threshold
]
if not filtered:
# 임계값을 충족하는 결과가 없으면 최소 1개는 반환
return [response.results[0]] if response.results else []
return filtered여러 질문에 대해 동시에 리랭킹을 수행해야 하는 경우, 배치 처리로 효율을 높입니다.
from concurrent.futures import ThreadPoolExecutor
def batch_rerank(co, queries_and_docs, max_workers=5):
"""여러 쿼리에 대한 병렬 리랭킹"""
def rerank_single(item):
query, docs = item
response = co.rerank(
model="rerank-v3.5",
query=query,
documents=docs,
top_n=5
)
return response.results
with ThreadPoolExecutor(max_workers=max_workers) as executor:
results = list(executor.map(rerank_single, queries_and_docs))
return resultsCross-Encoder 대신 LLM 자체를 리랭커로 활용하는 방식도 있습니다. LLM이 각 문서의 관련성을 직접 판단합니다.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
def llm_rerank(query, documents, top_n=5):
"""LLM 기반 리랭킹"""
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
prompt = ChatPromptTemplate.from_messages([
("system", """다음 질문에 대한 각 문서의 관련성을 0~10 점수로 평가하세요.
JSON 배열 형식으로만 응답하세요. 예시: [8, 3, 7, 1, 5]"""),
("human", """질문: {query}
문서 목록:
{documents}""")
])
doc_text = "\n\n".join([
f"[문서 {i+1}]\n{doc}" for i, doc in enumerate(documents)
])
response = (prompt | llm).invoke({
"query": query,
"documents": doc_text
})
import json
scores = json.loads(response.content)
ranked = sorted(
zip(documents, scores),
key=lambda x: x[1],
reverse=True
)
return [doc for doc, score in ranked[:top_n]]LLM 기반 리랭킹은 Cross-Encoder보다 비용이 높고 느리지만, 복잡한 관련성 판단이 필요한 특수 도메인에서 효과적일 수 있습니다. 일반적인 RAG 파이프라인에서는 Cross-Encoder(Cohere Rerank, bge-reranker)가 비용 대비 성능이 더 우수합니다.
Databricks의 벤치마크에 따르면, 50개 문서를 Cross-Encoder로 리랭킹하는 데 약 1.5초가 소요됩니다. 이 추가 지연 시간 대비 검색 정밀도 향상은 대부분의 RAG 시스템에서 충분히 정당화됩니다.
리랭킹 없음:
검색 (top-5) --> LLM 생성
총 시간: ~1.5초
Precision@5: 0.60
리랭킹 포함:
검색 (top-20) --> 리랭킹 (top-5) --> LLM 생성
총 시간: ~2.5초
Precision@5: 0.82
--> 정밀도 37% 향상, 지연 시간 1초 증가하이브리드 검색과 리랭킹을 함께 사용하는 기업들은 25%의 토큰 사용량 감소와 비용 절감을 보고하고 있습니다. 더 관련성 높은 컨텍스트만 LLM에 전달하므로, 불필요한 정보 처리가 줄어들기 때문입니다.
리랭킹은 RAG 검색 파이프라인의 정밀도를 크게 향상시키는 핵심 기법입니다. "넓게 가져오고(top-20), 정밀하게 선별한다(top-5)"는 2단계 검색 패턴이 프로덕션 RAG의 표준입니다. Cohere Rerank는 가장 간편한 상용 솔루션이며, BAAI/bge-reranker-v2-m3는 다국어 지원이 우수한 오픈소스 대안입니다. 리랭킹 도입 시 1초 내외의 지연 시간이 추가되지만, 검색 정밀도와 토큰 비용 절감 효과가 이를 충분히 상쇄합니다.
다음 장에서는 구축한 RAG 시스템의 품질을 객관적으로 측정하고 개선하기 위한 평가 프레임워크와 핵심 메트릭에 대해 다룹니다.
이 글이 도움이 되셨나요?
RAGAS, 충실도, 컨텍스트 정밀도 등 RAG 시스템의 품질을 객관적으로 측정하는 평가 프레임워크와 핵심 메트릭을 다룹니다.
키워드 기반 BM25와 벡터 기반 시맨틱 검색을 결합한 하이브리드 검색의 원리, 구현 방법, 그리고 Reciprocal Rank Fusion 전략을 다룹니다.
에이전트가 검색 전략을 스스로 판단하고 실패를 자동 수정하는 Agentic RAG, CRAG, Self-RAG 등 고급 RAG 패턴을 심층 분석합니다.