feat: Phase 3.5 calibration — telemetry source + grounding/verifier 강화 #1
Reference in New Issue
Block a user
Delete Branch "feat/phase3-5-calibration"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Phase 3.5 calibration — eval 채널 분리 + 숫자 hallucination 방어선 강화.
ask_events.source+eval_case_id스키마 +X-Eval-Tokentrust boundary (migrations 138~142)calibrate_ask.pyCLI (Q0~Q8 집계 + FP CSV + baseline compare, 읽기 전용)VERIFIER_NUMERIC_PROMOTE=0기본) + re-gate Tier 4 신규results/artifacts/gitignoreTest plan
git pull→ migrations 138~142 자동 적용 확인 (schema_migrations테이블)credentials.env에EVAL_RUNNER_TOKEN=$(openssl rand -hex 32)추가docker compose up -d --build fastapi+ 헬스체크X-Eval-Token헤더) →ask_events.source='eval'+eval_case_id기록 확인source='document_server')- migrations 138~142: source TEXT DEFAULT 'document_server' + eval_case_id TEXT 추가, 인덱스 2개, backfill, 1주 관찰 후 NOT NULL (140 적용 분리) - app/models/ask_event.py: source / eval_case_id ORM 필드 (138~141 단계 nullable) - app/services/search_telemetry.py: record_ask_event 시그니처에 source / eval_case_id - app/core/config.py: settings.eval_runner_token + EVAL_RUNNER_TOKEN env 로드 - app/api/search.py: - X-Source / X-Eval-Case-Id / X-Eval-Token 헤더 수신 - _resolve_eval_identity(): hmac.compare_digest 로 token 검증, 실패 시 source 'document_server' 강등 + warning log + eval_case_id=None - 두 record_ask_event 호출에 검증된 source/eval_case_id 전달 - credentials.env.example: EVAL_RUNNER_TOKEN= (empty default = 모든 eval claim 거부) - tests/test_ask_eval_auth.py: 9 케이스 — token 없음/틀림/일치, env 미설정, case_id only, non-eval source forces case_id None trust boundary: 일반 client 의 X-Source=eval / X-Eval-Case-Id 시도는 무시되어 calibration telemetry 오염 불가. eval runner 만 EVAL_RUNNER_TOKEN 으로 인증. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>scripts/calibrate_ask.py — ask_events 집계 + markdown report 영구 도구. 기능: - argparse: --source / --prompt-version / --since / --until / --eval-split (tuning|confirm|all, id 해시 기반 deterministic split) / --run-label / --output / --format md|json / --compare-against / --sample-limit / --fp-artifacts / --inspect-shape / --dry-run - 9개 fetcher (모두 read-only SELECT): - Q0 defense_layers shape inspect - Q1 re-gate tier 분포 - Q2 max_rerank_score 히스토그램 (bucket × bin) - Q3 classifier 혼동행렬 - Q4 verifier severity 분포 (cast + COALESCE NULL safe) - Q5 hallucination_flags top-K (UNION ALL outer wrap, strong/weak 컬럼 유지) - Q6 eval golden mismatch (eval_case_id 기반 join + query string fallback) - Q7 FP candidate (case A/B/C 분리 + candidate_reason 컬럼 + LIMIT/3 분배) - Q8 answer_length p25/p50/p75 분포 (E.3 v1↔v2 비교 축) - markdown render + json baseline + delta compare (compare-against) - FP CSV dump (artifacts/fp_candidates_{run_label}.csv) + is_true_fp 공란 - dry-run: tests/calibrate_fixtures/sample_ask_events.json 로 출력 검증 - --threshold-overrides: Step 0 feasibility 통과 후 v2 (현재 stub raise) read-only 강제: INSERT/UPDATE/DELETE/ALTER/DROP/TRUNCATE 0건. tests/calibrate_fixtures/sample_ask_events.json: dry-run snapshot fixture. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Codex adversarial review (no-ship) 반영: fix1: unit-aware numeric clearing - _extract_numeric_corpus(): 단위별 bucket dict (exact_by_unit) + ranges_by_unit (양방향 + 단방향 bound 통합) - _within_unit_range / _close_to_unit_pool: 같은 unit 안에서만 매칭 bare answer 는 보수적으로 range/tolerance 패스 X - 2-pass cleared_pairs (unit, digits): cross-unit cleared 절대 skip 안 함. bare(None) 답변은 unit-anchored cleared 시 duplicate 로 skip (콤마 normalize 부산물 보호 — Codex 케이스는 그대로 flag) fix3: 최대/최소 bound semantics - _APPROX_PREFIX_RE 에서 최대/최소 제거 (약/대략/거의/얼추 만 strip) - _BOUND_PATTERN_RE: 최대 N → range (0, N-1), 최소 N → range (N+1, 1e18) - 경계값 자체는 cleared 대상 아님 ("최대 100명" + answer "100명" → flag) - bound span 내 숫자는 exact pool 에서 제외 기존 prefix strip / 콤마 / 부터 separator / 단위 동의어 / tolerance 4자리+ / 식별자성 단위 1자리 flag 동작 모두 유지. tests/test_grounding_fabricated_number.py: 25 케이스 — 기존 17 + Codex unit-mismatch 3 (won_vs_myeong_range/tol, pct_vs_myeong_range) + bound 5 (최대/최소 boundary/inner/outer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>VERIFIER_NUMERIC_PROMOTE 환경변수로 numeric_conflict severity 승격 실험. verifier_service.py: - _NUMERIC_PROMOTE = os.getenv('VERIFIER_NUMERIC_PROMOTE', '0') == '1' (import time 평가 — env 변경 시 process restart 필수) - _SEVERITY_MAP['numeric_conflict']: env=1 → critical=strong / minor=medium, env=0 (기본) → 둘 다 medium (기존 동작 유지) - direct_negation 은 env 무관 항상 strong (안전장치) verifier.txt: - numeric_conflict 정의에 critical/minor 분리 명시 (core quantity vs peripheral) - "Range values satisfy any answer within range" rule 추가 - severity mapping 갱신: numeric_conflict 분기 명시 search.py re-gate (Tier 1~7 재번호, B2 신규 Tier 4): - v_strong_numeric = sum(1 for f in v_strong if f.startswith('verifier_numeric_conflict')) - Tier 4 (신규): g_strong + v_strong_numeric >= 1 + low_conf → refuse re_gate value: 'refuse(grounding+verifier_numeric)' - 원칙 유지: verifier strong 단독 refuse 금지 — g_strong 교차 필수 - 호환성: 기존 re_gate string literals 그대로 유지, 신규 1개만 추가 credentials.env.example: VERIFIER_NUMERIC_PROMOTE=0 (off, B3 통과 후 production 전환) tests/test_verifier_numeric_promote.py: 4 케이스 (env off / on / explicit 0 / direct_negation invariant). monkeypatch.setenv + importlib.reload 패턴. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>migration 142 ALTER COLUMN source SET NOT NULL 자동 적용 방지. _run_migrations 의 glob('*.sql') 비재귀 → _deferred/ 무시. 활성화 절차 (D7 참조): - 138~141 적용 + 7일 운영 후 SELECT COUNT(*) FROM ask_events WHERE source IS NULL AND created_at > <deploy> = 0 확인 - git mv migrations/_deferred/142_*.sql migrations/142_*.sql - docker compose restart fastapi (init_db 가 자동 적용) 이유: 새 코드의 source 누락 가능성 empirical 검증 후 lock. NOT NULL 적용 후 NULL INSERT 시도 시 ask_events 기록 실패 (data loss). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>