LLM(대규모 언어 모델) 기반 엔터프라이즈 서비스에서 사내 지식 베이스나 규정 문서를 연동할 때 RAG(Retrieval-Augmented Generation, 검색 증강 생성) 파이프라인은 사실상 표준 아키텍처로 자리 잡았습니다. 그러나 프로덕션 환경에서 RAG를 운영해 보면, 모델 자체의 생성 능력보다 ‘검색 단계에서 얼마나 정확하고 연관성 높은 문맥(Context)을 추출하여 전달하는가’가 최종 답변 품질의 80% 이상을 결정한다는 점을 체감하게 됩니다.
초기 RAG 구축 시에는 텍스트 임베딩 모델(Dense Embedding)과 벡터 데이터베이스(Vector DB)를 결합한 단순 코사인 유사도 검색을 주로 사용합니다. 하지만 실무 문서에 빈번히 등장하는 제품 모델명(예: AX-920-V2), 특정 법률 조항(예: 제14조제2항), 전문 약어, 고유 식별자 등은 임베딩 모델이 의미론적 공간에서 적절히 군집화하지 못해 검색 결과에서 완전히 누락되는 어휘적 불일치(Lexical Mismatch) 문제가 발생합니다.
본 글에서는 단일 벡터 검색의 구조적 한계를 보완하기 위해 BM25 기반 어휘 검색(Sparse)과 벡터 임베딩 검색(Dense)을 결합하는 하이브리드 검색(Hybrid Search)의 설계 원리와, 서로 다른 점수 척도를 정규화하는 RRF(Reciprocal Rank Fusion) 알고리즘, 그리고 최종 후보 문서를 정밀 재평가하는 Cross-Encoder 리랭킹(Reranking) 2단계 파이프라인을 엔지니어링 관점에서 상세히 분석합니다.
1. 단일 벡터 검색의 한계와 하이브리드 검색의 필요성
밀집 벡터 검색(Dense Vector Retrieval)은 문장의 문맥과 의미론적 유사성(Semantic Similarity)을 포착하는 데 탁월합니다. 예컨대 “직원 연차 규정”을 검색했을 때 “휴가 일수 산정 기준”이라는 표현이 담긴 문서를 높은 유사도로 찾아낼 수 있습니다.
그러나 실제 엔터프라이즈 환경에서 사용자가 입력하는 질의는 문맥적 서술뿐 아니라 정확한 키워드 매칭을 요구하는 경우가 많습니다.
- 고유 식별자 및 코드 검색 취약성:
CVE-2026-3819,Error Code 5021,API-KEY-AUTH와 같은 고유 토큰은 학습 코퍼스에 존재하지 않거나 서브워드(Subword) 단위로 잘게 쪼개져 벡터 공간에서 엉뚱한 위치로 매핑됩니다. - 도메인 특화 약어 매칭 실패: 의료, 금융, 제조 등 특정 산업군의 독자 약어는 일반 도메인 임베딩 모델이 의미를 파악하지 못해 관련성 낮은 문서를 상위권에 배치합니다.
- 스코어 인플레이션(Score Inflation): 긴 문서 청크를 고정 길이 벡터(예: 1536차원)로 압축하는 과정에서 세부 정보가 희석되며, 질문과 직접적인 관련이 없는 일반론적 문맥이 높은 유사도 점수를 얻는 왜곡이 발생합니다.
이러한 한계를 극복하기 위해, 전통적인 역색인 기반의 BM25(Best Matching 25) 키워드 검색과 현대의 Dense 벡터 검색을 상호 보완적으로 병렬 실행하는 하이브리드 검색 아키텍처가 필수로 요구됩니다.
2. 이종 검색 결합의 핵심 난제와 RRF(Reciprocal Rank Fusion) 알고리즘
하이브리드 검색을 구축할 때 가장 먼저 마주하는 엔지니어링 과제는 “두 검색 엔진의 점수(Score)를 어떻게 합산할 것인가”입니다.
- BM25 스코어: 0부터 수십 점까지 상한선이 없는 비정규화된 양수 점수이며, 문서 길이에 따라 동적으로 스케일이 변합니다.
- Dense 코사인 유사도: 일반적으로 -1.0에서 1.0(또는 정규화 시 0.0 ~ 1.0) 사이의 닫힌 범위를 가집니다.
두 점수의 스케일과 분포가 전혀 다르기 때문에 단순 가중치 합산(Linear Weighted Combination)을 적용하면, 특정 질의 유형에 따라 한쪽 점수가 전체 순위를 완전히 왜곡하는 현상이 발생합니다. Min-Max 정규화 역시 검색 결과에 따라 최댓값/최솟값이 극단적으로 튀는 이상치(Outlier)에 취약합니다.
이를 수학적으로 깔끔하게 해결하는 기법이 바로 RRF(Reciprocal Rank Fusion, 상호 순위 결합)입니다. RRF는 점수 수치를 직접 비교하지 않고, 각 검색 엔진이 매긴 순위(Rank)의 역수를 계산하여 합산합니다.
RRF_Score(d) = Σ [ 1 / (k + r_i(d)) ]여기서
r_i(d)는 시스템 i에서의 문서 d의 순위(1부터 시작)이며, k는 상위 순위 쏠림을 완화하는 스무딩 상수(Smoothing Constant, 통상 k=60 권장)입니다.RRF 알고리즘의 강력한 장점은 세 가지입니다:
- 점수 척도 독립성: 점수의 절대값이 아닌 상대적 순위만 사용하므로 사전 점수 정규화(Normalization) 과정이 일절 필요하지 않습니다.
- 양쪽 일치 문서에 대한 강력한 부스팅: BM25와 벡터 검색 양쪽에서 모두 상위권(예: 1~3위)을 차지한 문서는 비선형적으로 높은 점수를 획득하여 확실한 정답 후보로 부상합니다.
- 한쪽 엔진의 단독 상위권 보호: 한쪽 엔진에서만 1위이고 다른 쪽에는 아예 나타나지 않은 고유명사 검색 결과도 일정 수준 이상의 기본 점수를 보장받아 최종 후보군에 안정적으로 포함됩니다.
3. 2단계 리랭킹(Two-stage Reranking) 파이프라인 아키텍처
하이브리드 검색과 RRF를 통해 1차로 추출된 상위 30~50건의 문서는 재현율(Recall) 측면에서는 우수하지만, LLM의 컨텍스트 윈도우에 그대로 주입하기에는 토큰 비용이 크고 노이즈가 섞여 있을 수 있습니다.
따라서 Bi-Encoder(1차 대량 후보군 선별)와 Cross-Encoder(2차 정밀 리랭킹)로 역할을 분리하는 2단계 파이프라인을 구축하는 것이 실무 표준입니다.
| 구분 | 1단계: Bi-Encoder (Dense/Sparse) | 2단계: Cross-Encoder (Reranker) |
|---|---|---|
| 역할 | 수십만~수백만 건의 전체 코퍼스에서 상위 30~50건 고속 필터링 (High Recall) | 선별된 30~50건 중 LLM에 전달할 최종 Top-5 정밀 순위화 (High Precision) |
| 연산 구조 | 질문과 문서를 각각 독립적으로 임베딩한 후 내적(Dot Product) 계산 | 질문과 문서를 하나의 시퀀스로 묶어 풀 어텐션(Full Attention) 수행 |
| 지연 시간 (Latency) | 수 밀리초(ms) 단위 (인덱스 검색) | 청크당 수 밀리초, 30건 기준 약 30~70ms 소요 |
| 문맥 포착 능력 | 단어 간 상호작용이 단일 벡터로 압축되어 세부 의미 소실 | 질문의 모든 단어와 문서의 모든 단어 간 교차 어텐션으로 완벽한 문맥 평가 |
4. 파이썬 기반 하이브리드 검색 및 RRF 파이프라인 구현
다음은 PostgreSQL(pgvector + Full-Text Search) 또는 독립 검색기를 연동할 때 활용할 수 있는 순수 파이썬 RRF 결합 및 Cross-Encoder 리랭킹 모듈의 핵심 구현체입니다.
from typing import List, Dict, Any
from collections import defaultdict
def reciprocal_rank_fusion(
ranked_lists: List[List[Dict[str, Any]]],
k: int = 60,
top_n: int = 30
) -> List[Dict[str, Any]]:
"""
여러 검색 엔진의 순위 리스트를 RRF(Reciprocal Rank Fusion) 알고리즘으로 결합합니다.
:param ranked_lists: 각 검색 엔진에서 반환한 문서 딕셔너리 리스트의 모음
:param k: RRF 스무딩 파라미터 (기본값: 60)
:param top_n: 1차로 선별할 최종 후보 문서 수
:return: RRF 점수 기준 내림차순 정렬된 문서 리스트
"""
rrf_scores: Dict[str, float] = defaultdict(float)
doc_lookup: Dict[str, Dict[str, Any]] = {}
for engine_results in ranked_lists:
for rank, doc in enumerate(engine_results, start=1):
doc_id = doc["id"]
# RRF 점수 누적: 1 / (k + rank)
rrf_scores[doc_id] += 1.0 / (k + rank)
if doc_id not in doc_lookup:
doc_lookup[doc_id] = doc
# 점수 기준 내림차순 정렬
sorted_doc_ids = sorted(
rrf_scores.keys(),
key=lambda doc_id: rrf_scores[doc_id],
reverse=True
)
fused_results = []
for doc_id in sorted_doc_ids[:top_n]:
item = doc_lookup[doc_id].copy()
item["rrf_score"] = round(rrf_scores[doc_id], 6)
fused_results.append(item)
return fused_results
이어지는 단계에서는 오픈소스 리랭커(예: BAAI/bge-reranker-v2-m3)를 사용하여 상위 30건의 문서를 질의(Query)와 직접 쌍(Pair)으로 평가하고 최종 상위 5건을 선별합니다.
from sentence_transformers import CrossEncoder
class RAGPipeline:
def __init__(self, reranker_model_name: str = "BAAI/bge-reranker-v2-m3"):
# CPU/GPU 환경에 맞게 Cross-Encoder 모델 초기화
self.reranker = CrossEncoder(reranker_model_name)
def retrieve_context(
self,
query: str,
sparse_search_fn,
dense_search_fn,
final_top_k: int = 5
) -> List[Dict[str, Any]]:
# 1. BM25 어휘 검색과 Dense 벡터 검색을 병렬 수행 (각각 30건 추출)
sparse_docs = sparse_search_fn(query, limit=30)
dense_docs = dense_search_fn(query, limit=30)
# 2. RRF 알고리즘을 통한 1차 후보군 결합 (상위 30건 선별)
candidate_docs = reciprocal_rank_fusion([sparse_docs, dense_docs], k=60, top_n=30)
if not candidate_docs:
return []
# 3. Cross-Encoder 리랭킹용 [Query, Passage] 쌍 구성
query_passage_pairs = [[query, doc["content"]] for doc in candidate_docs]
# 4. 정밀 관련도 스코어 계산
rerank_scores = self.reranker.predict(query_passage_pairs)
for doc, score in zip(candidate_docs, rerank_scores):
doc["rerank_score"] = float(score)
# 5. 리랭커 점수 기준 내림차순 정렬 후 최종 Top-K 반환
candidate_docs.sort(key=lambda x: x["rerank_score"], reverse=True)
return candidate_docs[:final_top_k]
5. 실무 아키텍처 설계 시 핵심 고려사항
- 지연 시간(Latency) 예산과 SLA 관리: Cross-Encoder는 신경망 풀 어텐션을 수행하므로 청크 수가 많아질수록 지연 시간이 선형적으로 증가합니다. 실시간 챗봇의 경우 리랭커 입력 후보군을 20~30건 이내로 제한하고, 모델 크기를 추론 인프라 스펙에 맞춰 타협해야 합니다.
- 청크 분할(Chunking) 단위의 일관성: BM25는 긴 텍스트에서 키워드 빈도 페널티를 받기 쉽고, 임베딩 모델은 토큰 제한을 초과하면 정보가 누락됩니다. 제목, 부제목 등 계층형 메타데이터가 보존된 500~800자 단위의 의미적 청킹(Semantic Chunking)을 적용하는 것이 이상적입니다.
- 도메인별 최소 점수 컷오프(Relevance Threshold): 리랭킹 점수가 임계치 이하인 문서는 질문과 무관한 노이즈일 가능성이 높으므로, LLM 컨텍스트 주입 대상에서 과감히 제외하여 환각 발생을 방어해야 합니다.
6. 결론
RAG 시스템의 성능 병목은 프롬프트나 파운데이션 모델의 크기보다, 검색 단계에서 양질의 문맥을 선별하지 못하는 데서 기인하는 경우가 대부분입니다. 단일 벡터 검색에 의존하는 구조에서 벗어나 BM25를 결합한 하이브리드 검색, RRF 기반 점수 통합, 그리고 Cross-Encoder를 통한 정밀 리랭킹으로 이어지는 2단계 파이프라인을 구축하는 것은, 고도화된 엔터프라이즈 AI 어시스턴트의 신뢰성을 담보하는 가장 확실한 엔지니어링 해법입니다.