V2 사무실 기억 검색 품질 (ADR 0024)
V2 사무실 기억 검색 (ADR 0017 머지 후) 의 품질을 측정·개선하는 4 단계 인프라. ADR 0024 가 SSoT, 본 문서는 코드 위치·운영 흐름 요약.
배경
ADR 0017 V2 머지로 tenantId 격리 임베딩 + findNearest + 메타 매칭 결합 랭킹 (cosine 0.7 + meta 0.3) 까지는 완성됐지만:
- 검색 품질이 측정되지 않고 있었음 (precision/NDCG/MRR 산출 인프라 부재)
- 임베딩 + 메타 매칭만으로는 표층 의미 한계 — recall@10 향상 여지 명백
- 클릭 텔레메트리는 있었지만 정합 지표 산출에 활용 안 됨
ADR 0024 가 4 PR 묶음으로 텔레메트리 → 측정 → rerank → multi-query expansion 순서 도입.
4 PR 구성 (모두 머지 완료 2026-04-26)
| PR | 산출 | 핵심 산출물 |
|---|---|---|
| PR-1 | 텔레메트리 스키마 확장 | reranked · rerankModel · queryExpanded · expandedQueriesCount 4 필드 + clicked 이벤트 dwellMs |
| PR-2 | precision@K · NDCG · MRR 측정 | implicit feedback (클릭) 기반 계산 인프라 + 대시보드 카드 |
| PR-3 | LLM rerank 모듈 | Gemini Flash, DI 기반 fail-soft, top-N 재정렬, searchRerank 메타 플래그 (기본 OFF) |
| PR-4 | Multi-query expansion | deterministic rule-based, 1 → N (최대 4) 쿼리, MAX 점수 + appearanceBoost, queryExpansion 메타 플래그 (기본 OFF) |
측정 정의
precision@K (binary relevance)
precision@K = (top-K 안 클릭 수) / min(K, cardCount)
NDCG@K (binary relevance)
DCG@K = Σ_{i=0..K-1} rel(i) / log2(i+2)
IDCG@K = 클릭 수 만큼 상위에 모은 이상적 DCG
NDCG@K = DCG@K / IDCG@K (IDCG=0 시 0)
MRR (단일 세션)
MRR = 1 / (firstClickRank + 1) [클릭 없음 → 0]
세션 매칭 규칙
(caseId, docKind, actorUid)키로 shown → click 5분 window (SESSION_WINDOW_MS = 300_000)- 새 shown 등장 시 이전 세션 종료
- 동일 rankIndex 중복 클릭 1회만
LLM Rerank 알고리즘 (PR-3)
input: query + ranked top-N + snippets (redactedText, ≤240 char)
output: reranked top-N + applied + modelUsed
1. 빈/단일 후보 → 즉시 fail-soft (applied=false)
2. LLM 호출 (Gemini Flash, JSON 점수 0~100)
3. LLM throw → fail-soft, 원래 ranking 유지
4. LLM 빈 점수 → fail-soft
5. 점수 내림차순 정렬, 동점은 원래 finalScore 내림차순 (안정 정렬)
fail-soft 가 핵심: rerank 가 어떤 이유로든 실패해도 baseline ranking 이 그대로 노출. P1 양보 불가 — V1 결과보다 V2+rerank 결과가 덜 적합하면 즉시 rollback 가능해야 함.
Multi-Query Expansion 알고리즘 (PR-4)
4 variants 생성 (dedupe 후 최대 4):
V1: [recoveryType] [docType] (+ 상사) (+ bucket) — 가장 정밀
V2: [recoveryType] [docType] (+ 상사) — bucket 제거 (broader)
V3: [recoveryType] [핵심 법령 키워드] — 도메인 적합성
V4: [recoveryType] [docType] — broadest fallback
각 variant: embed → findNearest(pool=15) → rankMemories → top-N
병합:
- 각 docId 점수 = variant 결과 중 MAX
- appearanceBoost: 등장 1회 추가당 +0.05 (cap 0.15)
- finalScore + bonus 내림차순 정렬
도메인 법령 키워드 매핑 (10 도메인):
debt→ 민법 제162조 소멸시효divorce→ 민법 제840조 이혼사유real-estate-eviction→ 부동산 인도청구 강제집행inheritance-share→ 민법 제1117조 유류분unjust-enrichment→ 민법 제741조 부당이득- … (전체 매핑은 코드 SSoT)
코드 위치
- 측정 계산:
apps/web/lib/search-quality/precision.ts·ndcg.ts·mrr.ts(순수 함수) - 세션 매칭:
apps/web/lib/search-quality/session-window.ts - LLM rerank:
apps/web/lib/search-rerank/(DI 기반,rerankerImpl.ts) - Multi-query 빌더:
apps/web/app/(workspace)/legacy/_lib/multi-query-builder.ts - 텔레메트리:
tenants/{tid}/relatedMemoriesEvents/*(스키마 확장은 PR-1) - 대시보드 카드:
apps/web/app/(workspace)/_components/DashboardSearchQualityCard.tsx
Production 활성화 순서
플래그 모두 기본 OFF. 점진 활성:
- PR-1, PR-2 머지 직후 → 클릭 텔레메트리 자동 기록 시작 → 1주 baseline 측정
features.ai.queryExpansion = trueflip → recall 변화 측정features.ai.searchRerank = trueflip + client-side reranker wiring (PR-3.5 follow-up) → precision 변화 측정- 대시보드 카드 baseline vs reranked Δpp · expanded vs baseline P@5 로 효과 가시화
한계 (측정 정의의 함정)
- 클릭 = 관련 가정의 노출 편향: 상단 클릭 우세 — precision/NDCG 가 항상 정직하지 않음. trend (전 주 대비 ↑·↓) 추적은 유효
- 변호사 직접 입력 시 신호 0: 카드 보지 않고 직접 작성 시 데이터 없음. 사용자 적은 tenant 에선 표본 부족
- 5분 window 는 임의값: 너무 짧으면 누락, 너무 길면 다른 세션과 혼동. 운영하면서
SESSION_WINDOW_MS조정 가능 - rerank top-N 안 재정렬: 풀 확장 (top-2N → 재랭크 → top-N) 은 후속 최적화. 현재는 메타 ranking 으로 결정된 top-N 안에서만 LLM 이 흔듦
- query expansion 보너스 cap 0.15: 4 variant 모두 등장한 docId 가 너무 우세하지 않도록 보호. 값은 실측으로 조정 가능
후속 과제
- PR-3.5: client-side rerank wiring (CLAUDE.md 규약 — Gemini 클라 전용)
- 풀 확장 rerank: top-2N → LLM rerank → top-N
- 운영 매뉴얼: precision/NDCG ↓ 시 대처 가이드 (
operations/v2-search-quality-runbook.md, 예정)
관련 문서
- ADR 0024 V2 검색 품질 Phase 2
- ADR 0017 V2 검색 파이프라인 완성
- Platform RAG (ADR 0021) — 공공 판례 층, 본 검색과 병렬 운영
- 사무소 학습 루프 (ADR 0018)