AI Agent

LLM as a Judge 완전정리 4편 — G-Eval vs Prometheus 2 vs PAJAMA vs Themis: 무엇을 쓸 것인가

cell-devlog 2026. 5. 26. 15:08
반응형

LLM-as-a-Judge 프레임워크는 지금 빠르게 갈라지고 있습니다. API 프론티어 판사(GPT-4o, Claude Opus)에 프롬프트를 얹는 방향이 있고, 오픈소스로 파인튜닝한 전용 판사 모델 방향이 있고, 아예 API 호출 없이 코드로 판사를 합성하는 방향이 있습니다. 각 방향의 대표 프레임워크는 G-Eval, Prometheus 2, PAJAMA, Themis입니다. 그리고 증류된 판사(Distilled Judge)가 이 모두를 아우르는 비용 최적화 전략으로 자리잡았습니다. 무엇이 언제 맞는지, 각 프레임워크의 원리·구현·한계를 실전 코드와 함께 정리합니다.


 

4편이 다루는 것 → G-Eval (Liu et al. 2023, EMNLP) — CoT + Form-filling + 확률 가중 점수의 작동 원리 → Prometheus 2 (Kim et al. 2024, EMNLP) — DA/PWC 통합 오픈소스 판사, 가중치 병합 방법 → PAJAMA (Huang et al. 2025, ICML WS) — 프로그램 합성 판사, 3500× 비용 절감 원리 → Themis (Hu et al. 2024, EMNLP) — 시나리오 의존 프롬프트, 참조 없는 NLG 평가 → 증류된 판사 파이프라인 — 대형 판사의 판정을 소형 모델에 전수 → 프레임워크 선택 기준 — 태스크·비용·제어 수준·도메인에 따른 결정 트리


왜 프레임워크가 필요한가

"GPT-4에 '이 응답 1~10점으로 평가해줘'라고 하면 되지 않나?"

될 때도 있습니다. 하지만 이렇게 하면 세 가지 문제가 동시에 발생합니다. 점수가 어떤 기준에서 나왔는지 알 수 없고(불투명성), 같은 프롬프트에 같은 응답을 넣어도 점수가 달라지고(불안정성), 6개월 뒤 GPT 버전이 바뀌면 이전 점수와 비교할 수 없게 됩니다(캘리브레이션 표류).

프레임워크는 이 세 문제를 체계적으로 해결하기 위한 구조화된 방법입니다. G-Eval은 CoT 추론과 확률 기반 점수화를 결합해 기존 참조 기반 지표보다 인간 판단과의 상관성을 크게 높였습니다. Prometheus 2는 GPT-4의 세밀한 평가 능력을 오픈소스 모델에 증류해, 투명성·제어성·비용 문제를 동시에 해결하려 했습니다.


1. G-Eval — 가장 유연한 프레임워크

출처: Liu et al. 2023, EMNLP ("NLG Evaluation using GPT-4 with Better Human Alignment")

핵심 아이디어 세 가지:

G-Eval은 세 주요 컴포넌트로 구성됩니다. 사용자가 "태스크 소개"와 "평가 기준"을 정의한 프롬프트, 이를 받아 상세한 "평가 단계"를 생성하는 자동 CoT, 그리고 점수화 함수입니다.

① 자동 CoT 생성

사용자가 기준을 자연어로 정의하면, 판사 LLM이 평가 단계를 자동으로 생성합니다. 사용자가 단계를 직접 설계하지 않아도 됩니다.

from anthropic import Anthropic
import json

client = Anthropic()

# 1단계: 평가 기준 → 평가 단계 자동 생성
STEP_GENERATION_PROMPT = """당신은 NLG 출력 품질 평가 전문가입니다.

다음 평가 기준을 바탕으로, LLM 응답을 평가하기 위한
구체적인 단계별 절차를 생성하세요.

[태스크]
{task_description}

[평가 기준]
{criteria}

요구사항:
- 각 단계는 명확하고 실행 가능해야 합니다
- 단계는 순서대로 수행됩니다
- 각 단계는 판단의 근거가 됩니다
- 4~7개 단계로 구성하세요

JSON으로만 출력:
{{"evaluation_steps": ["단계1", "단계2", ...]}}
"""

def generate_eval_steps(
    task_description: str,
    criteria: str,
    judge_model: str = "claude-sonnet-4-6",
) -> list[str]:
    """기준에서 평가 단계 자동 생성 (1회 실행, 재사용)"""
    raw = client.messages.create(
        model=judge_model,
        max_tokens=512,
        messages=[{"role": "user", "content": STEP_GENERATION_PROMPT.format(
            task_description=task_description,
            criteria=criteria,
        )}]
    ).content[0].text.strip()
    return json.loads(raw)["evaluation_steps"]


# 사용 예시: 요약 품질 평가용 단계 생성
steps = generate_eval_steps(
    task_description="뉴스 기사 자동 요약 시스템 평가",
    criteria="일관성: 요약이 원문과 사실적으로 일치하는가? 원문에 없는 내용을 추가하지 않았는가?",
)
# → ['1. 원문의 핵심 사실을 파악한다', '2. 요약에서 각 주장을 확인한다', ...]

② Form-filling 평가

생성된 단계를 이용해 응답을 평가합니다. "빈칸 채우기"처럼 각 단계를 수행한 결과를 채워나갑니다.

# 2단계: 생성된 단계로 실제 평가
G_EVAL_SCORING_TEMPLATE = """다음 단계에 따라 응답의 품질을 평가하세요.

[소스 텍스트]
{source}

[평가할 요약]
{response}

[평가 단계]
{steps_formatted}

각 단계를 순서대로 수행한 뒤, 1-5점으로 최종 점수를 부여하세요.

점수 기준:
1: 기준을 전혀 충족하지 못함
2: 기준을 약간 충족
3: 기준을 보통 수준으로 충족
4: 기준을 잘 충족
5: 기준을 완벽하게 충족

JSON으로만 출력:
{{"step_analyses": [{{"step": "단계명", "finding": "관찰 내용"}}, ...],
  "score": 1-5,
  "justification": "2-3문장 근거"}}
"""

def g_eval_score(
    source: str,
    response: str,
    eval_steps: list[str],
    judge_model: str = "claude-opus-4-7",
    use_prob_weighting: bool = False,
) -> dict:
    """
    G-Eval 핵심 평가 함수.
    use_prob_weighting=True: 토큰 확률 가중 점수 (API 지원 시)
    """
    steps_formatted = "\n".join(
        f"{i+1}. {step}" for i, step in enumerate(eval_steps)
    )

    raw = client.messages.create(
        model=judge_model,
        max_tokens=768,
        messages=[{"role": "user", "content": G_EVAL_SCORING_TEMPLATE.format(
            source=source,
            response=response,
            steps_formatted=steps_formatted,
        )}]
    ).content[0].text.strip()

    result = json.loads(raw)

    # use_prob_weighting=True일 때:
    # 각 점수(1~5)의 토큰 로그 확률을 가중 평균하면
    # 단일 정수 샘플링보다 분산이 낮아짐
    # Anthropic API는 현재 logprobs를 직접 노출하지 않으므로
    # 실용적으로는 정수 점수를 여러 번 샘플링해 평균내는 방식 사용

    return result

③ 확률 가중 점수화

CoT 단계를 포함하면 Spearman ρ가 2~3포인트 향상되고, 확률 가중 점수화는 분산을 줄이고 의미 판별력을 높입니다. Anthropic API가 logprobs를 직접 노출하지 않으므로, 실용적으로는 여러 번 샘플링 후 평균을 냅니다.

def g_eval_multi_sample(
    source: str,
    response: str,
    eval_steps: list[str],
    judge_model: str = "claude-sonnet-4-6",
    n_samples: int = 5,
) -> dict:
    """
    확률 가중 점수화의 실용적 대안: 다중 샘플링 후 평균.
    단일 샘플보다 분산이 낮아집니다.
    """
    scores = []
    for _ in range(n_samples):
        result = g_eval_score(source, response, eval_steps, judge_model)
        scores.append(result["score"])

    return {
        "mean_score": round(sum(scores) / len(scores), 2),
        "std": round(float(__import__("statistics").stdev(scores)), 2),
        "scores": scores,
        "stable": max(scores) - min(scores) <= 1,  # 최대 1점 차이면 안정적
    }

G-Eval 완전 파이프라인

class GEvalPipeline:
    """G-Eval 전체 파이프라인: 단계 생성 → 캐싱 → 평가"""

    def __init__(self, task: str, criteria: dict[str, str]):
        """
        criteria: {"차원명": "기준 설명", ...}
        예: {"coherence": "요약이 원문과 사실적으로 일치하는가?",
             "conciseness": "불필요한 내용 없이 핵심만 담았는가?"}
        """
        self.task = task
        self.criteria = criteria
        self._cached_steps: dict[str, list[str]] = {}

    def get_eval_steps(self, dimension: str) -> list[str]:
        """평가 단계 캐싱 — 동일 기준은 한 번만 생성"""
        if dimension not in self._cached_steps:
            self._cached_steps[dimension] = generate_eval_steps(
                task_description=self.task,
                criteria=self.criteria[dimension],
            )
        return self._cached_steps[dimension]

    def evaluate(
        self,
        source: str,
        response: str,
        judge_model: str = "claude-opus-4-7",
    ) -> dict:
        results = {}
        for dim, _ in self.criteria.items():
            steps = self.get_eval_steps(dim)
            score_result = g_eval_score(source, response, steps, judge_model)
            results[dim] = score_result["score"]

        overall = round(sum(results.values()) / len(results), 2)
        return {"dimension_scores": results, "overall": overall}


# 사용
pipeline = GEvalPipeline(
    task="뉴스 기사 요약 품질 평가",
    criteria={
        "coherence":   "요약이 원문과 사실적으로 일치하는가?",
        "conciseness": "불필요한 내용 없이 핵심만 담았는가?",
        "fluency":     "자연스럽고 읽기 쉬운 문장인가?",
    },
)

result = pipeline.evaluate(
    source="[원문 뉴스 기사]",
    response="[평가할 요약]",
)

G-Eval 한계: G-Eval은 가장 다재다능한 유형의 지표로, 거의 모든 사용 사례를 인간 수준의 정확도로 평가할 수 있습니다. 단, 주관적이고 사용 사례 특화 평가에 가장 적합합니다. 반면 API 비용이 높고, 판사 모델 업데이트 시 캘리브레이션 표류가 발생하며, 단계 생성 자체가 판사 모델의 품질에 의존합니다.


2. Prometheus 2 — 오픈소스 전용 판사

출처: Kim et al. 2024, EMNLP ("Prometheus 2: An Open Source Language Model Specialized in Evaluating Other Language Models")

핵심 아이디어: Prometheus 2는 GPT-4의 세밀한 평가 능력을 에뮬레이션하기 위해 파인튜닝됩니다. 직접 평가(DA)에는 Feedback Collection 데이터셋으로, 페어와이즈 비교(PWC)에는 Preference Collection 데이터셋으로 각각 별도 모델을 훈련한 뒤 가중치 병합으로 단일 모델을 만듭니다.

from prometheus_eval import PrometheusEval
from prometheus_eval.prompts import ABSOLUTE_PROMPT, RELATIVE_PROMPT

# Hugging Face에서 모델 로드 (로컬 실행, API 비용 없음)
# pip install prometheus-eval
judge = PrometheusEval(
    model_id="prometheus-eval/prometheus-7b-v2.0",
    absolute_grade_template=ABSOLUTE_PROMPT,
    relative_grade_template=RELATIVE_PROMPT,
)

# ── 절대 평가 (Direct Assessment) ──────────────────
rubric_da = """
점수 기준:
1: 사실 오류가 있거나 질문과 무관한 응답
2: 부분적으로 정확하지만 중요한 내용 누락
3: 정확하고 관련 있지만 완전하지 않음
4: 정확하고 완전하며 유용함
5: 정확하고 완전하며 탁월하게 유용함
"""

instructions = ["Python에서 리스트를 정렬하는 방법은?"]
responses    = ["sorted() 함수나 .sort() 메서드를 사용합니다..."]
ref_answers  = ["Python에서 리스트를 정렬하는 방법은 두 가지입니다..."]

feedbacks, scores = judge.absolute_grade(
    instructions=instructions,
    responses=responses,
    rubric=rubric_da,
    reference_answers=ref_answers,   # 선택적 — 없어도 동작
)
# scores: [4]  feedbacks: ["응답이 두 가지 방법을 모두 언급했지만..."]

# ── 페어와이즈 비교 (Pairwise Comparison) ──────────
response_a = "sorted() 함수를 사용합니다."
response_b = "sorted()와 .sort()의 차이를 설명하면..."

feedbacks_pw, winners = judge.relative_grade(
    instructions=instructions,
    responses_A=[response_a],
    responses_B=[response_b],
    rubric=rubric_da,
    reference_answers=ref_answers,
)
# winners: ["B"]  → response_b가 더 나은 응답

가중치 병합의 의미

Prometheus 2의 BiGGen-Bench 버전은 Claude-3-Opus보다 절대 평가 태스크에서 높은 성능을 달성했습니다. 가중치 병합이 단순 공동 훈련보다 나은 결과를 내는 이유는, 두 태스크(DA, PWC)의 특성이 달라 각각 독립적으로 최적화한 뒤 합치는 것이 더 효과적이기 때문입니다.

# Prometheus 2 모델 선택 기준
PROMETHEUS_MODELS = {
    "prometheus-7b-v2.0": {
        "size": "7B",
        "vram": "~14GB",
        "use_case": "일반 목적, 중간 속도",
        "pearson_r_with_gpt4": "0.60~0.65",
    },
    "prometheus-8x7b-v2.0": {
        "size": "8×7B MoE",
        "vram": "~95GB (4-bit 양자화 시 ~48GB)",
        "use_case": "최고 성능, 고급 평가",
        "pearson_r_with_gpt4": "0.65~0.70",
    },
    "prometheus-bgb-8x7b-v2.0": {
        "size": "8×7B (BiGGen-Bench 특화)",
        "vram": "~95GB",
        "use_case": "다양한 능력 평가, Claude-3-Opus 수준",
        "pearson_r_with_gpt4": "최고",
    },
}

Prometheus 2 한계: 영어 중심 훈련 데이터, 도메인 일반화의 한계(BiGGen-Bench 외 태스크에서 성능 저하), GPU 요구사항(7B도 14GB VRAM), 그리고 훈련 데이터가 GPT-4 판정을 증류한 것이므로 GPT-4의 편향을 일부 상속합니다.


3. PAJAMA — 프로그램이 판사가 될 때

출처: Huang et al. 2025, ICML Workshop ("Time To Impeach LLM-as-a-Judge: Programs are the Future of Evaluation", arXiv:2506.10403)

이것은 패러다임 전환입니다. LLM에게 "이 응답을 평가하라"고 하는 대신, LLM에게 "이 기준을 평가하는 코드를 작성하라"고 합니다. 작성된 코드를 로컬에서 실행합니다.

PAJAMA는 판단 일관성을 평균 15.83% 개선하고 편향된 응답을 평균 23.7% 줄였습니다. 프로그램 판정을 모델로 증류하면 RewardBench의 CHAT-HARD 서브셋에서 Prometheus 대비 2.19%, JudgeLM 데이터셋 대비 8.67% 더 높은 성능을 달성하면서 비용은 약 3500배 절감합니다.

작동 원리

# PAJAMA 파이프라인
# 1단계: LLM으로 판사 프로그램(코드) 합성
# 2단계: 합성된 프로그램들을 약한 감독(Weak Supervision)으로 앙상블
# 3단계: 로컬 실행

# ── 1단계: 판사 프로그램 합성 ──────────────────────

PROGRAM_SYNTHESIS_PROMPT = """다음 평가 기준에 따라 LLM 응답을 평가하는
Python 함수를 작성하세요.

[평가 기준]
{criteria}

요구사항:
- 함수 시그니처: def judge(question: str, response: str) -> dict
- 반환값: {{"score": float(0~1), "reason": str, "passed": bool}}
- 외부 API 호출 없이 순수 Python 로직만 사용
- 평가 로직을 명확하고 감사 가능한 방식으로 구현
- 다음 기준 중 하나에 집중: 정확성/관련성/완전성/안전성/형식준수

Python 코드만 출력 (함수 정의만):"""

def synthesize_judge_program(
    criteria: str,
    judge_model: str = "claude-opus-4-7",
) -> str:
    """평가 기준을 실행 가능한 Python 함수로 변환"""
    raw = client.messages.create(
        model=judge_model,
        max_tokens=1024,
        messages=[{"role": "user", "content": PROGRAM_SYNTHESIS_PROMPT.format(
            criteria=criteria
        )}]
    ).content[0].text.strip()

    # 코드 블록 제거 (```python ... ```)
    if raw.startswith("```"):
        raw = raw.split("```")[1]
        if raw.startswith("python"):
            raw = raw[6:]
    return raw.strip()


# 사용 예시: 3개 기준에 대해 3개 프로그램 합성
criteria_list = [
    "응답이 질문에 직접적으로 답하는가?",
    "응답에 사실 오류나 오해의 소지가 있는 내용이 없는가?",
    "응답이 유해하거나 편향된 내용을 포함하지 않는가?",
]

judge_programs = []
for criteria in criteria_list:
    program_code = synthesize_judge_program(criteria)
    judge_programs.append(program_code)
# ── 2단계: 프로그램 실행 + 약한 감독 앙상블 ──────────

import ast
import traceback

def execute_judge_program(
    program_code: str,
    question: str,
    response: str,
) -> dict | None:
    """합성된 판사 프로그램 안전 실행"""
    try:
        # 안전성 검사: ast로 위험한 호출 탐지
        tree = ast.parse(program_code)
        for node in ast.walk(tree):
            if isinstance(node, ast.Call):
                func_name = ""
                if isinstance(node.func, ast.Attribute):
                    func_name = node.func.attr
                elif isinstance(node.func, ast.Name):
                    func_name = node.func.id
                # 외부 I/O, 시스템 호출 차단
                if func_name in {"open", "exec", "eval", "__import__",
                                 "subprocess", "os", "sys"}:
                    return None

        # 함수 정의 실행
        namespace = {}
        exec(program_code, namespace)

        if "judge" not in namespace:
            return None

        result = namespace["judge"](question, response)
        return result

    except Exception:
        return None   # 실행 실패 시 이 프로그램 무시


def pajama_ensemble(
    question: str,
    response: str,
    judge_programs: list[str],
    weights: list[float] | None = None,
) -> dict:
    """
    여러 판사 프로그램의 결과를 약한 감독으로 앙상블.
    weights: 각 프로그램의 신뢰도 가중치 (None이면 균등)
    """
    results = []
    for i, program in enumerate(judge_programs):
        result = execute_judge_program(program, question, response)
        if result and "score" in result:
            w = weights[i] if weights else 1.0
            results.append((result, w))

    if not results:
        return {"score": 0.5, "confidence": "low", "n_programs": 0}

    # 가중 평균
    total_weight = sum(w for _, w in results)
    weighted_score = sum(r["score"] * w for r, w in results) / total_weight

    return {
        "score": round(weighted_score, 3),
        "confidence": "high" if len(results) >= 3 else "medium",
        "n_programs_executed": len(results),
        "n_programs_total": len(judge_programs),
        "individual_scores": [r["score"] for r, _ in results],
    }

PAJAMA 비용 비교

60,000개 예시 기준: PAJAMA는 $0.053, LLM-as-a-Judge는 $133~$184입니다.

60K 평가 기준 비용 비교:
LLM-as-a-Judge (API):  $133~$184
PAJAMA (프로그램):     $0.053
비율:                  ~3,500배 차이

왜 이렇게 싼가?
1. 프로그램 합성은 한 번만 (재사용)
2. 실행은 로컬 Python 인터프리터 (API 호출 없음)
3. 앙상블도 로컬 실행 (CPU만 필요)

PAJAMA 한계: 프로그램으로 표현할 수 없는 평가 기준(창의성, 미묘한 어조)에는 적합하지 않습니다. 합성된 코드의 정확성 검증이 필요하며, 반복적인 기준 재사용이 발생하는 문제를 6가지 다양성 기준으로 완화하지만 완전히 해결하지 못합니다. 수학·코드·안전성 같이 체크 가능한 기준에 가장 강력합니다.


4. Themis — 시나리오 의존 평가

출처: Hu et al. 2024, EMNLP ("Themis: A Reference-free NLG Evaluation Language Model with Flexibility and Interpretability")

기존 접근법들은 GPT-4 같은 독점 모델에 의존하거나(비용·재현 불가), 참조 답안 의존·유연성 부족·낮은 해석성 문제를 가집니다. Themis는 이 두 가지 한계를 동시에 해결하는 참조 없는 NLG 평가 모델입니다.

핵심 차별점: 시나리오 의존 프롬프트

Themis는 시나리오 의존적 평가 프롬프트와 두 가지 통제 지시 생성 방법을 포함하며, 교사 모델로부터 평가 능력을 효과적으로 증류하면서 지속 개발의 유연성을 유지합니다.

# Themis는 9개 NLG 태스크 × 여러 평가 차원에 맞는
# 시나리오별 프롬프트 템플릿을 내장합니다

THEMIS_SCENARIO_PROMPTS = {
    "summarization": {
        "coherence": """[요약 일관성 평가]
원문: {source}
요약: {response}

원문의 사실과 요약 내용을 대조해 일관성을 평가합니다.
1. 원문의 핵심 사실들을 파악합니다.
2. 요약의 각 주장이 원문과 일치하는지 확인합니다.
3. 원문에 없는 내용이 추가됐는지 확인합니다.
점수(1-5)와 근거를 JSON으로: {{"score": N, "rationale": "..."}}""",
        "consistency": "...",
    },
    "dialogue": {
        "engagement": """...""",
        "naturalness": """...""",
    },
    "story_generation": {
        "coherence": """...""",
        "creativity": """...""",
    },
    # 9개 태스크 전체...
}

def themis_evaluate(
    task: str,
    dimension: str,
    source: str,
    response: str,
    judge_model: str = "claude-sonnet-4-6",
) -> dict:
    """시나리오에 맞는 프롬프트로 평가"""
    if task not in THEMIS_SCENARIO_PROMPTS:
        raise ValueError(f"지원하지 않는 태스크: {task}")
    if dimension not in THEMIS_SCENARIO_PROMPTS[task]:
        raise ValueError(f"태스크 {task}에서 {dimension} 미지원")

    prompt = THEMIS_SCENARIO_PROMPTS[task][dimension].format(
        source=source, response=response
    )

    raw = client.messages.create(
        model=judge_model, max_tokens=256,
        messages=[{"role": "user", "content": prompt}]
    ).content[0].text.strip()

    return json.loads(raw)

Themis는 Alibaba Cloud API로 서비스되며, Python OpenAI SDK와 호환됩니다. 데이터, 벤치마크, 모델 체크포인트가 오픈소스로 공개됩니다.

Themis vs Prometheus 2 선택 기준

항목 Prometheus 2 Themis

훈련 데이터 Feedback/Preference Collection NLG-Eval (58개 데이터셋, 0.5M 샘플)
강점 커스텀 루브릭 자유도 NLG 9개 태스크 특화
참조 답안 강력 권장 불필요 (참조 없이 동작)
해석성 CoT 피드백 제공 시나리오 프롬프트로 명확한 근거
API 서비스 HuggingFace 모델 Alibaba Cloud API

5. 증류된 판사 파이프라인 — 비용과 성능의 균형

네 프레임워크 중 어느 것을 선택하든, 프로덕션 규모에서 대형 API 판사(Opus 4.7, GPT-5.5)를 매 요청마다 쓰는 것은 비용이 지속 불가능합니다. **증류된 판사(Distilled Judge)**는 이 문제의 현실적 해결책입니다.

원리: 대형 판사의 판정 데이터로 소형 모델을 파인튜닝해, 대형 판사 수준의 판정 능력을 소형 모델에 이식합니다.

# ── 증류 데이터 수집 파이프라인 ──────────────────

def collect_distillation_data(
    samples: list[dict],              # [{"question": ..., "response": ...}, ...]
    teacher_judge: str = "claude-opus-4-7",
    eval_steps: list[str] = None,
    n_samples_per_item: int = 3,      # 안정성을 위해 3회 평균
) -> list[dict]:
    """
    교사 판사의 판정을 수집해 증류 훈련 데이터 생성.
    목표: 2,000~10,000개 판정 수집
    """
    distillation_data = []

    for sample in samples:
        scores = []
        rationales = []

        for _ in range(n_samples_per_item):
            result = g_eval_score(
                source=sample.get("context", ""),
                response=sample["response"],
                eval_steps=eval_steps or [],
                judge_model=teacher_judge,
            )
            scores.append(result["score"])
            rationales.append(result.get("justification", ""))

        # 안정적인 판정만 수집 (표준편차 낮은 것)
        import statistics
        if len(set(scores)) > 1:
            std = statistics.stdev(scores)
            if std > 0.8:   # 불안정한 판정 제외
                continue

        distillation_data.append({
            "question": sample["question"],
            "response": sample["response"],
            "teacher_score": round(sum(scores) / len(scores), 1),
            "teacher_rationale": rationales[0],  # 대표 근거
            "score_std": round(statistics.stdev(scores), 2) if len(scores) > 1 else 0,
            "teacher_model": teacher_judge,
        })

    return distillation_data


# ── 판사 계층 구조 ────────────────────────────────

JUDGE_HIERARCHY = {
    "gold": {
        "model": "claude-opus-4-7",
        "cost_per_1m": 5.00,
        "use_case": "황금 세트 캘리브레이션, 고위험 평가, 증류 교사",
        "throughput": "느림",
    },
    "standard": {
        "model": "claude-sonnet-4-6",
        "cost_per_1m": 3.00,
        "use_case": "일반 품질 모니터링, A/B 테스트",
        "throughput": "보통",
    },
    "fast": {
        "model": "claude-haiku-4-5-20251001",
        "cost_per_1m": 0.25,
        "use_case": "대량 스크리닝, CI 게이트",
        "throughput": "빠름",
    },
    "local": {
        "model": "prometheus-7b-v2.0 (로컬)",
        "cost_per_1m": 0,
        "use_case": "최대 처리량, 비용 민감 환경",
        "throughput": "GPU 의존",
    },
}

def route_judge(
    task_importance: str,   # "critical" | "standard" | "bulk"
    requires_explanation: bool = False,
) -> str:
    if task_importance == "critical" or requires_explanation:
        return JUDGE_HIERARCHY["gold"]["model"]
    elif task_importance == "standard":
        return JUDGE_HIERARCHY["standard"]["model"]
    else:
        return JUDGE_HIERARCHY["fast"]["model"]

6. 프레임워크 선택 기준 — 결정 트리

[질문 1] 참조 답안이 있는가?
  → 있음: Prometheus 2 (참조 활용 시 최고 성능)
  → 없음: 다음 질문

[질문 2] 특정 NLG 태스크(요약/대화/스토리)인가?
  → 예: Themis (태스크 특화 프롬프트)
  → 아니오: 다음 질문

[질문 3] 평가 기준이 코드로 표현 가능한가?
  (수학, 코드 정확성, 형식 준수, 안전성 등)
  → 예: PAJAMA (3500× 비용 절감)
  → 아니오: 다음 질문

[질문 4] GPU 인프라가 있는가?
  → 있음: Prometheus 2 로컬 (API 비용 없음)
  → 없음: 다음 질문

[질문 5] 평가 기준이 자주 바뀌는가?
  → 예: G-Eval (단계 자동 생성, 유연성 최고)
  → 아니오: G-Eval 또는 Prometheus 2 API

7. 프레임워크별 수치 비교

프레임워크 인간 일치율 비용 참조 필요 강점 약점

G-Eval Spearman 0.514 (요약) API 호출 선택적 유연성, 어떤 기준이든 캘리브레이션 표류
Prometheus 2 Pearson 0.60~0.70 with GPT-4 무료 (로컬) 권장 오픈소스, 재현성 GPU 필요, 영어 중심
PAJAMA Consistency +15.83% $0.053/60K 불필요 비용, 감사 가능성 코드화 불가 기준
Themis NLG 태스크 고성능 API 불필요 태스크 특화 범위 제한적
Distilled Judge 교사 수준 근접 0 (로컬) 교사 필요 비용·속도 초기 증류 비용

✅ 4편 정리

"원시 판사 점수는 캘리브레이션 없이 크로스 스터디나 시간 비교에 직접 사용해선 안 됩니다. 점수는 표류합니다." 이것이 프레임워크를 선택하는 것보다 더 중요한 원칙입니다.

프레임워크 언제 쓰나

G-Eval 기준이 자주 바뀌고 참조가 없는 오픈엔드 평가
Prometheus 2 재현성·비용·투명성이 모두 중요한 팀
PAJAMA 코드로 검증 가능한 기준, 대규모 배치
Themis 요약·대화 등 NLG 태스크 특화
Distilled Judge 프로덕션 규모, 속도·비용이 주 제약

선택보다 중요한 것은 어떤 프레임워크를 쓰더라도 황금 세트 + Cohen's κ + 캘리브레이션 주기를 갖추는 것입니다. 5편에서 이것을 구체적으로 다룹니다.

 

 


LLM-as-a-Judge 완전정리 시리즈 — 완결

  1.  1편 — 왜 기존 지표는 죽었고, 세 패러다임은 무엇인가 https://cell-devlog.tistory.com/265
  2.  2편 — 판사는 어디서 거짓말하나: 7가지 편향 해부 https://cell-devlog.tistory.com/266
  3.  3편 — 편향 잡는 법: Position Swap부터 Cross-family까지 https://cell-devlog.tistory.com/267
  4.  4편 — G-Eval vs Prometheus 2 vs PAJAMA vs Themis https://cell-devlog.tistory.com/268 
  5.  5편 — 판사를 평가하기: Cohen's κ, Bradley-Terry, 황금 세트 설계 https://cell-devlog.tistory.com/269
  6.  6편 — 프로덕션 파이프라인: 샘플링·CI 게이트·캘리브레이션 주기 https://cell-devlog.tistory.com/270
  7.  7편 — 한계와 대안: LLM 판사가 절대 못 하는 것들 https://cell-devlog.tistory.com/271

 

반응형