RAG2026-5일33차시 · Modular RAG 최적화8 / 19← 이전목차다음 →MetaContents

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개를 정밀 선별

Python
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회를 아끼면 재순위 비용을 넘습니다.

← 이전 절33차시 · 4. 최적화 ② — 검증 호출 줄이기다음 절 →33차시 · 6. 최적화 ④ — 캐싱