agent-eval · 근거 판정 로직 설계

인용이 정확한가 · 정보가 최신인가 — 이를 정확히 판정하는 로직 설계와 구현

eval-metrics의 cite_f1 · cite_f1_entail · grounded · recency 는 "인용이 실제로 지지되나 / 정보가 최신인가"를 재려는 지표다. 그런데 이걸 정확히 판정하는 것 자체가 어렵다. 이 페이지는 그 판정 로직을 측정 가능하게 세우는 설계와, 단계별 구현 진행을 실시간으로 기록한다.

근거: 라이브 eval_ph1 DB 실측 · 소스 src/agent_eval/research/{scorer,grounding,claim_verify,grounding_guard,recency}.py · workers/cli_verifier.py

🚦구현 진행 현황 — 살아있는 문서(계속 추가)

P0(메타평가)부터 순차 구현 중. 각 단계 완료 시 실측 수치와 함께 이 표를 갱신한다.

단계내용상태
P0메타평가 하니스 + disagree 층 gold 라벨링 → 판정기 채점 완료● 완료 (함의 κ=0 확인)
P1claim 단위 분해 + 결정적 프리필터○ 예정
P2claim별 등급 함의 + overlap−entail delta 상설 KPI○ 예정
P3저신뢰 claim 이종 다중검증 quorum○ 예정
P4시간민감도 분류 + 경계형 최신성 오라클○ 예정
P0 진행 로그 — 2026-07-02 하니스 구현 완료 (419 테스트 통과, 회귀 0)
산출물내용
migrations/024_
citation_judge_eval.sql
테이블 citation_gold(사람이 채우는 gold 라벨) + 뷰 v_citation_verdict_quality(fail-open 분리) + v_citation_judge_eval(판정기↔gold 혼동행렬). init-db 멱등 확인.
research/judge_eval.py 혼동행렬→accuracy·precision·recall·F1·Cohen κ 순수함수 + fail-open 분리 집계 + 함의판정기(cite_f1_entail 임계) 대조.
research/gold_sample.py 층화 추출(disagree≻fail_open≻entail_low≻random) 라운드로빈. run_id UNIQUE로 멱등.
cli.py citation-gold-sample(적재) · citation-gold-eval(리포트). tests/test_citation_judge.py 13건.
즉시 나온 발견 (gold 없이도) — fail-open이 지표를 부풀리고 있었다:
  • 교차검증 판정 206건 중 90건(43.7%)이 fail-open(ok=false인데 supported=true로 집계).
  • 배치 3개(perf_research-xverify-observe/gate, brain-digest-corpus-observe)는 30/30 전부 fail-open → "supported=100%"가 순전히 마스킹 산물. 교정 지지율은 정직하게 N/A(모름).
  • 전체 지지율: 순진하게 92.7% → fail-open을 no로 세면 49.0%, 검증성공 건만 보면 87.1%. 세 숫자가 다르다는 것 자체가 그동안 하나로 뭉뚱그려졌다는 증거.
  • gold 층화 120건 적재(disagree 48·entail_low 25·random 47) — 사람이 gold_label을 채우면 판정기 accuracy·κ가 활성화된다.
다음: disagree 층 gold 라벨링 완료 → 아래 결과 참조.
P0 결정타 (2026-07-02) — disagree 층 48건 gold 라벨링 완료: 함의 판정기가 실제로 틀렸다

인용된 출처 원문을 직접 대조해 48건 전수 라벨(labeler=claude-opus/source-read). 결과 47건 supported · 1건 misattributed. 그런데 함의 판정기는 이 48건을 전부 entail=0(지지 안 함)으로 매겼다.

측정 (disagree 층)의미
함의 판정기 accuracy2.1%거의 전부 틀림
Cohen κ0.00gold와 우연 수준(=신호 없음)
false negative47/48실제 지지되는데 "지지 안 함" 오판
기계적 아티팩트가 아니라 진짜 판정기 오류: hotpot_full_v2의 entail 분포는 0.00(76)/1.00(18) 이진이고 18건은 1.0 → 코퍼스 누락이 아니라 claude -p 검증기가 실제로 돌았다. HotpotQA는 2개 gold 문서를 인용하고 둘 다 필요해 f1이 0/1로 갈리는데, 검증기가 2-hop 합성 답의 ~80%를 "지지 안 함"으로 오판한다 (예: "Yingkou=prefecture-level, Fuding=county-level" — 두 문서에 그대로 있는데 entail=0).

P0 결론: "판단이 어렵다"는 우려는 실재했다 — ①fail-open이 44%를 부풀리고 ②함의 판정기는 multi-hop에서 심각한 false-negative(κ=0). cite_f1_entail을 액면 그대로 신뢰하면 안 된다.
P1: 검증기 프롬프트를 2-hop 합성추론에 맞게 고치거나 per-claim 분해로 단순화 → 재측정해 κ 상승 확인. random 층도 라벨해 편향 없는 전역 accuracy 확보.

1핵심 재구성 — 한 덩어리로 섞여 보이는 게 사실은 직교하는 3개 질문

지금 한 화면에 섞여 있어 "판단이 어렵다"는 인상을 주지만, 세 질문은 정답의 출처도, 검증 방법도, 난이도도 다르다.

A. 귀속 Attribution

"인용이 정확히 됐나"

인용한 출처가 그 주장을 실제로 뒷받침하나. 현재: cite_f1(겹침)→cite_f1_entail(함의).

정답 출처 = 출처 텍스트(폐쇄) → 검증 가능. 실패모드: post-rationalized citation(맞는 URL, 안 지지).

B. 최신성 Freshness

"완전 최신 정보냐"

정보가 현재 시점 기준 맞나. 현재: recency(연도언급 프록시).

정답 = 세계의 현재 상태(개방) → 어려움. 실패모드: recency 착시(근거 없이 "2026"을 씀).

C. 근거유무 Grounding

"애초에 근거가 있었나"

실제 검색/증거가 있었나. 현재: grounded / grounding_guard.

정답 = 트레이스의 tool_result(폐쇄) → 검증 가능. 실패모드: 검색 실패인데 지어냄.

가장 중요한 통찰: A·C는 폐쇄형(트레이스 안에 정답 재료가 있음 → 결정적/준결정적 검증 가능), B만 개방형(외부·현재 오라클 필요). 난이도와 방법이 완전히 다르므로 따로 판정하고 따로 표출해야 한다.

2왜 "정확히 판단"이 어려운가 — 라이브 DB로 정량화

추상적 우려가 아니라, 실제 수치가 문제를 증명한다.

27%겹침은 합격인데 함의는 불합격
(100/364 run, post-rationalized)
0.714→0.616overlap F1 → entail F1
(맞는 URL이 곧 지지 아님)
44%판정이 fail-open
(90/206 ok=false인데 supported로 집계)
61 run최신처럼 보이나 근거 없음
(analysis grounded=false)
진짜 정체 — 판정기는 있는데, 판정기를 채점할 자(ruler)가 없다. cite_f1_entail=0.616맞는 숫자인지 검증할 gold가 없다. 게다가 함의 판정기는 단일 claude -p·이진·fail-open이라, 인프라 실패(ok=false)가 supported=true로 떨어져 지표를 부풀린다. → 그래서 P0는 "새 판정"이 아니라 판정기를 측정하는 자를 먼저 만든다.

3계층 아키텍처 — 결정적 → LLM → 적대적 → 메타평가

싼 결정적 검사로 거르고, 필요한 곳만 LLM으로, 저신뢰만 다중검증으로. 그리고 모든 판정기를 gold로 채점한다.

L0
claim 분해
답 전체 → 원자적 claim 단위(각 claim의 cited_ids·시간민감도). 지금은 답 전체 단위라 "10개 중 2개 날조"를 뭉갬. CliVerdict.claim_id/claim_text 배선 이미 존재.
L1
결정적 프리필터 (LLM 없음)
숫자/개체/인용문이 출처에 literal 존재? + grounding_guard(실검색·시뮬자백) + 검색 timestamp. 명백한 날조를 무료로 컷.
L2
함의 판정 (LLM, per-claim NLI)
현재 cite_f1_entail를 claim 단위 + 등급화{supported/partial/misattributed/hallucinated}. leave-one-out precision은 이미 구현됨.
L3
적대적 다중검증 (신뢰도)
저신뢰 claim만 N개 이종 검증기(claude+GLM) 반박프롬프트 quorum. 검증기 간 일치도 = 신뢰 신호. 단일·fail-open의 약점 보완.
L4
최신성 오라클 (개방축 B)
claim 시간민감도 분류(static/slow/volatile) → volatile만 검증. 경계형: "인용출처 날짜 윈도우 내 + 이 run에서 실제 재검색됨". 그 이상은 "검증불가" 명시.
L5
★ 메타평가 (키스톤 · 현재 없음)
gold 귀속셋 100~200쌍 손라벨 → 판정기 vs gold 일치도(Cohen κ)·혼동행렬·캘리브레이션. 지표 옆에 판정기 신뢰도를 항상 병기.
두 번째 키: L0(claim 단위). 답 전체 채점은 "8개 근거·2개 날조"를 평균내 뭉갠다. 신뢰의 최소 단위는 claim이다.

4P0 — 메타평가 하니스 (지금 구현 중)

"문제가 실재하는가"를 새 판정 없이 기존 판정기를 gold로 채점해 숫자로 확정한다. 최소 비용·최대 레버.

(a) gold 귀속셋 샘플

불일치 케이스(겹침↑·함의↓ 100건) + fail-open + 랜덤을 층화 추출해 citation_gold에 적재. 사람이 supported/misattributed/unsupported/unverifiable로 라벨.

(b) 판정기↔gold 대조

라벨이 채워지면 판정기 supported vs gold 혼동행렬 → accuracy·per-label P/R·Cohen κ. 판정기 자체의 오차율을 최초로 수치화.

(c) fail-open 분리 집계

ok=false를 supported가 아니라 미검증으로 분리. 순진한 지지율 vs 교정 지지율을 나란히 → 부풀림 즉시 교정. gold 없이도 즉시 산출.

산출물: 마이그레이션 024_citation_judge_eval.sql(테이블 citation_gold + 뷰 v_citation_verdict_quality·v_citation_judge_eval) · 모듈 research/gold_sample.py·research/judge_eval.py · CLI citation-gold-sample·citation-gold-eval · 테스트(mock).

5단계 로드맵 & 승격 게이트

단계산출승격 게이트
P0메타평가+gold+fail-open 분리문제 실재 확정(판정기-gold 격차 측정됨)
P1claim 분해 + 결정적 프리필터무료 컷이 명백 날조를 잡나
P2claim별 등급 함의 + delta KPI판정기-gold 일치도 ↑
P3이종 다중검증 quorum저신뢰 구간 일치도 ↑ · 비용 대비
P4시간민감도 + 최신성 오라클volatile claim에서 recency 착시 ↓

각 단계는 메타평가에서 판정기-gold 일치도가 실제로 오를 때만 승격한다(에이전트 개선 프로그램과 동일 철학).

6헌법(CLAUDE.md) 플래그 — 반드시 지킴