33차시. Modular RAG 최적화
5. 최적화 ③ — Re-ranking
출처: 원본 PDF 33차시_ModularRAG최적화.pdf 11~12쪽 · 쪽 그림 11쪽 12쪽
검색 품질을 올리면 재생성 횟수가 줄어듭니다. 근본적인 개선입니다.
5-1) 왜 재순위가 필요한가
🔍 임베딩 검색의 한계
임베딩은 질문과 문서를 각각 따로 벡터로 만듭니다.
그리고 거리를 잽니다.
빠르지만 정밀하지 않습니다.
"이 문서가 이 질문에 정말 답이 되는가"를
직접 비교한 것이 아니니까요.
Cross-Encoder는 다릅니다.
질문과 문서를 함께 넣고 관련도를 매깁니다.
훨씬 정확하지만 느립니다.
그래서 2단계로 씁니다.
1단계 — 임베딩으로 20개를 빠르게 추림
2단계 — Cross-Encoder로 상위 5개를 정밀 선별
from sentence_transformers import CrossEncoder
_reranker = CrossEncoder("BAAI/bge-reranker-v2-m3")
def retriever_node(state) -> dict:
query = state.get("query") or state["question"]
k = state.get("top_k", config.TOP_K)
# ① 임베딩으로 넉넉히 가져오기
pairs = _store.similarity_search_with_relevance_scores(
query, k=k * 4) # 5개 필요하면 20개 후보
cands = [d for d, _ in pairs]
if not cands:
return {"documents": [], "scores": [],
"retrieval_status": "empty",
"log": ["검색 empty"]}
# ② Cross-Encoder로 재순위
scores = _reranker.predict(
[(query, d.page_content) for d in cands])
def sort_key(pair):
return -pair[1]
ranked = sorted(zip(cands, scores), key=sort_key)
top = ranked[:k]
# 기준(RERANK_MIN)을 넘는 것만 골라 담기
docs = []
for d, s in top:
if s >= config.RERANK_MIN:
docs.append(d)
best = float(ranked[0][1])
status = ("ok" if docs else
"weak" if best >= config.RERANK_MIN - 0.1 else "empty")
return {"documents": docs,
"scores": [round(float(s), 3) for _, s in top],
"retrieval_status": status,
"retrieval_ok": bool(docs),
"log": [f"검색+재순위 {status} 최고={best:.2f} "
f"통과={len(docs)}/{len(cands)}"]}💡 재순위의 실제 효과
· 정답 문서가 1~3위에 올 확률 상승
· 노이즈 조각이 줄어듦
· → 답변 품질 상승 → 재생성 횟수 감소
비용은 늘지만(모델 로딩 + 추론)
로컬에서 돌아가므로 API 비용은 0입니다.
재생성 1회를 아끼면 재순위 비용을 넘습니다.