feat(search): Phase 0.5 RRF fusion + 강한 신호 boost

기존 weighted-sum merge를 Reciprocal Rank Fusion으로 교체.
정확 키워드 매치에서 RRF가 평탄화되는 문제는 boost로 보완.

신규 모듈 app/services/search_fusion.py:
- FusionStrategy ABC
- LegacyWeightedSum  : 기존 _merge_results 동작 (A/B 비교용)
- RRFOnly            : 순수 RRF, k=60
- RRFWithBoost       : RRF + title/tags/법령조문/high-text-score boost (default)
- normalize_display_scores: SearchResult.score를 [0..1] 랭크 기반 정규화
  (프론트엔드가 score*100을 % 표시하므로 RRF 원본 점수 노출 시 표시 깨짐)

search.py:
- ?fusion=legacy|rrf|rrf_boost 파라미터 (default rrf_boost)
- _merge_results 제거 (LegacyWeightedSum에 흡수)
- pre-fusion confidence: hybrid는 raw text/vector 신호로 계산
  (fused score는 fusion 전략마다 스케일이 달라 일관 비교 불가)
- timing에 fusion_ms 추가
- debug notes에 fusion 전략 표시

telemetry:
- compute_confidence_hybrid(text_results, vector_results) 헬퍼
- record_search_event에 confidence override 파라미터

run_eval.py:
- --fusion CLI 옵션, call_search 쿼리 파라미터에 전달

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-04-07 08:58:33 +09:00
parent 1af94d1004
commit 161ff18a31
6 changed files with 778 additions and 45 deletions

View File

@@ -149,6 +149,22 @@ def _cosine_to_confidence(cosine: float) -> float:
return 0.10
def compute_confidence_hybrid(
text_results: list[Any],
vector_results: list[Any],
) -> float:
"""hybrid 모드 confidence — fusion 적용 *전*의 raw text/vector 결과로 계산.
Phase 0.5에서 RRF 도입 후 fused score는 절대값 의미가 사라지므로,
원본 retrieval 신호의 더 강한 쪽을 confidence로 채택.
"""
text_conf = compute_confidence(text_results, "fts") if text_results else 0.0
vector_conf = (
compute_confidence(vector_results, "vector") if vector_results else 0.0
)
return max(text_conf, vector_conf)
# ─── 로깅 진입점 ─────────────────────────────────────────
@@ -200,16 +216,22 @@ async def record_search_event(
user_id: int | None,
results: list[Any],
mode: str,
confidence: float | None = None,
) -> None:
"""검색 응답 직후 호출. 실패 트리거에 해당하면 로그 INSERT.
background task에서 await로 호출. request 세션과 분리.
user_id가 None이면 reformulation 추적 + 로깅 모두 스킵 (시스템 호출 등).
confidence 파라미터:
- None이면 results 기준으로 자체 계산 (legacy 호출용).
- 명시적으로 전달되면 그 값 사용 (Phase 0.5+: fusion 적용 전 raw 신호 기준).
"""
if user_id is None:
return
confidence = compute_confidence(results, mode)
if confidence is None:
confidence = compute_confidence(results, mode)
result_count = len(results)
base_ctx = _build_context(results, mode, extra={"confidence": confidence})