33차시. Modular RAG 최적화
6-2. 최적화 ⑤ — 하이브리드 검색
출처: 원본 PDF 33차시_ModularRAG최적화.pdf 15~16쪽 · 쪽 그림 15쪽 16쪽
앞의 네 가지는 속도와 비용을 다뤘습니다. 마지막은 정확도 최적화입니다.
6-2-1) 임베딩이 약한 지점
🔍 9차시에서 배운 한계
의미 검색은 뜻이 비슷하면 잘 찾습니다.
그런데 아래 두 가지에는 약합니다.
① 고유명사·코드·숫자
"제품코드 A-7742" → 의미가 없어 좌표가 어색합니다
"제12조 3항" → 조항 번호를 정확히 못 짚습니다
② 부정문
"환불이 가능하다" ↔ "환불이 불가능하다"
→ 유사도가 0.9 이상 나옵니다 (뜻은 정반대인데!)
이럴 때는 키워드 검색이 오히려 정확합니다.
6-2-2) 두 검색을 합치기
질문: "제품코드 A-7742 환불 가능한가요?"
■ 의미 검색 (FAISS)
→ "환불 규정" 관련 조각들 (A-7742는 못 짚음)
■ 키워드 검색 (BM25)
→ "A-7742"가 들어간 조각 정확히 매칭
■ 하이브리드 = 둘의 결과를 합쳐 재정렬
→ 코드도 맞고 주제도 맞는 조각이 상위로📝 nodes/retriever.py에 추가
from langchain_community.retrievers import BM25Retriever
from langchain.retrievers import EnsembleRetriever
from indexer import get_chunks # 조각 목록 반환 함수
# ── 키워드 검색기 (인덱싱 불필요, 메모리에서 동작) ──
_chunks = get_chunks()
_bm25 = BM25Retriever.from_documents(_chunks)
_bm25.k = config.TOP_K
# ── 의미 + 키워드 앙상블 ──
_hybrid = EnsembleRetriever(
retrievers=[_store.as_retriever(search_kwargs={"k": config.TOP_K}),
_bm25],
weights=[0.7, 0.3], # 의미 70% · 키워드 30%
)
def _search_hybrid(query, k):
docs = _hybrid.invoke(query)[:k]
# 점수는 의미 검색 기준으로 다시 매긴다
pairs = _store.similarity_search_with_relevance_scores(query, k=k*2)
score_map = {d.page_content: s for d, s in pairs}
return [(d, score_map.get(d.page_content, 0.0)) for d in docs]💡 weights를 어떻게 정할까
[0.7, 0.3] — 일반 문서 (권장 출발점) [0.5, 0.5] — 제품코드·조항이 많은 문서 [0.8, 0.2] — 서술형 설명이 대부분인 문서 14차시 방식으로 직접 측정해 정하세요.
질문 20개로 세 조합을 비교하면 30분이면 끝납니다.