LLM을 심판(Judge)로 정렬하기
LLM을 심판(Judge)로 정렬하기 (How to Align an LLM as a Judge)
이 가이드에서는 Ragas를 사용해서 LLM-as-judge 메트릭을 인간 전문가의 판단과 체계적으로 평가하고 정렬하는 방법을 배워요. 재사용 가능한 평가 파이프라인을 구축하고, 심판과 인간 라벨 사이의 불일치 패턴을 분석하며, 전문가 결정과의 정렬을 개선하도록 심판 프롬프트를 반복 개선할 수 있어요.
출처: 문서
본문
이 가이드에서는 Ragas를 사용해서 LLM-as-judge 메트릭을 인간 전문가의 판단과 체계적으로 평가하고 정렬하는 방법을 배워요.
- 심판 정렬을 위한 재사용 가능한 평가 파이프라인 구축
- 심판과 인간 라벨 사이의 불일치 패턴 분석
- 전문가 결정과의 정렬을 개선하기 위해 심판 프롬프트 반복
왜 먼저 LLM 심판을 정렬해야 할까?
평가 실험을 실행하기 전에 LLM 심판을 자신의 특정 사용 사례에 맞게 정렬하는 것이 중요해요. 잘못 정렬된 심판은 방향이 틀린 나침반과 같아요. 그 지침을 기반으로 하는 모든 개선은 목표에서 더 멀어지게 하죠. 심판을 전문가 판단에 맞게 정렬하면 실제로 중요한 것을 개선하고 있다는 걸 보장할 수 있어요. 이 정렬 단계는 신뢰할 수 있는 평가의 기초예요.
진짜 가치는: 데이터를 바라보는 것
정렬된 LLM 심판을 구축하는 것도 유용하지만, 진짜 비즈니스 가치는 데이터를 체계적으로 분석하고 실패 패턴을 이해하는 데서 나와요. 심판 정렬 과정은 엣지 케이스를 깊이 조사하고, 평가 기준을 명확히 하며, 응답을 좋거나 나쁘게 만드는 것에 대한 통찰을 발견하도록 강제해요. 심판은 분석을 확장하는 도구라고 생각해요. 분석을 대체하는 게 아니라요.
환경 설정
애플리케이션을 만드는 대신 평가 과정을 이해하는 데 집중할 수 있도록 간단한 모듈을 설치해서 실행할 수 있게 만들어 두었어요.
uv pip install "ragas[examples]"
export OPENAI_API_KEY="your-api-key-here"
전체 코드
심판 정렬 평가 파이프라인의 전체 코드는 여기에서 볼 수 있어요.
데이터셋 이해하기
비즈니스 질문에 대한 LLM 응답의 전문가 주석 예제를 포함하는 EvalsBench 데이터셋을 사용할 거예요. 각 행은 다음을 포함해요.
question: 원래 질문grading_notes: 좋은 응답에 포함되어야 할 핵심 포인트response: LLM이 생성한 응답target: 인간 전문가의 이진 판단(pass/fail)
데이터셋 다운로드:
# datasets 폴더를 만들고 데이터셋 다운로드
mkdir -p datasets
curl -o datasets/benchmark_df.csv https://raw.githubusercontent.com/vibrantlabsai/EvalsBench/main/data/benchmark_df.csv
데이터셋 로드 및 검사:
import pandas as pd
from ragas import Dataset
def load_dataset(csv_path: str = None) -> Dataset:
"""인간 판단이 포함된 주석 데이터셋 로드.
기대 컬럼: question, grading_notes, response, target (pass/fail)
"""
path = csv_path or "datasets/benchmark_df.csv"
df = pd.read_csv(path)
dataset = Dataset(name="llm_judge_alignment", backend="local/csv")
for _, row in df.iterrows():
dataset.append({
"question": row["question"],
"grading_notes": row["grading_notes"],
"response": row["response"],
"target": (row["target"]),
})
return dataset
# 데이터셋 로드
dataset = load_dataset()
print(f"Dataset loaded with {len(dataset)} samples")
데이터셋 샘플 행:
| question | grading_notes | response | target |
|---|---|---|---|
| What are the key methods for determining the pre-money valuation of a tech startup before a Series A investment round, and how do they differ? | DCF method: !future cash flows!, requires projections; Comp. analysis: similar co. multiples; VC method: rev x multiple - post-$; Founder's share matter; strategic buyers pay more. | Determining the pre-money valuation of a tech startup before a Series A investment round is a critical step... (covers DCF, comparable analysis, VC method) | pass |
| What key metrics and strategies should a startup prioritize to effectively manage and reduce churn rate in a subscription-based business model? | Churn:! monitor monthly, <5% ideal. Retention strategies: engage users, improve onboarding. CAC & LTV: balance 3:1+. Feedback loops: implement early. Customer support: proactive & responsive, critical. | Managing and reducing churn rate in a subscription-based business model is crucial... (missing specific metrics and strategies) | fail |
데이터셋은 같은 질문에 대한 여러 응답을 포함하며 일부는 pass, 일부는 fail이에요. 이는 심판이 수용 가능한 응답과 그렇지 않은 응답 사이의 미묘한 차이를 학습하는 데 도움이 돼요.
ground truth 이해하기
심판 정렬의 품질은 전적으로 ground truth 라벨의 품질에 달려 있어요. 프로덕션 시나리오에서는 주요 도메인 전문가를 참여시키세요. 사용 사례에서 그 판단이 가장 중요한 사람(예: 정신 건강 AI에는 심리학자, 법률 AI에는 변호사, 지원 챗봇에는 고객 서비스 이사)이요. 그들의 일관된 판단이 심판이 정렬해야 하는 골드 스탠다드가 돼요. 모든 예제에 라벨이 필요하진 않아요. 다양한 시나리오를 다루는 대표 샘플(100~200개 예제)이면 신뢰할 만한 정렬에 충분해요.
평가 접근 방식 이해하기
이 가이드에서는 새 응답을 생성하는 대신 데이터셋의 기존 응답을 평가해요. 이 접근 방식은 평가 실행 간에 재현 가능한 결과를 보장하고, 응답 생성이 아닌 심판 정렬에 집중할 수 있게 해요.
평가 워크플로우는 다음과 같아요: 데이터셋 행 (question + response) → 심판 → 인간 target과 비교
평가 메트릭 정의하기
심판 정렬에는 두 가지 메트릭이 필요해요.
기본 메트릭: accuracy (LLM 심판) - 응답을 평가하고 이유와 함께 pass/fail 결정을 반환해요.
정렬 메트릭: judge_alignment - 심판의 결정이 인간 전문가의 판결과 일치하는지 확인해요.
심판 메트릭 설정
grading notes에 대해 응답을 평가하는 간단한 베이스라인 심판 메트릭을 정의해요.
from ragas.metrics import DiscreteMetric
# 간단한 베이스라인 프롬프트로 심판 메트릭 정의
accuracy_metric = DiscreteMetric(
name="accuracy",
prompt="Check if the response contains points mentioned from the grading notes and return 'pass' or 'fail'.\n\nResponse: {response}\nGrading Notes: {grading_notes}",
allowed_values=["pass", "fail"],
)
정렬 메트릭
정렬 메트릭은 심판의 결정을 인간 판결과 비교해요.
from ragas.metrics.discrete import discrete_metric
from ragas.metrics.result import MetricResult
@discrete_metric(name="judge_alignment", allowed_values=["pass", "fail"])
def judge_alignment(judge_label: str, human_label: str) -> MetricResult:
"""심판 결정을 인간 라벨과 비교."""
judge = judge_label.strip().lower()
human = human_label.strip().lower()
if judge == human:
return MetricResult(value="pass", reason=f"Judge={judge}; Human={human}")
return MetricResult(value="fail", reason=f"Judge={judge}; Human={human}")
실험 함수
실험 함수는 심판으로 응답을 평가하고 정렬을 측정하는 완전한 평가 파이프라인을 오케스트레이션해요.
from typing import Dict, Any
from ragas import experiment
from ragas.metrics import DiscreteMetric
from ragas_examples.judge_alignment import judge_alignment # 위에서 만든 메트릭
@experiment()
async def judge_experiment(
row: Dict[str, Any],
accuracy_metric: DiscreteMetric,
llm,
):
"""완전한 평가 실행: 심판 → 인간과 비교."""
# Step 1: 응답 가져오기 (프로덕션에서는 여기서 LLM 앱을 호출)
# 이 평가에서는 데이터셋의 기존 응답을 사용
app_response = row["response"]
# Step 2: 심판이 응답 평가
judge_score = await accuracy_metric.ascore(
question=row["question"],
grading_notes=row["grading_notes"],
response=app_response,
llm=llm,
)
# Step 3: 심판 결정을 인간 target과 비교
alignment = judge_alignment.score(
judge_label=judge_score.value,
human_label=row["target"]
)
return {
**row,
"judge_label": judge_score.value,
"judge_reason": judge_score.reason,
"alignment": alignment.value,
"alignment_reason": alignment.reason,
}
베이스라인 평가 실행
평가 파이프라인 실행 및 결과 수집
import os
from openai import AsyncOpenAI
from ragas.llms import llm_factory
from ragas_examples.judge_alignment import load_dataset
# 데이터셋 로드
dataset = load_dataset()
print(f"Dataset loaded with {len(dataset)} samples")
# LLM 클라이언트 초기화
openai_client = AsyncOpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
llm = llm_factory("gpt-4o-mini", client=openai_client)
# 실험 실행
results = await judge_experiment.arun(
dataset,
name="judge_baseline_v1_gpt-4o-mini",
accuracy_metric=accuracy_metric,
llm=llm,
)
# 정렬률 계산
passed = sum(1 for r in results if r["alignment"] == "pass")
total = len(results)
print(f"✅ Baseline alignment: {passed}/{total} passed ({passed/total:.1%})")
📋 출력 (baseline v1)
2025-10-08 22:40:00,334 - Loaded dataset with 160 samples
2025-10-08 22:40:00,334 - Initializing LLM client with model: gpt-4o-mini
2025-10-08 22:40:01,858 - Running baseline evaluation...
Running experiment: 100%|████████████████████████| 160/160 [04:35<00:00, 1.72s/it]
2025-10-08 22:44:37,149 - ✅ Baseline alignment: 121/160 passed (75.6%)
초기 성능 분석
이 평가는 모든 입력(question, grading_notes, response), 인간 target, 이유가 포함된 심판 결정, 정렬 비교를 포함하는 포괄적인 CSV 결과를 생성해요.
오류 및 실패 패턴 분석
베이스라인 평가를 실행한 후, 심판이 인간 전문가와 어디에서 의견이 다른지 이해하기 위해 불일치 패턴을 분석해요.
베이스라인 성능: 75.6% 정렬 (121/160 정확)
오류 분포를 살펴볼게요.
📋 코드
import pandas as pd
# 결과 로드
df = pd.read_csv('experiments/judge_baseline_v1_gpt-4o-mini.csv')
# 불일치 분석
false_positives = len(df[(df['judge_label'] == 'pass') & (df['target'] == 'fail')])
false_negatives = len(df[(df['judge_label'] == 'fail') & (df['target'] == 'pass')])
print(f"False positives (judge too lenient): {false_positives}")
print(f"False negatives (judge too strict): {false_negatives}")
📋 출력
False positives (judge too lenient): 39
False negatives (judge too strict): 0
핵심 관찰: 39개의 불일치(24.4%)가 모두 false positive예요. 즉 심판은 "pass"라고 했지만 인간 전문가는 "fail"이라고 한 경우죠. 베이스라인 심판은 너무 관대해서 grading notes의 핵심 개념을 빠뜨린 응답을 놓치고 있어요.
샘플 실패 사례
심판이 핵심 개념이 빠진 응답을 잘못 통과시킨 예시들이에요.
| Grading Notes | Human Label | Judge Label | What's Missing |
|---|---|---|---|
*Valuation caps*, $, post-$ val key. Liquidation prefs: 1x+ common. Anti-dilution: *full vs. weighted*. Board seats: 1-2 investor reps. ESOP: 10-20%. |
fail | pass | Response discusses all points comprehensively but human annotators marked it as fail for subtle omissions |
*Impact on valuation*: scalability potential, dev costs, integration ease. !Open-source vs proprietary issues. !Tech debt risks. Discuss AWS/GCP/Azure... |
fail | pass | Missing specific discussion of post-money valuation impact |
Historical vs. forecasted rev; top-down & bottom-up methods; *traction evidence*; !unbiased assumptions; 12-24mo project... |
fail | pass | Missing explicit mention of traction evidence |
오류의 일반적인 패턴:
- 다른 개념을 다루면서 grading notes의 특정 개념 1-2개 누락
- 암시적 vs 명시적 커버리지 - 심판이 암시된 개념을 수용하지만, 우리는 명시적 언급을 원함
- 약어가 제대로 해독되지 않음 (예: "mkt demand" = market demand, "post-$" = post-money valuation)
- 중요 마커 무시 -
*또는!로 표시된 포인트가 필수인 경우가 많음
심판 프롬프트 개선하기
오류 분석을 바탕으로 다음을 수행하는 개선된 프롬프트가 필요해요.
- grading notes에 사용된 약어 이해
- 중요 마커 인식 (
*,!, 특정 숫자) - 대부분이 아닌 모든 개념이 존재해야 함
- 의미적 동등 표현 수용 (같은 개념의 다른 표현)
- 엄격함의 균형 - 너무 관대하지도, 너무 엄격하지도 않게
개선된 v2 프롬프트 만들기
포괄적인 평가 기준으로 강화된 심판 메트릭을 정의해요.
from ragas.metrics import DiscreteMetric
# 강화된 평가 기준으로 개선된 심판 메트릭 정의
accuracy_metric_v2 = DiscreteMetric(
name="accuracy",
prompt="""Evaluate if the response covers ALL the key concepts from the grading notes. Accept semantic equivalents but carefully check for missing concepts.
ABBREVIATION GUIDE - decode these correctly:
• Financial: val=valuation, post-$=post-money, rev=revenue, ARR/MRR=Annual/Monthly Recurring Revenue, COGS=Cost of Goods Sold, Opex=Operating Expenses, LTV=Lifetime Value, CAC=Customer Acquisition Cost
• Business: mkt=market, reg/regs=regulation/regulatory, corp gov=corporate governance, integr=integration, S&M=Sales & Marketing, R&D=Research & Development, acq=acquisition
• Technical: sys=system, elim=elimination, IP=Intellectual Property, TAM=Total Addressable Market, diff=differentiation
• Metrics: NPS=Net Promoter Score, SROI=Social Return on Investment, proj=projection, cert=certification
EVALUATION APPROACH:
Step 1 - Parse grading notes into distinct concepts:
- Separate by commas, semicolons, or line breaks
- Each item is a concept that must be verified
- Example: "*Gross Margin* >40%, CAC, LTV:CAC >3:1" = 3 concepts
Step 2 - For each concept, check if it's addressed:
- Accept semantic equivalents (e.g., "customer acquisition cost" = "CAC")
- Accept implicit coverage when it's clear (e.g., "revenue forecasting" covers "historical vs forecasted rev")
- Be flexible on exact numbers (e.g., "around 40%" acceptable for ">40%")
Step 3 - Count missing concepts:
- Missing 0 concepts = PASS
- Missing 1+ concepts = FAIL (even one genuinely missing concept should fail)
- Exception: If a long list (10+ items) has 1 very minor detail missing but all major points covered, use judgment
CRITICAL RULES:
1. Do NOT require exact wording - "market demand" = "mkt demand" = "demand analysis"
2. Markers (* or !) mean important, not mandatory exact phrases:
- "*traction evidence*" can be satisfied by discussing metrics, growth, or validation
- "!unbiased assumptions" can be satisfied by discussing assumption methodology
3. Numbers should be mentioned but accept approximations:
- "$47B to $10B" can be "$47 billion dropped to around $10 billion"
- "LTV:CAC >3:1" can be "LTV to CAC ratio of at least 3 to 1" or "3x or higher"
4. FAIL only when concepts are genuinely absent:
- If notes mention "liquidation prefs, anti-dilution, board seats" but response only has board seats → FAIL
- If notes mention "scalability, tech debt, IP" but response never discusses technical risks → FAIL
- If notes mention "GDPR compliance" and response never mentions GDPR or EU regulations → FAIL
5. PASS when ALL concepts present:
- All concepts covered, even with different wording → PASS
- Concepts addressed implicitly when clearly implied → PASS
- Minor phrasing differences → PASS
- One or more concepts genuinely absent → FAIL
Response: {response}
Grading Notes: {grading_notes}
Are ALL distinct concepts from the grading notes covered in the response (accepting semantic equivalents and implicit coverage)?""",
allowed_values=["pass", "fail"],
)
LLM을 사용한 프롬프트 최적화
오류 패턴을 명확히 식별한 후에는 LLM을 사용해서 프롬프트를 최적화할 수 있어요. 오류 식별에도 LLM을 쓸 수 있지만, ground truth 라벨과 일치하도록 반드시 검토해야 해요. Cursor, Claude Code 같은 코딩 에이전트나 DSPy 같은 프레임워크를 사용해서 심판 프롬프트를 체계적으로 최적화할 수도 있어요.
개선된 프롬프트로 평가 재실행
강화된 v2 프롬프트로 평가를 다시 실행해요(베이스라인과 동일한 설정, 메트릭만 교체):
# 위 베이스라인 평가와 동일한 데이터셋과 LLM 설정 사용
results = await judge_experiment.arun(
dataset,
name="judge_accuracy_v2_gpt-4o-mini",
accuracy_metric=accuracy_metric_v2, # ← 개선된 v2 프롬프트 사용
llm=llm,
)
passed = sum(1 for r in results if r["alignment"] == "pass")
total = len(results)
print(f"✅ V2 alignment: {passed}/{total} passed ({passed/total:.1%})")
📋 출력 (improved v2)
2025-10-08 23:42:11,650 - Loaded dataset with 160 samples
2025-10-08 23:42:11,650 - Initializing LLM client with model: gpt-4o-mini
2025-10-08 23:42:12,730 - Running v2 evaluation with improved prompt...
Running experiment: 100%|██████████| 160/160 [04:39<00:00, 1.75s/it]
2025-10-08 23:46:52,740 - ✅ V2 alignment: 139/160 passed (86.9%)
큰 개선! 정렬이 75.6%에서 86.9%로 올라갔어요.
더 반복해야 한다면:
- 남은 오류를 분석해서 패턴을 식별해요 (false positive인지 false negative인지)
- 라벨과 함께 추론을 주석 달아요. LLM 심판을 개선할 때 few-shot 예제로 추가할 수 있어요.
- 더 똑똑한 모델 사용 - GPT-5나 Claude 4.5 Sonnet 같은 더 강력한 모델이 일반적으로 심판으로 더 잘 작동해요
- AI 어시스턴트 활용 - 이 가이드는 Cursor AI 에이전트로 실패를 분석하고 프롬프트를 반복하면서 만들어졌어요. AI 코딩 에이전트(Cursor, Claude 등)나 DSPy 같은 프레임워크로 심판 프롬프트를 체계적으로 최적화할 수 있어요
- 정렬이 2~3회 반복에서 평평해지거나 비즈니스 임계값을 충족하면 중단해요
달성한 것 (What you've accomplished)
Ragas를 사용해서 다음을 수행하는 체계적인 평가 파이프라인을 구축했어요.
- 명확한 메트릭으로 전문가 판단에 대한 심판 정렬 측정
- 구조화된 오류 분석으로 실패 패턴 식별
- 재현 가능한 실험으로 평가 실행 간 개선 추적
이 정렬된 심판은 신뢰할 수 있는 AI 평가의 기초가 돼요. 신뢰할 수 있는 심판을 가지면 RAG 파이프라인, 에이전트 워크플로우, 어떤 LLM 애플리케이션이든 자신 있게 평가할 수 있어요. 메트릭 개선이 실제 품질 개선으로 이어진다는 걸 알기 때문이에요.