Files
hyungi_document_server/tests/search_eval
hyungi 3e6866b4ae feat(search): Phase 2Q Diagnose Phase 1B — scaffold + dispatcher
phase-2q-query-rewrite-diagnose.md v6 plan Phase 1 의 fixture 외 잔여.
Phase 1A 446ba82 위 dispatcher + cache + LLM call + API param + eval flag + 21 unit test.
retrieval 합성 (search_with_rewrite) 은 Phase 2 별 commit.

신규:
- app/services/search/query_rewriter.py — LLM_BACKEND_MAP + _resolve + cache + rewrite()
  · slug-based allowlist (no silent fallback), httpx 직접, Priority.FOREGROUND semaphore
  · sampling 박제 (gemma response_format json_object / qwen prompt rule only — Phase 0 inspect 9)
  · manual TTL cache (query_analyzer 패턴 1:1, sha256[:32] NFKC key, LLM_REWRITE_TIMEOUT_MS=15000)
- tests/test_query_rewriter.py — 21 test PASS (resolve / cache key / parser / cache TTL / constants)

수정:
- app/api/search.py — ?rewrite_backend= query param + 400 unknown / 503 unavailable.
  scaffold = call but discard variants (retrieval path 영향 0). Phase 2 에서 합성.
- tests/search_eval/run_eval.py — --rewrite-backend flag + 4 hot spot wire-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 22:25:03 +00:00
..

Document Server 검색 평가셋 (search_eval)

Phase 1 산출물 — graded relevance baseline. peppy-hugging-nest.md Phase 1. Phase 2 모델 점검 (embedding / reranker / OCR-marker pipeline / STT) 의 객관 측정 도구.

개요

tests/search_eval/queries.yaml 은 Document Server 의 검색 품질을 정량 측정하기 위한 평가셋이다. 두 버전 존재:

  • v0.1 (2026-04-07) — binary relevance (relevant_ids set + top3_ids 보조). 23 case, 9 카테고리. corpus 753 documents 기준.
  • v0.2 (2026-05-23) — graded relevance (0~3 등급) + 사용자 권장 7 카테고리 + language / ocr_derived metadata. v0.1 호환 유지 (legacy_category + relevant_ids + top3_ids 컬럼 보존).

본 PR (PR-Eval-V0_2-Schema-Harness) = v0.1 23 case 의 v0.2 schema swap + run_eval graded harness 만. 신규 28 case (50+ 목표) + baseline 박제 + 약점 분석은 후속 PR (PR-Eval-V0_2-Baseline-Analysis).

Graded relevance 등급 (4단계)

Grade 의미 기준
3 highly relevant (top-3 강제) direct match — 본법/본 chapter/본 article. 검색 결과 top-3 에 반드시 포함돼야 함.
2 relevant (정답 후보) 보조 정답 — 시행령/규칙/관련 chapter. top-10 안 = OK.
1 marginal 약한 관련 — 같은 법령군이지만 다른 chapter, 같은 주제지만 다른 문서 유형. top-10 안 = 가점, 밖 = 무감점.
0 irrelevant 무관 — 검색 결과 잡히면 감점 후보 (현재는 표시만, 감점 미구현).

failure_expected 케이스는 graded_relevance 비움 ({}). 검색 결과 0건 = correct.

Graded score 계산식 (run_eval.py)

# graded NDCG@k — gain = 2^grade - 1
dcg = sum((2 ** grade - 1) / log2(rank + 1) for rank, doc_id ...)
idcg = sum((2 ** g - 1) / log2(rank + 1) for rank, g in sorted(grades, desc)[:k] ...)
ndcg = dcg / idcg

# graded recall@k — grade >= threshold 만 정답
recall_t2 = |{returned[:k]}  {grade>=2}| / |{grade>=2}|
recall_t3 = |{returned[:k]}  {grade>=3}| / |{grade>=3}|

카테고리 (7개)

Category 의미 측정 목적
standards 법령/규칙/기준 (산업안전보건법, ASME, KGS, NIST 등) exact_keyword + structured doc lookup
korean_only 한국어 자연어 query → 한국어 doc base semantic_search 품질
english_only 영어 자연어 query → 영어 doc base 영어 자료 검색
mixed 한국어+영어 혼합 OR ko query → en doc OR vice versa crosslingual 능력 (현재 0.53 미달 root cause 검증)
exam 가스기사 study 도메인 (study_questions 기반) 시험 문제 → 학습 자료 매칭
ocr_derived marker/OCR pipeline 통한 scanned PDF chunk 검색 Phase 2C 입력 — OCR 추출 품질이 검색에 미치는 영향
failure_expected 결과 0건 expected precision 측정 (no-result correctness)

legacy_category 컬럼으로 v0.1 9 카테고리 호환 보존 (분석 시 옛 분포 재현 가능).

v0.2 schema 컬럼

- id: kw_001
  query: "산업안전보건법 제6장"
  category: standards               # v0.2 7 카테고리 중 하나
  legacy_category: exact_keyword    # v0.1 카테고리 보존
  intent: fact_lookup               # semantic_search / fact_lookup / filter_browse
  domain_hint: document             # document / news / mixed
  language: ko                      # ko / en / mixed
  ocr_derived: false                # marker/OCR chunk 검색 케이스 여부
  failure_expected: false           # 결과 0건 expected
  graded_relevance:                 # {doc_id: grade} (0~3)
    3856: 3
    3868: 2
    3879: 2
  relevant_ids: [3856, 3868, 3879]  # v0.1 호환 (--eval-version v0.1 용)
  top3_ids: [3856]                  # v0.1 호환
  notes: |
    Act(3856) = grade 3 (본법, top-3 강제).
    Decree(3868)/Rule(3879) = grade 2 (보조 정답, 본법 우선).

신규 case 작성 가이드

  1. id 명명: 카테고리 prefix + 3-digit (예: std_006, ko_006, en_001, mix_006, exam_001, ocr_001, fail_004)
  2. graded_relevance 채점 사유 notes 필수 — 왜 그 등급 부여했는지 1~3줄 사람용 audit trail
  3. failure_expected = true 인 case 는 graded_relevance: {} (빈 dict)
  4. language 결정:
    • 순수 한국어 query + 한국어 doc → ko
    • 순수 영어 query + 영어 doc → en
    • 혼합 (한국어+영어 keyword OR ko query → en doc) → mixed
    • 다른 언어 (불어 등) 도 일단 mixed (PR-2 에서 enum 확장 검토)
  5. ocr_derived 결정: 정답 doc 중 documents.source_type='scanned' 또는 marker 처리 chunk 가 1개 이상이면 true
  6. 정답 doc_id 식별: GPU DB 의 documents 테이블에서 title ILIKE '%...%' 또는 metadata grep 으로 식별. graded 등급은 사람 판단.

Self-check (PR review)

  • notes 에 채점 사유 명시?
  • graded grade 분포 합리적 (모든 정답이 grade 3 = 의심)?
  • failure_expected case 의 graded_relevance 가 {} 인가?
  • legacy_category 보존 (v0.1 호환)?
  • language / ocr_derived 일관성?

v0.1 호환성

--eval-version v0.1 으로 옛 점수 (Recall@10 / MRR@10 / NDCG@10 / Top-3 hit-rate) 재현 가능. swap 후 옛 baseline 대비 ±0.001 이내여야 함.

load_queries() 의 v0.1 fallback:

  • yaml 에 graded_relevance 없고 relevant_ids 만 있으면, top3_ids 는 grade 3 / 나머지 relevant_ids 는 grade 2 로 자동 매핑.
  • 이 fallback 으로 v0.1 yaml 도 v0.2 mode 에서 graded score 산출 가능.

CLI 사용 예

export DOCSRV_TOKEN="eyJ..."

# 단일 평가, default = 두 mode 다 출력
.venv/bin/python tests/search_eval/run_eval.py \
    --base-url https://docs.hyungi.net \
    --output reports/baseline_v0_2_2026-05-23.csv

# v0.2 graded only
.venv/bin/python tests/search_eval/run_eval.py \
    --base-url https://docs.hyungi.net \
    --eval-version v0.2 \
    --output reports/v0_2_only.csv

# v0.1 mode (옛 점수 회귀 확인)
.venv/bin/python tests/search_eval/run_eval.py \
    --base-url https://docs.hyungi.net \
    --eval-version v0.1

# A/B 비교 (baseline vs candidate)
.venv/bin/python tests/search_eval/run_eval.py \
    --baseline-url https://docs.hyungi.net \
    --candidate-url http://localhost:8000 \
    --output reports/phase2a_vs_baseline.csv

Output (v0.2 mode 예)

=== single (n=23, scored=20, graded=20) ===
  -- v0.2 graded --
    NDCG@10 (graded)        : 0.812
    Recall@10 (grade>=2)    : 0.756
    Recall@10 (grade>=3)    : 0.900
  Latency p50: 240 ms
  Latency p95: 412 ms
  Failure-case precision: 3/3 (1.00) — empty result expected
  by category:
    failure_expected      n= 3  recall=0.00  ndcg=0.00  gndcg=0.00
    korean_only            n= 9  recall=0.73  ndcg=0.78  gndcg=0.81
    mixed                  n= 5  recall=0.53  ndcg=0.61  gndcg=0.65
    standards              n= 5  recall=0.90  ndcg=0.95  gndcg=0.95
    english_only           n= 1  recall=0.67  ndcg=0.75  gndcg=0.78
  by language:
    en         n= 1  recall=0.67  gndcg=0.78
    ko         n=15  recall=0.81  gndcg=0.85
    mixed      n= 4  recall=0.55  gndcg=0.63

(위는 예시 — 실제 baseline 박제는 후속 PR)

Baseline 박제 정책

위치: tests/search_eval/baselines/v{version}_{date}.{json,md}

각 baseline = 2 파일:

  • .json — 점수 raw (overall + by_category + by_language + by_ocr_derived)
  • .md — 약점 카테고리 분석 보고서 (사람용 audit)

박제 시점:

  • 평가셋 schema 변경 시 (v0.1 → v0.2 swap)
  • 검색 시스템 주요 변경 commit 직후 (모델 swap / chunks 재생성 / reranker 교체)
  • Phase 2A~D Diagnose PR 의 비교 baseline

박제 명명 컨벤션: v0_2_baseline_YYYY-MM-DD[_label].{json,md}. label 은 선택 (예: _after_embedding_swap, _chunks_md_content).

Phase 1 closure 조건 (feedback_quant_expectation_not_hard_gate)

본 PR (PR-Eval-V0_2-Schema-Harness):

  • v0.2 schema swap (23 case)
  • run_eval graded 함수 + --eval-version flag
  • README.md (본 파일)

후속 PR (PR-Eval-V0_2-Baseline-Analysis):

  • 50+ graded case 확정 (신규 28 case 작성)
  • baseline json + analysis md 박제
  • 약점 카테고리 보고서 — embedding-sensitive failure pattern 4 카테고리 식별:
    • candidate recall@k (embedding 후보 selection)
    • crosslingual miss (ko↔en mismatch)
    • Korean-English mismatch (한자/영문 fallback)
    • OCR-derived chunk miss

closure gate: 0.70 미달 OK — 원인 분해 보고서 (analysis md) 가 있으면 Phase 1 closure.

관련

  • Phase 1 plan: ~/.claude/plans/phase-1-graded-eval-v0-2.md
  • Parent plan: ~/.claude/plans/peppy-hugging-nest.md § Phase 1
  • 건강도 평가 (graded relevance 1년 미작성 gap): ~/.claude/projects/-Users-hyungi/memory/project_document_server_health_assessment.md:24
  • 발주건 단위 baseline (별 트랙, Phase 0 / merry-yawning-owl): queries_order_baseline.yaml + order_groups.yaml — 본 PR 영향 없음