RRF 하이브리드 병합 — BM25 + 임베딩

무엇을

BM25(nori) 결과와 임베딩 결과를 RRF(Reciprocal Rank Fusion)로 병합해 HybridRetriever 를 완성한다.

어디서 병합하는가 — 이미 정해져 있다

우리 코드에서 한다(_project/decisions/021). ES 의 rrf 리트리버를 쓰지 않는다. 3.1절 요구사항표의 「nori+dense_vector+RRF」는 재료를 가리키는 것이지 ES 기능을 쓰라는 뜻이 아니다.

완료 조건

주의

합쳤으니 좋아졌을 것이라고 적지 않는다. 표본 96건에서 Recall@5 한 건은 약 0.01 이다 — 0.833 → 0.843 은 «한 건 더 맞았다» 이지 개선이 아니다. 차이를 적을 때 건수로도 함께 적는다.


2026-09-15 — 만들었고 쟀다. 채택하지 않는다 — dense 단독보다 낮다

측정일 2026-09-15 · 커밋 38f2fc3-dirty · 골든셋 v1-150 B 항목 n96 · 지식베이스 98조항 · 로컬 Darwin arm64
.venv/bin/python scripts/index_knowledge_base.py --to-es --recreate     # 본문 + KoE5 임베딩
.venv/bin/python scripts/compare_retrievers.py [--device mps]
결과 파일 data/processed/retrieval-compare/2026-09-15*.json (gitignore)

변형                          Recall@5   hits    MRR   top1 │ p95 CPU   p95 MPS
bm25 (run_id=2 와 같은 값)       0.833   80/96  0.659    51  │     4ms      7ms
dense (KoE5 kNN)                0.979   94/96  0.885    78  │    59ms     50ms
hybrid RRF k=60 cand=20         0.927   89/96  0.828    73  │    58ms     39ms
  k=10 / 30 / 100               0.927   89/96  0.835 / 0.828 / 0.828
rerank(bm25) cand=5             0.833   80/96  0.795    73  │   494ms    237ms
rerank(bm25) cand=20            0.906   87/96  0.860    79  │  1760ms   1068ms
rerank(dense) cand=5            0.979   94/96  0.919    84  │   521ms    289ms
rerank(dense) cand=20           0.979   94/96  0.921    84  │  1789ms   1317ms
rerank(hybrid) cand=5           0.927   89/96  0.886    82  │   529ms    373ms
rerank(hybrid) cand=20          0.979   94/96  0.916    83  │  1844ms   1197ms

결정

기본 구성은 dense + 리랭킹이다(_project/decisions/206). HybridRetriever 는 지우지 않는다 — 지식베이스가 커지거나 번호·고유명사 질의가 늘면 BM25 가 기여할 수 있고, 같은 스크립트로 다시 잴 수 있어야 한다. 요구사항표의 「nori+dense_vector+RRF」는 재료를 적은 것이었고(티켓 본문 그대로), 재료를 다 써 본 결과가 이것이다.