k
korAI
고급 전체
🔥 고급2026-05-257~9분

LLM-as-Judge 평가 파이프라인: 편향 제어와 회귀 감지 자동화

Claude를 판정 모델로 사용하는 평가 파이프라인에서 위치 편향·자기편향을 수치로 측정하고, CI에서 품질 회귀를 자동 차단하는 실전 구조를 다룬다.

evaluationllm-judgeci-cd

LLM 판정의 구조적 편향 문제

LLM을 Judge로 쓸 때 두 가지 편향이 반드시 나타난다. 위치 편향(Position Bias): A/B 비교 시 먼저 제시된 응답을 선호하는 경향으로, 연구에 따르면 편향 미보정 시 약 15~25%의 판정이 위치에 의해 결정된다. 자기편향(Self-Enhancement Bias): Claude가 Claude 생성 텍스트를 다른 모델 출력보다 높게 평가하는 경향으로, 동일 품질 기준에서 약 8~12% 과대 평가가 관찰된다. 이를 보정하지 않으면 평가 파이프라인 자체가 무결한 것처럼 보여도 실제 사용자 만족도와 상관이 낮아진다.

편향 보정 패턴: 교차 판정과 앙상블

import anthropic
import asyncio
from statistics import mean, stdev

JUDGE_PROMPT = """
다음 두 응답 중 어느 것이 더 정확하고 유용한지 평가하세요.
[응답 1]: {response_a}
[응답 2]: {response_b}

점수를 1~5로 각각 매기고 JSON으로 출력하세요: {{"score_1": N, "score_2": N, "reasoning": "..."}}
"""

async def judge_pair(client, response_a: str, response_b: str) -> dict:
    import json
    # 순서 스왑으로 위치 편향 측정
    async def call(a, b):
        r = await client.messages.create(
            model="claude-opus-4-5",
            max_tokens=256,
            messages=[{"role": "user", "content": JUDGE_PROMPT.format(response_a=a, response_b=b)}],
        )
        return json.loads(r.content[0].text)

    normal = await call(response_a, response_b)   # A→B
    swapped = await call(response_b, response_a)   # B→A (스왑)

    # 스왑 후 score_1이 원래 B의 점수이므로 역변환
    score_a = mean([normal["score_1"], swapped["score_2"]])
    score_b = mean([normal["score_2"], swapped["score_1"]])
    position_bias = abs(normal["score_1"] - swapped["score_2"])  # 0에 가까울수록 좋음

    return {"score_a": score_a, "score_b": score_b, "position_bias": position_bias}

async def run_evaluation_suite(test_cases: list[dict]) -> dict:
    client = anthropic.AsyncAnthropic()
    tasks = [judge_pair(client, tc["baseline"], tc["candidate"]) for tc in test_cases]
    results = await asyncio.gather(*tasks)
    wins = sum(1 for r in results if r["score_b"] > r["score_a"])
    avg_bias = mean(r["position_bias"] for r in results)
    return {"win_rate": wins / len(results), "avg_position_bias": avg_bias, "n": len(results)}

위치 편향 지표(position_bias)가 0.5 이상이면 해당 테스트 케이스의 판정을 무효 처리하고 재판정한다. 앙상블 크기를 늘릴수록 정밀도는 높아지지만 비용도 선형 증가하므로, 일반적으로 스왑 2회 + 독립 판정 1회(총 3콜) 구성이 비용/신뢰도 최적점이다.

CI 통합과 회귀 차단

평가는 개발 루프에 통합되어야 의미가 있다. 기준선(baseline)은 고정하고, 새 프롬프트 버전의 win_rate가 기준선 대비 5% 이상 하락하면 PR 머지를 차단한다. 통계적 유의성 보장을 위해 최소 샘플 크기는 30개 테스트 케이스 이상이어야 한다(이하에서는 분산이 너무 커 오탐률이 높다).

운영 체크리스트

  • [ ] 모든 평가 결과를 테스트 케이스 ID, 타임스탬프, 모델 버전과 함께 DB에 영구 저장
  • [ ] position_bias > 0.5인 케이스 비율을 대시보드에 노출 (5% 초과 시 알림)
  • [ ] 주 1회 동일 테스트셋으로 Judge 모델 자체의 점수 분포 드리프트 모니터링
  • [ ] win_rate 신뢰구간(95% CI)을 함께 리포트, 포인트 추정값만 보고하지 않기
  • [ ] 자기편향 측정을 위해 분기 1회 Claude 외 Judge 모델(GPT-4o 등)과 교차 검증