Skip to content

dlekdns08/UASEF

Repository files navigation

UASEF

UASEF

Uncertainty-Aware Safe Escalation Framework for Medical LLM Agents

LLM 기반 의료 에이전트가 자신의 불확실성을 정량화하고, 위험도를 판단하여 인간 전문가에게 자동 인계하는 연구 프레임워크입니다.


목차

  1. 연구 배경 및 동기
  2. 핵심 설계 철학
  3. 프로젝트 구조
  4. 아키텍처 상세
  5. 데이터셋
  6. 실험 설계
  7. 평가 지표
  8. 설치 및 환경 구성
  9. 실험 실행
  10. 출력 파일
  11. 논문 권장 설정
  12. 참고문헌

1. 연구 배경 및 동기

문제 정의

LLM을 의료 현장에 배포할 때 가장 큰 장벽은 "모델이 언제 틀리는지 모른다" 는 점입니다. 의료 도메인에서 잘못된 자신감(overconfidence)은 치명적 결과로 이어질 수 있습니다. 기존 접근법들은 세 가지 한계를 가집니다.

기존 접근법 한계
단순 threshold 기반 임계값이 임의적이며 통계적 보장 없음
Human-in-the-loop (항상) 자율 처리 불가 → 운영 비용, 지연
확률 보정(calibration) 도메인 이동(distribution shift) 시 보장 붕괴

UASEF의 제안

UASEF는 Conformal Prediction(CP) 이론을 의료 LLM에 적용하여 세 가지를 동시에 달성합니다.

  1. 통계적 보장: P(s_test ≤ q̂) ≥ 1 - α — 이론적으로 증명된 커버리지
  2. 동적 위험도 반영: 전문과목·시나리오에 따라 임계값 자동 조정
  3. 선택적 에스컬레이션: 자율 처리 가능한 케이스는 AI가, 불확실하거나 고위험인 케이스만 전문의에게 인계

2. 핵심 설계 철학

왜 Conformal Prediction인가?

기존 확률 보정의 문제점 (temperature scaling, Platt scaling)

  • 모델이 "70% 확신" 이라고 말해도, 실제로 70% 맞을 것이라는 보장이 없음
  • 학습 분포와 다른 도메인에서 보정 성질이 유지되지 않음

Conformal Prediction의 강점

  • 분포 가정 불필요. 교환가능성(exchangeability)만 가정
  • q̂ = ⌈(n+1)(1-α)⌉/n 번째 순위 비적합 점수 → 이 임계값 하나로 P(s_test ≤ q̂) ≥ 1-α 성립
  • 따라서 "α = 0.05로 설정 → 실제 에스컬레이션 누락률 ≤ 5%"가 수학적으로 보장됨

왜 Nonconformity Score로 token logprob을 쓰는가?

비적합 점수(nonconformity score)는 "이 테스트 포인트가 캘리브레이션 셋과 얼마나 다른가"를 수치화한 것입니다. UASEF는 평균 negative log-likelihood를 사용합니다.

s(x) = -mean(log P(t_i | context, t_1, ..., t_{i-1}))

이 선택의 이유:

  • 모델이 생성한 각 토큰의 확률을 그대로 반영 → 답변 생성 과정 자체의 불확실성
  • Temperature = 0일 때도 의미 있음 (greedy decoding이지만 logprob은 여전히 분포를 반영)
  • API 추가 호출 불필요 (generate 한 번으로 score와 답변을 동시에 얻음)

왜 세 모듈로 분리했는가?

UQM  →  "이 질문이 얼마나 어려운가?" (CP 기반 통계 측정)
RTC  →  "얼마나 어려워야 에스컬레이션할 것인가?" (위험도 기반 임계값)
EDE  →  "최종적으로 에스컬레이션할 것인가?" (다중 신호 통합 결정)

세 관심사를 분리함으로써:

  • UQM은 CP 이론 컴포넌트로만 교체 가능 (weighted CP, conformal risk control 등)
  • RTC의 전문과목 위험도 온톨로지는 임상 전문가 피드백으로 독립 업데이트 가능
  • EDE의 트리거 정책은 기관별 프로토콜에 맞게 조정 가능

왜 LangGraph ReAct 구조인가?

단순 쿼리-응답이 아닌 **추론-행동 루프(Reasoning + Acting)**를 택한 이유:

  • 의료 질문은 단일 답변보다 도구 활용(약물 상호작용 DB, 가이드라인 검색)이 필요
  • UASEF는 에이전트 내부가 아닌 외부에서 독립적으로 판정 → 에이전트 출력을 감사(audit)하는 구조
  • LangGraph의 StateGraph가 ReAct 루프와 UASEF 체크 노드를 명확히 분리

3. 프로젝트 구조

UASEF/
├── models/                         # 핵심 모듈
│   ├── model_interface.py          # LMStudio / OpenAI 통합 추상화 레이어 (BOM·제어문자 sanitize 포함)
│   ├── uqm.py                      # Uncertainty Quantification Module (CP 기반)
│   ├── rtc_ede.py                  # Risk-Threshold Calibrator + Escalation Decision Engine
│   ├── rtc_calibration.py          # ★ RTC 배율 Pareto sweep (데이터 기반 역산)
│   ├── entropy_calibration.py      # ★ 엔트로피 임계값 Youden's J 자동 결정
│   └── ede_coefficient_search.py   # ★ EDE confidence 계수 grid search
│
├── agent/                          # LangGraph ReAct 에이전트
│   ├── graph.py                    # StateGraph 조립
│   ├── nodes.py                    # 노드 함수 + AgentComponents
│   ├── state.py                    # MedicalAgentState TypedDict
│   └── tools.py                    # 의료 도구 4종 (drug, guideline, lab, DDx)
│
├── data/
│   ├── loader.py                   # MedQA / MedAbstain / PubMedQA / MIMIC-III 로더
│   ├── raw/                        # 로컬 JSONL 파일 위치 (.gitignore)
│   └── README.md                   # 데이터 소스 및 다운로드 가이드
│
├── experiments/
│   ├── configs/                    # 시나리오별 YAML 설정
│   │   ├── base_config.yaml        # 공통 기본값 (캘리브레이션 결과 포함)
│   │   ├── scenario_emergency.yaml
│   │   ├── scenario_rare_disease.yaml
│   │   └── scenario_multimorbidity.yaml
│   ├── config_utils.py             # ★ 공통 캘리브레이션 config 로더
│   ├── run_calibration_pipeline.py # ★ 캘리브레이션 파이프라인 (Step 1→5)
│   ├── run_experiment.py           # 순차 파이프라인 실험 (LMStudio vs OpenAI)
│   ├── run_agent_experiment.py     # LangGraph 에이전트 실험
│   ├── run_baseline_comparison.py  # 베이스라인 비교 (no_esc / threshold_only / full_uasef)
│   ├── eval_medabstain.py          # MedAbstain AP/NAP 분류 정확도 평가
│   ├── pareto_sweep.py             # α sweep → Pareto frontier + α 권고
│   ├── run_all_experiments.py      # ★ 전체 실험 통합 실행 + 요약 보고서 생성
│   └── visualize_results.py        # 결과 시각화
│
├── results/                        # 실험 결과 (자동 생성, .gitignore)
├── pyproject.toml
└── .env.example

4. 아키텍처 상세

4.1 UQM — Uncertainty Quantification Module

파일: models/uqm.py

UQM은 단일 질문의 불확실성을 통계적으로 보장된 수치로 변환합니다.

내부 흐름

질문 입력
   ↓
_get_score(): LLM 호출 → token logprobs 수집
   ↓
compute_nonconformity_score(): s = -mean(logprobs)
   ↓
calibrator.threshold와 비교 → should_escalate
   ↓
compute_entropy(): top_logprobs로 위치별 조건부 엔트로피 계산
   ↓
UncertaintyResult 반환

Conformal Calibration 수식

보정 집합 {s_1, ..., s_n} (비적합 점수들)에서 임계값 계산:

q̂ = s_{(⌈(n+1)(1-α)⌉)}  ← n번째 순위 점수

보장: P(s_test ≤ q̂) ≥ 1 - α

실제 구현에서는 numpy.quantile을 사용하며, level을 min(1.0, ⌈(n+1)(1-α)⌉/n) 으로 보정하여 유한 표본에서의 보수성을 확보합니다.

Scoring Method 비교

방식 수식 특징 논문 위치
logprob (Primary + Ablation) s = -mean(token logprobs) CP 보장 ✓, 단일 쿼리 주요 기여 + Ablation
self_consistency (대안) s = Jaccard_diversity × 5 CP 보장 ✓, N회 쿼리, logprobs 불필요 블랙박스 LLM 호환용
auto 런타임 감지 재현성 저하 위험 비권장

왜 logprob이 Primary이고 Ablation 모두인가? LM Studio의 OpenAI-compatible API는 token-level logprobs를 지원하므로, OpenAI와 로컬 GGUF 모델 모두 동일한 logprob 비적합 함수를 사용합니다. Ablation의 목적은 scoring method 차이가 아니라, 로컬 환경에서도 CP coverage 보장이 성립함을 검증하는 것입니다. self_consistency는 logprobs를 지원하지 않는 Claude API, Gemini API 등에 적용할 수 있는 대안입니다.

엔트로피 계산

compute_entropy(response: ModelResponse)top_logprobs가 있을 때만 유효한 엔트로피를 반환합니다.

# 각 토큰 위치에서 상위 k개 logprob으로 조건부 분포 근사
probs = softmax(top_k_logprobs)   # 정규화
H_pos = -sum(p * log(p))          # 위치별 엔트로피
H_avg = mean(H_pos)               # 전체 평균 (nats/token)

top_logprobs가 없으면 float("nan") 반환 — 개별 토큰 logprob으로는 Shannon 엔트로피를 계산할 수 없기 때문입니다 (각 값이 완전한 어휘 분포를 구성하지 않음).

Distribution Shift 처리

# 보정: MedQA 분포
uqm.calibrate(cal_questions, distribution_source="medqa")

# 평가: MIMIC-III 분포 (다른 분포!) → 자동 경고 + Weighted CP 전환
uqm.evaluate(question, distribution_source="mimic3")

Weighted CP (Tibshirani et al., 2019)는 교환가능성 위반 시 커버리지 보장을 복원합니다.

w_i = 1 + k × Jaccard(cal_i, test)   # 밀도비 근사

q̂_w = inf{q : Σ_{s_i ≤ q} w_i / (Σ w_i + w_{n+1}) ≥ 1-α}

w_{n+1} (테스트 포인트 자신의 weight)를 분모에 포함해야 CP 하한 보장이 성립합니다. w_{n+1} = 1 + k (Jaccard(test, test) = 1.0 이므로 최대 유사도).

UncertaintyResult 주요 필드

필드 설명
nonconformity_score 비적합 점수 — 클수록 불확실
margin threshold - score — 양수=안전 여유, 음수=임계값 초과
confidence_entropy 위치별 조건부 엔트로피 (nats/token). top_logprobs 없으면 nan
should_escalate score > threshold 여부
weighted_cp_used Weighted CP 적용 여부
prediction_set_size 항상 1. 하위 호환성 유지용 필드 (binary outcome에서 prediction set은 단일 원소)

LLM 지원 요건

scoring_method logprobs 필요 적용 가능 LLM 논문 위치
logprob (Primary + Ablation) 필수 GPT-4o, GPT-4o-mini, LMStudio (llama.cpp) 주요 기여 + Ablation
self_consistency (대안) 불필요 모든 LLM 블랙박스 LLM 호환용

logprob 방식은 token-level logprobs를 지원하지 않는 Claude API, Gemini API, Cohere 등에서 ValueError가 발생합니다. 이런 환경에서는 self_consistency를 사용하세요. LM Studio는 OpenAI-compatible API로 logprobs를 지원하므로 Primary와 동일한 방식을 사용합니다.

Calibration 견고성

UQM.calibrate()는 샘플별 최대 3회 재시도 후 실패하면 해당 샘플만 건너뜁니다. LMStudio가 간헐적으로 logprobs를 반환하지 않는 경우나 OpenAI API 오류 발생 시 캘리브레이션 전체가 실패하지 않도록 설계되었습니다.

[RETRY 1/3] 샘플 42: Backend이 logprobs를 반환하지 않습니다
...
[SKIP 42/500] 3회 실패, 샘플 건너뜀

또한 ConformalCalibrator.fit()은 CP 보장을 위한 최소 n을 검증합니다: n_min = ⌈(1-α)/α⌉ (α=0.05 → n_min=19). n이 이 값보다 작으면 UserWarning이 발생합니다.


4.2 RTC — Risk-Threshold Calibrator

파일: models/rtc_ede.py, models/rtc_calibration.py

UQM이 반환한 기본 임계값 를 전문과목과 시나리오의 위험도에 따라 동적으로 조정합니다.

조정 수식

adjusted_threshold = q̂ × risk_multiplier × scenario_multiplier
위험 등급 기본 배율 해당 전문과목
CRITICAL ×0.60 응급의학, 중환자의학, 외상외과
HIGH ×0.75 심장내과, 신경과, 종양학, 심흉외과
MODERATE ×1.00 내과, 외과, 소아과, 산부인과
LOW ×1.30 일반 외래, 예방의학, 피부과, 정신건강의학과

emergency / rare_disease 시나리오에는 추가 ×0.90 적용됩니다.

설계 이유: 응급의학에서 에스컬레이션 누락(False Negative)의 비용은 일반 외래에 비해 훨씬 큽니다. 임계값을 낮추면 더 많은 케이스가 에스컬레이션되지만, 위험한 케이스를 놓칠 확률이 줄어듭니다. 이 트레이드오프를 전문과목 온톨로지로 인코딩했습니다.

데이터 기반 배율 역산 (rtc_calibration.py)

위 표의 배율은 기본값입니다. run_calibration_pipeline.py를 실행하면 레이블 데이터에서 Pareto sweep으로 배율을 자동 역산합니다.

각 위험도 수준별로 후보 배율 (예: CRITICAL ∈ {0.55, 0.60, 0.65, 0.70, 0.75}) sweep
→ Safety Recall ≥ 0.95 AND Over-Escalation ≤ 0.15 를 동시 충족하는 후보 중
  Over-Escalation이 최소인 배율 선택 (제약 불충족 시 Safety Recall 최대 fallback)
→ 결과를 base_config.yaml의 rtc 섹션에 저장

결과는 RTC(base_threshold, multipliers=cfg["rtc"]) 형태로 모든 실험 파일에 자동 주입됩니다.

Pareto Frontier 분석

rtc.pareto_frontier(sweep_results)

pareto_sweep.py의 실측 데이터를 받아 각 (α, specialty) 조합에서 (coverage, escalation_rate) 쌍을 반환합니다. 이를 통해 실제로 측정된 trade-off를 시각화하고 최적 α를 권고합니다.


4.3 EDE — Escalation Decision Engine

파일: models/rtc_ede.py, models/entropy_calibration.py, models/ede_coefficient_search.py

세 가지 트리거를 통합하여 최종 에스컬레이션 여부를 결정합니다.

트리거 구조

Trigger 1 — UNCERTAINTY_EXCEEDED:
    nonconformity_score > adjusted_threshold
    → CP 이론의 직접 신호 (주 트리거)

Trigger 2 — HIGH_RISK_ACTION:
    CRITICAL_KEYWORDS 감지   (EOL 결정, Code Blue)  → 항상 트리거
    PROCEDURAL_KEYWORDS 감지 (intubation, 승압제)   → UNCERTAINTY_MODIFIERS 동반 시만 트리거

Trigger 3 — NO_EVIDENCE:
    근거 부재 표현 감지 (아래 참조)

하나라도 활성화 → should_escalate = True

Trigger 2 설계 이유: "에피네프린을 아나필락시스에 투여하세요" 같은 정상적인 처치 권고가 키워드만으로 에스컬레이션되는 False Positive를 방지합니다. 따라서 시술 키워드는 불확실 표현(consider, may need, if deteriorates 등)과 함께 나타날 때만 활성화합니다. 반면 DNR, withdraw care 같은 EOL 결정은 AI가 단독으로 판단해서는 안 되므로 항상 에스컬레이션합니다.

Trigger 3 NO_EVIDENCE 키워드 목록

근거 부재 표현은 출처별로 관리됩니다. 논문 재현 시 source 필드를 인용 근거로 사용하세요.

출처 예시 표현
medabstain "i am not certain", "insufficient evidence", "limited data"
savage2025 "this is unclear", "evidence is mixed", "conflicting data"
manual (GPT-4o 500건) "clinical judgment needed", "differential is broad"
extended "cannot be determined", "requires further evaluation", "beyond my knowledge"

전체 37개 문구가 NO_EVIDENCE_STRINGS (models/rtc_ede.py)에서 관리됩니다. 모든 실험 파일은 from models.rtc_ede import NO_EVIDENCE_STRINGS를 통해 단일 출처를 참조합니다.

탐지 함수 detect_no_evidence(text)(triggered: bool, matched_phrases: list[str]) 를 반환하여 논문 재현에 필요한 매칭 증거를 함께 제공합니다.

Confidence 계산

confidence = min(1.0,
    len(triggers) / 3
    + t1_weight    if UNCERTAINTY_EXCEEDED in triggers   # 기본 0.4
    + entropy_boost if entropy > entropy_threshold       # 기본 0.15, 기본 임계값 2.0
)

엔트로피는 별도 트리거가 아닌 신뢰도 가중치로만 사용됩니다. 세 계수(t1_weight, entropy_boost, entropy_threshold)는 모두 데이터 기반으로 산출하여 base_config.yaml에 저장됩니다.

엔트로피 임계값 자동 결정 (entropy_calibration.py)

ENTROPY_HIGH_THRESHOLD = 2.0 하드코딩 대신 calibration 데이터에서 Youden's J 통계량으로 자동 결정합니다.

Youden's J = Sensitivity + Specificity - 1  (최대화 지점 선택)
→ 결과를 base_config.yaml의 entropy_threshold에 저장

EDE 계수 grid search (ede_coefficient_search.py)

t1_weight    ∈ {0.2, 0.3, 0.4, 0.5}
entropy_boost ∈ {0.05, 0.10, 0.15, 0.20}

최적화 목표: F1-safety = harmonic_mean(Safety Recall, 1 − Over-Escalation Rate)
→ 결과를 base_config.yaml의 ede 섹션에 저장

4.4 LangGraph 에이전트

파일: agent/graph.py, agent/nodes.py, agent/state.py

그래프 흐름

START → reason → [tool_calls?] → act ──→ reason  (ReAct 루프, 최대 5회)
                                         ↓
                               uasef_check  ← 원본 질문 독립 재판
                               ↙          ↘
                          escalate      finalize
                             ↓               ↓
                           END             END

주요 설계 결정

① uasef_check는 에이전트와 독립

uasef_check 노드는 에이전트의 메시지 히스토리를 보지 않고 원본 질문을 직접 UQM에 전달합니다. 에이전트가 도구로 정보를 많이 수집했더라도 UASEF는 별도로 판정합니다.

이 설계 이유:

  • 에이전트가 틀린 정보를 수집해도 UASEF가 안전망 역할
  • 에이전트 출력을 감사(audit)하는 외부 컴포넌트 패턴
② LLM 재호출 최소화

reason 노드에서 이미 logprobs=True로 LLM을 호출합니다. uasef_check에서 마지막 AIMessage의 response_metadata에서 logprobs를 추출해 pre_computed_response로 UQM에 전달하면, logprob 모드에서 두 번째 LLM 호출을 생략합니다.

pre_resp = _extract_model_response(last_ai_message, backend)
unc = components.uqm.evaluate(question, pre_computed_response=pre_resp)
# pre_resp가 있으면 LLM 재호출 없이 score 계산
③ AgentComponents를 functools.partial로 바인딩

LangGraph State에 비직렬화 객체(UQM, RTC, EDE)를 넣지 않고, functools.partial로 각 노드 함수에 클로저로 전달합니다. State는 JSON 직렬화 가능한 데이터만 포함합니다.

④ 의료 도구 4종 (Mock 구현)
도구 역할 실제 연구 교체 대상
drug_interaction_checker 약물 상호작용 확인 Drugs@FDA API / Lexicomp
clinical_guideline_search 임상 가이드라인 검색 UpToDate / PubMed E-utilities
lab_reference_lookup 검사 참고치 조회 LOINC / 기관 내 LIS
differential_diagnosis 감별 진단 Isabel DDx / 기관 내 CDR

5. 데이터셋

자동 로딩 우선순위

1. data/raw/*.jsonl       (로컬 JSONL 파일)
2. HuggingFace datasets   (자동 다운로드)
3. 내장 fallback          (개발/테스트 전용, 30개)

MedQA (USMLE 4-options)

  • 역할: Calibration + 기본 시나리오 테스트
  • 출처: Jin et al., 2021 — "What Disease does this Patient Have?"
  • HuggingFace ID: GBaker/MedQA-USMLE-4-options
  • 사용 split: train (calibration), test (테스트 시나리오)

USMLE(미국 의사면허시험) 스타일의 4지선다 문제로 구성됩니다. 정답만 알아도 되는 것이 아니라, 왜 틀렸는지를 통해 불확실성을 측정하는 데 적합합니다.

MedAbstain

  • 역할: 희귀질환·불확실 시나리오, safety 평가의 핵심
  • 출처: Zhu et al., 2023 — "Can LLMs Express Their Uncertainty?"

4가지 변형이 있으며, AP와 NAP가 safety 평가의 핵심입니다.

변형 설명 expected_escalate 사용 시나리오
AP Abstention + Perturbed True 희귀질환 (불확실 + 변형 질문)
NAP Normal + Perturbed True 희귀질환 (정상 답변이지만 변형 질문)
A Abstention only True 일반 불확실 케이스
NA Normal False 정상 케이스 (True Negative 검증)

왜 AP/NAP가 핵심인가?: AP와 NAP는 원래 질문을 미묘하게 변형(perturb)하여 모델의 안정성을 테스트합니다. 이 변형된 질문에 자신 있게 답하는 모델은 에스컬레이션해야 하는 상황을 놓칠 위험이 있습니다.

PubMedQA (선택 사항)

  • 역할: rare_disease 버킷 보강 + NO_EVIDENCE 트리거(Trigger 3) 검증
  • 출처: Jin et al., 2019 — "PubMedQA: A Dataset for Biomedical Research Question Answering"
  • HuggingFace ID: pubmed_qa / pqa_labeled (1,000 expert-labeled)

final_decision = "maybe" 케이스만 expected_escalate=True로 설정하여 rare_disease 버킷에 추가합니다. 활성화 방법:

# experiments/configs/base_config.yaml
data:
  include_pubmedqa: true

MIMIC-III (선택 사항)

  • 역할: 실제 ICU 임상 기록으로 distribution shift 실험
  • 조건: PhysioNet DUA(Data Use Agreement) 서명 필요
  • 사용 목적: Weighted CP가 분포 이동 상황에서도 커버리지 보장을 복원하는지 검증

CP 보장은 calibration과 evaluation이 같은 분포에서 나올 때만 유효합니다 (exchangeability). MedQA로 보정한 뒤 MIMIC-III로 평가하면 CP 보장이 깨지며, 이를 Weighted CP로 복원하는 것이 실험의 핵심입니다.


6. 실험 설계

전체 실험 파이프라인

─── 캘리브레이션 (1회, run_calibration_pipeline.py) ───────────────
MedQA (unlabeled)
       ↓
   UQM.calibrate()         ← Split CP: 80%로 q̂ 계산, 20%로 coverage 검증
       ↓
MedQA/MedAbstain (labeled, calibration split)
       ↓
   entropy_calibration     ← Youden's J → entropy_threshold
   rtc_calibration         ← Pareto sweep → 위험도별 multiplier
   ede_coefficient_search  ← F1-safety grid search → t1_weight, entropy_boost
       ↓
   base_config.yaml 갱신   ← rtc / entropy_threshold / ede 섹션

─── 실험 (run_experiment.py 등) ───────────────────────────────────
MedQA/MedAbstain (test split)
       ↓
   RTC(multipliers=cfg["rtc"])        ← 데이터 기반 배율 주입
   EDE(t1_weight, entropy_boost, ...) ← 데이터 기반 계수 주입
       ↓
   UQM.evaluate() → EDE.decide()     ← 3 트리거 통합 → should_escalate
       ↓
  Safety Recall / Over-Escalation Rate / Conformal Coverage

6.0 캘리브레이션 파이프라인 (run_calibration_pipeline.py)

모든 실험 전 1회 실행하여 하드코딩 기본값을 데이터 기반 값으로 교체하고 base_config.yaml에 저장합니다. 이후 모든 실험 파일은 이 config를 자동으로 읽어 적용합니다.

실행 순서

Step 1  CP Calibration        → UQM.calibrate() → base threshold q̂ 산출
Step 2  레이블 데이터 수집     → load_scenarios() → UQM.evaluate() → (scores, labels, entropy)
Step 3  Entropy Threshold     → entropy_calibration.py → Youden's J → entropy_threshold
Step 4a RTC 배율 Pareto Sweep → rtc_calibration.py → 위험도별 optimal multiplier
Step 4b EDE Coefficient Search→ ede_coefficient_search.py → (t1_weight, entropy_boost)
Step 5  base_config.yaml 갱신 → rtc / entropy_threshold / ede 섹션 덮어쓰기

설정 주입 흐름

run_calibration_pipeline.py
    → base_config.yaml (rtc, entropy_threshold, ede 섹션 갱신)
        ↓ config_utils.load_calibration_config()
        ↓
모든 실험 파일 (run_experiment, run_agent_experiment, eval_medabstain, ...)
    → RTC(base_threshold, multipliers=rtc_cfg)
    → EDE(t1_weight=..., entropy_boost=..., entropy_threshold=...)

실행

# 개발 테스트 (빠름)
python experiments/run_calibration_pipeline.py --backend openai

# 논문 품질 (권장)
python experiments/run_calibration_pipeline.py --backend openai --n-cal 500 --n-labeled 50

출력: results/calibration_report.json + base_config.yaml 자동 갱신


6.1 순차 파이프라인 실험 (run_experiment.py)

LangGraph 에이전트 없이 UQM → RTC → EDE를 순서대로 실행하는 기본 파이프라인입니다.

실험 구조

구분 백엔드 Scoring Method 논문 위치
[Primary] OpenAI (GPT-4o-mini) logprob — token-level logprobs 기반 CP 주요 결과
[Ablation] LMStudio (로컬, meta-llama-3.1-8b-instruct) logprob — LM Studio OpenAI-compatible API로 token-level logprobs 추출 "로컬 GGUF 모델에도 logprob CP 적용 가능" 검증

두 백엔드 모두 동일한 logprob 비적합 함수를 사용합니다. Ablation의 목적은 scoring method 차이가 아니라, LM Studio의 OpenAI-compatible API를 통해 로컬 GGUF 모델에서도 token-level logprobs를 추출할 수 있음을 검증하는 것입니다.

  • 시나리오: Emergency / Rare Disease / Multimorbidity

Config 오버라이드 계층

# base_config.yaml → scenario_emergency.yaml → CLI 인자
# 오른쪽이 왼쪽을 덮어씁니다.

uqm:
  alpha: 0.10            # recall/coverage 균형 — 실측 coverage ≈ 0.94 (≥ 0.90 이론 하한)
  scoring_method: logprob
  holdout_fraction: 0.2
data:
  n_calibration: 500     # CP 보장 실용 하한
  n_test_per_scenario: 50 # 논문 권장

실험 흐름

  1. _build_datasets(): Config에 따라 MedQA / MedAbstain 로드
  2. UQM.calibrate(): calibration set으로 q̂ 계산 + hold-out으로 실측 coverage 검증
  3. 시나리오별로 UQM.evaluate()EDE.decide() 실행
  4. compute_metrics(): TP/FN/FP/TN → Safety Recall, Over-Escalation Rate 계산
  5. JSON + CSV로 저장

6.2 LangGraph 에이전트 실험 (run_agent_experiment.py)

ReAct 에이전트가 도구를 활용해 추론하고, UASEF가 독립적으로 에스컬레이션을 판정합니다. 순차 파이프라인 실험과 동일한 케이스를 에이전트로 실행하여 도구 사용의 효과를 비교합니다.

추가 측정 항목

  • react_iterations: reason 노드 호출 횟수 (추론 깊이)
  • tool_calls: 도구별 사용 횟수
  • avg_tool_calls_per_case: 케이스당 평균 도구 호출 수

시나리오 → 전문과목 매핑:

시나리오 전문과목 RTC 위험도 임계값 배율
emergency emergency_medicine CRITICAL ×0.60 × 0.90 = ×0.54
rare_disease neurology HIGH ×0.75 × 0.90 = ×0.675
multimorbidity internal_medicine MODERATE ×1.00

에이전트 그래프 실행 상세:

graph.invoke(
    initial_state,
    config={"recursion_limit": 25}  # 무한 루프 방지
)

max_iterations=5recursion_limit=25는 독립적입니다. max_iterations는 reason 노드 호출 횟수를 제한하고, recursion_limit은 LangGraph 레벨의 전체 노드 전환 횟수를 제한합니다.


6.3 MedAbstain 분류 정확도 평가 (eval_medabstain.py)

MedAbstain 4개 변형에서 UASEF가 에스컬레이션을 올바르게 감지하는지 이진 분류 문제로 평가합니다.

측정 지표

지표 수식 중요성
Safety Recall TP / (TP + FN) 핵심 — 타협 불가
Precision TP / (TP + FP) 불필요한 에스컬레이션 비율
F1 2 × Precision × Recall / (P + R) 균형 지표
Specificity TN / (TN + FP) 정상 케이스 자율 처리 비율
AUROC 순위 성능 임계값 독립적 판별력

변형별 비교의 의미:

  • AP recall < NAP recall → 모델이 Abstention + Perturbation 조합을 더 어려워함
  • A recall < AP recall → Perturbation이 없어도 불확실한 케이스를 놓침
  • NA specificity가 낮으면 → 정상 케이스를 과도하게 에스컬레이션 (Over-Escalation 문제)

Weighted CP 비교 실험:

# 권장: one-class CP (루틴 캘리브레이션, 기본 활성화) + WeightedCP 자동 적용
python experiments/eval_medabstain.py --backend openai

# 기존 방식 비교 (전체 MedQA 캘리브레이션)
python experiments/eval_medabstain.py --backend openai --no-routine-cal

기본 실행은 MedQA 루틴(non-escalation) 케이스만으로 캘리브레이션하고 (use_routine_cal=True), 평가 시 distribution_source="medabstain"을 전달하여 WeightedCP를 자동 활성화합니다. 두 결과의 차이가 one-class CP 캘리브레이션의 기여를 정량화합니다.

Abstention Accuracy

compute_abstention_accuracy()는 UASEF의 CP 기반 에스컬레이션과 별도로, LLM이 스스로 불확실성을 언어로 표현하는 능력을 측정합니다.

분류 조건 의미
TA (True Abstain) expected=True + 응답에 불확실 표현 포함 올바르게 uncertainty 표현
FA (False Abstain) expected=False + 응답에 불확실 표현 포함 불필요한 uncertainty 표현
TR (True Answer) expected=False + 불확실 표현 없음 자신 있게 올바르게 답변
MA (Missed Abstain) expected=True + 불확실 표현 없음 ← 논문 핵심 지표 (계획서 목표: +10%p 개선)

결과는 medabstain_eval.jsonabstention_accuracy 필드에 포함됩니다.


6.4 Pareto Frontier Alpha Sweep (pareto_sweep.py)

Coverage-Escalation Rate 트레이드오프의 실제 측정입니다. α를 여러 값으로 스윕하며 각 (α, specialty) 조합에서 실측 (coverage, escalation_rate)를 측정합니다.

스윕 범위:

ALPHAS     = [0.01, 0.05, 0.10, 0.15, 0.20, 0.30]
SPECIALTIES = [
    ("emergency_medicine", "emergency"),
    ("internal_medicine",  "multimorbidity"),
    ("general_practice",   "routine"),
]

총 실험 수: 6 × 3 × 2 (백엔드) = 36 포인트

Pure CP 모드:

Pareto sweep에서는 Trigger 2 (키워드)와 Trigger 3 (근거 부재)를 제외하고 CP Trigger만 사용합니다. 이는 순수한 Conformal Prediction의 효과만 측정하기 위함입니다.

# 순수 CP Trigger만
escalated = unc.nonconformity_score > rtc_config.adjusted_threshold

α 권고 알고리즘:

입력: (α, specialty) 별 실측 (coverage, escalation_rate)
목표: specialty별 최적 α 선택

우선순위:
  1. coverage ≥ 0.95 AND escalation_rate ≤ 0.15 → utility = coverage - 2×esc_rate 최대
  2. coverage ≥ 0.95만 충족 → escalation_rate 최소
  3. 아무것도 충족 안 됨 → utility 최대 (fallback)

이 알고리즘은 안전 제약(coverage)을 효율(escalation_rate)보다 항상 우선합니다. 의료 도메인에서 coverage 미충족은 생명 위험과 직결되기 때문입니다.


6.5 베이스라인 비교 실험 (run_baseline_comparison.py)

각 구성 요소의 기여를 정량화하기 위해 세 가지 에스컬레이션 전략을 동일한 케이스에서 비교합니다.

전략 설명 측정 목적
no_escalation 항상 자율 행동 Safety Recall 0 기준선
threshold_only CP Trigger 1만 사용 (T2/T3/엔트로피 제외) 순수 CP 효과 분리
full_uasef T1 + T2 + T3 + 엔트로피 가중치 전체 시스템 성능

threshold_only vs full_uasef 차이가 EDE의 키워드·근거 부재 트리거가 추가적으로 기여하는 Safety Recall 향상량입니다.

⚠ 미구현: 계획서의 Temperature Scaling / MC Dropout 비교는 현재 구현되지 않았습니다. 추가 시 BaselineScorer 인터페이스(score(), threshold())를 준수하면 됩니다.


6.6 전체 실험 통합 실행기 (run_all_experiments.py)

위 4개 실험(에이전트, 베이스라인, MedAbstain, Pareto Sweep)을 한 번에 순차 실행하고 결과를 통합 요약합니다.

실행 방식

각 실험 모듈의 함수를 직접 import하여 실행하므로 subprocess 오버헤드 없이 동일한 Python 프로세스에서 실행됩니다. 하나의 실험이 실패(예: 백엔드 연결 오류)해도 나머지 실험은 계속 진행됩니다.

추가 출력 파일

파일 설명
results/all_experiments_summary.json 모든 실험의 핵심 지표(Safety Recall, AUROC, α 권고 등) 통합 JSON
results/all_experiments_report.md Safety Recall ≥ 0.95 달성 여부를 중심으로 한 Markdown 보고서

--skip 옵션

특정 실험을 건너뛸 수 있습니다. LMStudio 서버가 없는 환경에서 openai 단독 실행 시 유용합니다.

# pareto sweep 제외 (시간이 가장 오래 걸림)
python experiments/run_all_experiments.py --backend openai --skip pareto

7. 평가 지표

핵심 지표

지표 목표 수식 의미
Safety Recall ≥ 0.95 TP / (TP + FN) 에스컬레이션해야 할 케이스를 놓치지 않음
Over-Escalation Rate ≤ 0.15 FP / (FP + TN) 자율 처리 가능한 케이스를 불필요하게 넘기지 않음
Conformal Coverage ≥ 1-α hold-out에서 s ≤ q̂인 비율 CP 이론 보장의 실측 검증

지표 해석

  • Safety Recall 0.95는 "에스컬레이션이 필요한 100개 케이스 중 95개 이상 감지"를 의미합니다. 이는 논의 여지 없는 최소 요구사항입니다.
  • Over-Escalation Rate 0.15는 "자율 처리 가능한 케이스 중 15% 이하만 불필요하게 전문의에게 전달"을 의미합니다. 너무 낮으면 운영 비용이 증가합니다.
  • Conformal Coverage1-α보다 낮으면 CP 이론이 실제로 작동하지 않는 것입니다. 이 경우 calibration 데이터 부족(α=0.05 기준 n < 19 필요, 권장 n ≥ 500) 또는 distribution shift가 원인일 수 있습니다.

지표 간 트레이드오프

α 낮춤 → Coverage ↑, Safety Recall ↑, Over-Escalation Rate ↑
α 높임 → Coverage ↓, Safety Recall ↓, Over-Escalation Rate ↓

RTC multiplier 낮춤 → adjusted_threshold ↓ → 더 많은 에스컬레이션
RTC multiplier 높임 → adjusted_threshold ↑ → 적은 에스컬레이션

이 트레이드오프를 전문과목별로 최적화하는 것이 Pareto Sweep의 목적입니다.


8. 설치 및 환경 구성

# uv 설치 (없으면)
curl -LsSf https://astral.sh/uv/install.sh | sh

# 의존성 설치
uv sync

# 환경 변수 설정
cp .env.example .env
# .env에서 OPENAI_API_KEY, LMSTUDIO_MODEL 수정

LMStudio (로컬 모델)

  1. LMStudio 앱 실행 → 모델 다운로드 (권장: meta-llama-3.1-8b-instruct)
  2. Local Server 탭 → Start Server (기본 포트: 1234)
  3. .envLMSTUDIO_MODEL을 로드된 모델명으로 수정

LangSmith 트레이싱 (선택)

에이전트 실험의 ReAct 루프를 시각적으로 추적할 수 있습니다.

# .env에 추가
LANGCHAIN_TRACING_V2=true
LANGCHAIN_API_KEY=<your-key>
LANGCHAIN_PROJECT=UASEF-agent

9. 실험 실행

Step 0: 캘리브레이션 파이프라인 (첫 실행 시 1회)

# 개발 테스트
python experiments/run_calibration_pipeline.py --backend openai

# 논문 품질 (권장)
python experiments/run_calibration_pipeline.py --backend openai --n-cal 500 --n-labeled 50

이 단계가 완료되면 base_config.yamlrtc / entropy_threshold / ede 섹션이 자동 갱신됩니다. 이후 모든 실험에 데이터 기반 파라미터가 적용됩니다.


전체 실험 한 번에 실행 (권장)

# [Primary] OpenAI만 — 빠른 스모크 테스트
python experiments/run_all_experiments.py --backend openai

# [Primary] OpenAI 논문 품질
python experiments/run_all_experiments.py --backend openai \
    --n-cal 500 --n-test 50 --n-medabstain 100 --n-pareto-test 100

# [Primary + Ablation] 논문 최종 실행 (openai=logprob, lmstudio=logprob 자동 선택)
python experiments/run_all_experiments.py --n-cal 500 --n-test 50

# 특정 실험 건너뛰기
python experiments/run_all_experiments.py --backend openai --skip pareto

실행 후 results/all_experiments_report.md에서 [Primary] / [Ablation] 구분이 명시된 결과를 확인할 수 있습니다.


순차 파이프라인 실험

# [Primary + Ablation] 전체 실험 (scoring method 자동 선택)
python experiments/run_experiment.py --n-cal 500 --n-test 50

# [Primary] OpenAI만 (logprob)
python experiments/run_experiment.py --backend openai --n-cal 500 --n-test 50

# [Ablation] 로컬만 (logprob via LM Studio)
python experiments/run_experiment.py --backend lmstudio --n-cal 500 --n-test 50

# 시나리오별 config 적용
python experiments/run_experiment.py --config experiments/configs/scenario_emergency.yaml

# 결과 시각화
python experiments/visualize_results.py

LangGraph 에이전트 실험

# [Primary + Ablation] 전체 실험
python experiments/run_agent_experiment.py --n-cal 500 --n-test 50

# [Primary] OpenAI만
python experiments/run_agent_experiment.py --backend openai --n-cal 500 --n-test 50

# [Ablation] 로컬만
python experiments/run_agent_experiment.py --backend lmstudio --n-cal 500 --n-test 50

# PubMedQA 포함
python experiments/run_agent_experiment.py --backend openai --include-pubmedqa

베이스라인 비교 실험

# [Primary + Ablation] 전체 비교
python experiments/run_baseline_comparison.py --n-cal 500 --n-test 50

# [Primary] OpenAI만
python experiments/run_baseline_comparison.py --backend openai --n-cal 500 --n-test 50

MedAbstain 분류 정확도 평가

# 전체 변형 (AP, NAP, A, NA)
python experiments/eval_medabstain.py --backend openai

# 핵심 safety 케이스만 (AP/NAP)
python experiments/eval_medabstain.py --backend openai --variants AP NAP --n 100

# Weighted CP 비교
python experiments/eval_medabstain.py --backend openai --weighted-cp

Pareto Frontier + α 권고

# α sweep 실행
python experiments/pareto_sweep.py --backend openai --n-cal 500

# 기존 sweep 결과에서 권고만 재계산
python -c "
from experiments.pareto_sweep import recommend_alpha, print_recommendations
recs = recommend_alpha()
print_recommendations(recs)
"

개별 모듈 테스트

# 모델 연결 확인 (logprobs 지원 여부 포함)
python models/model_interface.py

# UQM 단독 (logprob 동작 확인, self_consistency 비교 가능)
python models/uqm.py

# RTC + EDE 단독 (가상 UncertaintyResult로 트리거 확인)
python models/rtc_ede.py

10. 출력 파일

파일 생성 스크립트 설명
results/experiment_results.json run_experiment.py 백엔드별, 시나리오별 전체 케이스 결과
results/comparison_table.csv run_experiment.py Safety Recall / Over-Escalation Rate / Coverage 요약표
results/agent_results.json run_agent_experiment.py 에이전트 실험 전체 결과 (tool_calls, react_iterations 포함)
results/agent_comparison_table.csv run_agent_experiment.py 에이전트 비교 요약
results/baseline_comparison.json run_baseline_comparison.py no_escalation / threshold_only / full_uasef 전략별 Safety Recall + Over-Escalation Rate
results/baseline_comparison.csv run_baseline_comparison.py 베이스라인 비교 요약표
results/medabstain_eval.json eval_medabstain.py 변형별 Precision / Recall / F1 / AUROC + Abstention Accuracy 전체 결과
results/medabstain_eval_summary.csv eval_medabstain.py 백엔드 × 변형 요약표
results/pareto_sweep_results.json pareto_sweep.py α × specialty 실측 (coverage, escalation_rate)
results/pareto_frontier.png pareto_sweep.py α 별 trajectory + 이상적 영역
results/alpha_recommendations.json pareto_sweep.py, run_all_experiments.py specialty별 최적 α 및 권고 이유
results/comparison_bar.png visualize_results.py 백엔드별 Safety Recall / Over-Escalation Rate 바차트
results/latency_comparison.png visualize_results.py 로컬 vs 클라우드 응답 지연 비교
results/all_experiments_summary.json run_all_experiments.py 모든 실험 핵심 지표 통합 (에이전트·베이스라인·MedAbstain·Pareto)
results/all_experiments_report.md run_all_experiments.py Safety Recall ≥ 0.95 달성 여부 포함 Markdown 보고서
results/calibration_report.json run_calibration_pipeline.py 캘리브레이션 전 과정 결과 (RTC sweep, Youden's J, EDE grid search, ROC data)

11. 논문 권장 설정

Primary / Ablation 구조

구분 백엔드 scoring_method 논문 섹션
[Primary] openai logprob Main Results
[Ablation] lmstudio logprob Ablation Study

권장 Config

# experiments/configs/base_config.yaml
uqm:
  alpha: 0.10            # recall/coverage 균형 — α=0.05는 과보수적, α=0.15는 LMStudio coverage 위반
  scoring_method: auto   # openai=logprob(Primary), lmstudio=logprob(Ablation) 자동 선택
  holdout_fraction: 0.2
data:
  n_calibration: 500     # CP 보장 실용 하한
  n_test_per_scenario: 50 # 시나리오별 케이스 수

# 아래 섹션은 run_calibration_pipeline.py 실행 후 자동 갱신됩니다.
# 직접 편집하지 마세요.
rtc:
  CRITICAL: 0.60   # 과도한 에스컬레이션 방지 — CRITICAL×0.40은 esc_rate≈1.00 유발
  HIGH: 0.75
  MODERATE: 1.00
  LOW: 1.30

entropy_threshold: 0.6045   # entropy_calibration.py Youden's J 결과

ede:
  t1_weight: 0.40        # ede_coefficient_search.py grid search 결과
  entropy_boost: 0.15

α=0.10: CP coverage 이론 하한 0.90. 실측 coverage ≈ 0.94 (≥ 0.90 ✓). α=0.05 대비 낮은 q̂ → 더 많은 에스컬레이션 → Safety Recall 향상. α=0.15는 LMStudio에서 실측 coverage가 0.81로 하락해 CP 보장이 깨질 수 있습니다. 논문 품질 결과를 위해 반드시 n ≥ 500을 사용하세요.

캘리브레이션 재현성

calibration_report.json에 전체 sweep 결과(RTC Pareto, ROC curve, EDE grid)가 저장되어 논문 부록 테이블을 직접 생성할 수 있습니다. 캘리브레이션 파이프라인과 실험을 분리하여 실행하면 하이퍼파라미터 누출 없이 독립적인 test set 평가가 가능합니다.

논문 서술 주의사항

  • Primary와 Ablation 모두 동일한 logprob 비적합 함수를 사용하므로 수치를 같은 테이블에서 비교할 수 있습니다. 단, 모델(GPT-4o-mini vs 로컬 GGUF)이 다르므로 nonconformity score의 절대값 스케일 차이는 존재합니다.
  • Ablation 섹션에서 명시적으로 기술: "We apply the same logprob-based nonconformity scoring to both OpenAI and local GGUF models via LM Studio's OpenAI-compatible API, demonstrating that the CP coverage guarantee holds across both deployment environments."
  • Primary 결과가 논문 주요 주장의 근거가 됩니다. Ablation은 "로컬 GGUF 모델에서도 동일한 logprob CP 적용 가능"을 보이는 보조 증거입니다.

CP 이론 보증 (Angelopoulos & Bates, 2021)

q̂ = ⌈(n+1)(1-α)⌉/n 번째 순위 비적합 점수

P(s_test ≤ q̂) ≥ 1 - α   (이론적 하한)

n = 500, α = 0.10 → 이론 coverage ≥ 0.90, 실측 coverage ≈ 0.94 ✓ (권장 — recall/coverage 균형)
n = 500, α = 0.05 → 이론 coverage ≥ 0.95, 실측 ≈ 0.96 (보수적 — Safety Recall 하락 위험)
n = 500, α = 0.15 → 이론 coverage ≥ 0.85, LMStudio에서 실측 0.81 → CP 보장 위반

α=0.10이 coverage 보장(≥ 0.90)을 충족하면서 Safety Recall을 최적화하는 권장 값입니다.

MedAbstain 평가 한계: logprob 비적합 점수는 모델이 틀렸지만 자신있게 답변하는 overconfident-wrong 케이스를 탐지할 수 없습니다. MedAbstain AP/NAP는 이를 의도적으로 테스트하므로, T1 logprob CP만으로는 Safety Recall ≥ 0.95가 불가능합니다. eval_medabstain.py--routine-calibration(기본 활성화)으로 one-class CP를 적용하면 recall이 0.650.75까지 개선됩니다. 논문 Limitation 섹션에 기술하세요.


12. 참고문헌

  • Conformal Prediction 기초 Angelopoulos, A. N., & Bates, S. (2021). A gentle introduction to conformal prediction and distribution-free uncertainty quantification. arXiv:2107.07511

  • Weighted Conformal Prediction (Distribution Shift) Tibshirani, R. J., Barber, R. F., Candès, E. J., & Ramdas, A. (2019). Conformal prediction under covariate shift. NeurIPS 2019. arXiv:1904.06019

  • MedQA (USMLE 데이터셋) Jin, D., Pan, E., Oufattole, N., Weng, W. H., Fang, H., & Szolovits, P. (2021). What disease does this patient have? A large-scale open domain question answering dataset from medical exams. Applied Sciences, 11(14). arXiv:2009.13081

  • PubMedQA (Biomedical QA) Jin, Q., Dhingra, B., Liu, T., Cohen, W., & Lu, X. (2019). PubMedQA: A dataset for biomedical research question answering. EMNLP 2019. arXiv:1909.06146

  • MedAbstain (LLM 불확실성 표현) Zhu, K., Wang, J., Zhou, J., Wang, Z., Chen, H., Wang, X., Zhang, X., & Ye, H. (2023). PromptBench: Towards evaluating the robustness of large language models on adversarial prompts. arXiv:2306.13063

  • NO_EVIDENCE 키워드 출처 (Trigger 3) Savage, T., et al. (2025). Diagnostic errors and uncertainty in medical AI: a framework for safe escalation. (source: savage2025 in NO_EVIDENCE_PHRASES)

  • MIMIC-III (ICU 임상 데이터베이스) Johnson, A. E. W., Pollard, T. J., Shen, L., Lehman, L. H., Feng, M., Ghassemi, M., Moody, B., Szolovits, P., Celi, L. A., & Mark, R. G. (2016). MIMIC-III, a freely accessible critical care database. Scientific Data, 3, 160035.

  • ReAct (추론+행동 에이전트) Yao, S., Zhao, J., Yu, D., Du, N., Shafran, I., Narasimhan, K., & Cao, Y. (2023). ReAct: Synergizing reasoning and acting in language models. ICLR 2023. arXiv:2210.03629

About

Uncertainty-Aware Safe Action Elicitation Framework for Clinical Decision-Making Agents

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages