평가자 개요
평가자 개요 (Evaluators Overview)
**평가자(Evaluators)**는 Pydantic Evals의 핵심이에요. 평가자는 작업 출력을 분석하고 점수, 라벨, 또는 합격/불합격 판정을 제공해줘요.
출처: 문서
본문
언제 어떤 평가자를 쓰면 좋을까요?
결정적 검사 (빠르고 신뢰할 수 있어요)
정확한 규칙을 정의할 수 있을 때는 결정적(deterministic) 평가자를 사용해요:
| 평가자 | 사용 사례 | 예시 |
|---|---|---|
EqualsExpected |
정확한 출력 일치 | 구조화된 데이터, 분류 |
Equals |
특정 값과 동일한지 | 센티널 값을 확인할 때 |
Contains |
부분 문자열/요소 확인 | 필수 키워드, PII 탐지 |
IsInstance |
타입 검증 | 포맷 검증 |
MaxDuration |
성능 임계값 | SLA 준수 |
HasMatchingSpan |
동작(Behavior) 검증 | 툴 호출, 코드 경로 |
ToolCorrectness |
필수 툴 커버리지 | 호출된 툴 이름의 멀티셋 |
TrajectoryMatch |
툴 호출 순서 품질 | 기대 궤적 대비 F1 |
ArgumentCorrectness |
툴 인자 확인 | 환불 order_id, 검색 쿼리 |
MaxToolCalls |
예산 규율 | 툴 호출 예산 |
MaxModelRequests |
예산 규율 | 모델 요청 예산 |
장점:
- 빠른 실행 (마이크로초에서 밀리초)
- 결정적 결과
- 비용이 들지 않아요
- 디버깅이 쉬워요
이럴 때 사용해요:
- 포맷 검증 (JSON 구조, 타입 확인)
- 필수 콘텐츠 확인 (X를 반드시 포함해야 함, Y를 포함하면 안 됨)
- 성능 요구사항 (지연 시간, 토큰 수)
- 동작 검사 (어떤 툴이 호출됐는지, 어떤 코드 경로가 실행됐는지)
LLM 저스팅 (유연하고 세밀해요)
평가에 이해나 판단이 필요할 때는 LLMJudge를 사용해요:
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import LLMJudge
dataset = Dataset(
name='llm_judge_example',
cases=[Case(inputs='What is 2+2?', expected_output='4')],
evaluators=[
LLMJudge(
rubric='Response is factually accurate based on the input',
include_input=True,
)
],
)
널리 쓰이는 평가 방법(G-Eval, Ragas RAG 메트릭, GEMBA)에 맞춘 메트릭은 표준 품질 메트릭 항목을 참고해요. 거기에 GEval 평가자와 그대로 복사해 쓸 수 있는 LLMJudge 루브릭이 들어 있어요. 외부 프레임워크의 정확한 원본 구현을 그대로 붙여 넣으려면 타사 통합 항목을 참고해요.
장점:
- 주관적 특성(도움됨, 톤, 창의성)을 평가할 수 있어요
- 자연어를 이해해요
- 복잡한 루브릭을 따를 수 있어요
- 여러 도메인에 유연하게 적용돼요
단점:
- 더 느려요 (평가당 몇 초)
- 비용이 들어요
- 비결정적이에요
- 편향이 있을 수 있어요
이럴 때 사용해요:
- 사실 정확성
- 관련성과 도움됨
- 톤과 스타일
- 완성도
- 지시사항 준수
- RAG 품질 (근거 강도, 인용 정확성)
커스텀 평가자
프레임워크가 제공하지 않는 어떤 평가 로직이든 활용하고 싶다면 커스텀 평가자가 유용해요. 도메인 특화 로직에 자주 쓰여요:
from dataclasses import dataclass
from pydantic_evals.evaluators import Evaluator, EvaluatorContext
@dataclass
class ValidSQL(Evaluator):
def evaluate(self, ctx: EvaluatorContext) -> bool:
try:
import sqlparse
sqlparse.parse(ctx.output)
return True
except Exception:
return False
이럴 때 사용해요:
- 도메인 특화 검증 (SQL 문법, 정규식 패턴, 비즈니스 규칙)
- 외부 API 호출 (생성된 코드 실행, 데이터베이스 확인)
- 복잡한 계산 (정밀도/재현율, BLEU 점수)
- 통합 확인 (API 호출이 성공하나요?)
평가 유형 (Evaluation Types)
커스텀 평가자가 정확히 무엇을 반환할 수 있는지 자세히 보려면 커스텀 평가자 반환 유형을 참고해요.
평가자는 기본적으로 세 가지 결과 유형을 반환해요:
1. 판정 (bool)
보고서에 ✔ 또는 ✗로 표시되는 합격/불합격 검사:
from dataclasses import dataclass
from pydantic_evals.evaluators import Evaluator, EvaluatorContext
@dataclass
class HasKeyword(Evaluator):
keyword: str
def evaluate(self, ctx: EvaluatorContext) -> bool:
return self.keyword in ctx.output
이럴 때 사용해요: 이진 검사, 품질 게이트, 규정 준수 요구사항
2. 점수 (int 또는 float)
숫자 메트릭:
from dataclasses import dataclass
from pydantic_evals.evaluators import Evaluator, EvaluatorContext
@dataclass
class ConfidenceScore(Evaluator):
def evaluate(self, ctx: EvaluatorContext) -> float:
# 점수를 분석하고 반환
return 0.87 # 87% 신뢰도
이럴 때 사용해요: 품질 메트릭, 순위, A/B 테스트, 회귀 추적
3. 라벨 (str)
범주형 분류:
from dataclasses import dataclass
from pydantic_evals.evaluators import Evaluator, EvaluatorContext
@dataclass
class SentimentClassifier(Evaluator):
def evaluate(self, ctx: EvaluatorContext) -> str:
if 'error' in ctx.output.lower():
return 'error'
elif 'success' in ctx.output.lower():
return 'success'
return 'neutral'
이럴 때 사용해요: 분류, 오류 범주화, 품질 버킷
여러 결과
하나의 평가자에서 여러 평가 결과를 반환할 수 있어요:
from dataclasses import dataclass
from pydantic_evals.evaluators import Evaluator, EvaluatorContext
@dataclass
class ComprehensiveCheck(Evaluator):
def evaluate(self, ctx: EvaluatorContext) -> dict[str, bool | float | str]:
return {
'valid_format': self._check_format(ctx.output), # bool
'quality_score': self._score_quality(ctx.output), # float
'category': self._classify(ctx.output), # str
}
def _check_format(self, output: str) -> bool:
return True
def _score_quality(self, output: str) -> float:
return 0.85
def _classify(self, output: str) -> str:
return 'good'
평가자 조합하기
평가자를 조합해서 포괄적인 평가 스위트를 만들 수 있어요:
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import (
Contains,
IsInstance,
LLMJudge,
MaxDuration,
)
dataset = Dataset(
name='layered_evaluation',
cases=[Case(inputs='test', expected_output='result')],
evaluators=[
# 먼저 빠른 결정적 검사부터
IsInstance(type_name='str'),
Contains(value='required_field'),
MaxDuration(seconds=2.0),
# 그 다음 느린 LLM 검사
LLMJudge(
rubric='Response is accurate and helpful',
include_input=True,
),
],
)
케이스별 평가자
케이스별 평가자는 포괄적인 평가 스위트를 만들 때 가장 강력한 기능 중 하나예요. 개별 Case 객체에 평가자를 붙이면 그 특정 케이스에서만 실행돼요:
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import IsInstance, LLMJudge
dataset = Dataset(
name='case_specific_evaluators',
cases=[
Case(
name='greeting_response',
inputs='Say hello',
evaluators=[
# 이 평가자는 이 케이스에서만 실행돼요
LLMJudge(
rubric='Response is warm and friendly, uses casual tone',
include_input=True,
),
],
),
Case(
name='formal_response',
inputs='Write a business email',
evaluators=[
# 이 케이스는 요구사항이 달라요
LLMJudge(
rubric='Response is professional and formal, uses business language',
include_input=True,
),
],
),
],
evaluators=[
# 이건 모든 케이스에서 실행돼요
IsInstance(type_name='str'),
],
)
케이스별 평가자가 중요한 이유
케이스별 평가자는 만능 평가가 가진 근본적인 문제를 해결해줘요: 만약 모든 케이스에 걸쳐 요구사항을 완벽히 잡아내는 루브릭 하나를 작성할 수 있다면, 그 루브릭을 아예 에이전트 지시사항에 반영하는 게 나아요. (참고: 프로덕션에서 더 저렴한 모델을 쓰고 더 비싼 모델로 평가하려는 경우엔 이 내용이 덜 관련되지만, 많은 경우 프로덕션에서 쓸 수 있는 최고의 모델을 쓰는 게 합리적이에요.)
케이스별 평가의 강점은 세부적인 차이에서 나와요:
- 케이스마다 요구사항이 달라요: 고객 지원 응답엔 공감이 필요하고, 기술 API 응답엔 정확성이 필요해요
- "환자를 옥에 가두는" 함정을 피해요: LLMJudge 루브릭이 어디서나 통하는 일반적인 수준이라면, 에이전트가 이미 그걸 따르고 있어야 해요
- 세밀한 골든 동작을 담아요: 각 케이스마다 그 시나리오에서 "좋다"는 것이 정확히 무엇인지 지정할 수 있어요
케이스별 LLMJudge로 골든 데이터셋 만들기
특히 강력한 패턴은 케이스별 LLMJudge 평가자로 포괄적이고 유지보수하기 쉬운 평가 스위트를 빠르게 만드는 거예요. 정확한 expected_output 값이 없어도, 신경 쓰는 내용을 서술하기만 하면 돼요:
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import LLMJudge
dataset = Dataset(
name='golden_dataset',
cases=[
Case(
name='handle_refund_request',
inputs={'query': 'I want my money back', 'order_id': '12345'},
evaluators=[
LLMJudge(
rubric="""
Response should:
1. Acknowledge the refund request empathetically
2. Ask for the reason for the refund
3. Mention our 30-day refund policy
4. NOT process the refund immediately (needs manager approval)
""",
include_input=True,
),
],
),
Case(
name='handle_shipping_question',
inputs={'query': 'Where is my order?', 'order_id': '12345'},
evaluators=[
LLMJudge(
rubric="""
Response should:
1. Confirm the order number
2. Provide tracking information
3. Give estimated delivery date
4. Be brief and factual (not overly apologetic)
""",
include_input=True,
),
],
),
Case(
name='handle_angry_customer',
inputs={'query': 'This is completely unacceptable!', 'order_id': '12345'},
evaluators=[
LLMJudge(
rubric="""
Response should:
1. Prioritize de-escalation with empathy
2. Avoid being defensive
3. Offer concrete next steps
4. Use phrases like "I understand" and "Let me help"
""",
include_input=True,
),
],
),
],
)
이 접근 방식은 여러분에게 다음을 가능하게 해줘요:
- 포괄적인 테스트 스위트를 빠르게 만들어요: 케이스마다 원하는 내용을 서술하기만 하면 돼요
- 쉽게 유지보수해요: 요구사항이 바뀌면 출력을 재생성하지 않고 루브릭을 갱신해요
- 자연스럽게 엣지 케이스를 커버해요: 발견할 때마다 특정 요구사항을 가진 새 케이스를 추가해요
- 도메인 지식을 담아요: 각 루브릭은 그 시나리오에서 "좋다"는 의미를 문서화해요
LLM 평가자는 세밀한 요구사항을 이해하고 준수 여부를 평가하는 데 탁월해서, 취약하지 않으면서도 철저한 평가 커버리지를 만드는 실용적인 방법이에요.
비동기 vs 동기
평가자는 동기 또는 비동기일 수 있어요:
from dataclasses import dataclass
from pydantic_evals.evaluators import Evaluator, EvaluatorContext
@dataclass
class SyncEvaluator(Evaluator):
def evaluate(self, ctx: EvaluatorContext) -> bool:
return True
async def some_async_operation() -> bool:
return True
@dataclass
class AsyncEvaluator(Evaluator):
async def evaluate(self, ctx: EvaluatorContext) -> bool:
result = await some_async_operation()
return result
Pydantic Evals는 두 경우 모두 자동으로 처리해줘요. 비동기는 이럴 때 사용해요:
- API 호출
- 데이터베이스 쿼리 실행
- I/O 작업 수행
- LLM 호출 (예:
LLMJudge)
평가 컨텍스트
모든 평가자는 EvaluatorContext를 받아요:
ctx.inputs- 작업 입력ctx.output- 작업 출력 (평가 대상)ctx.expected_output- 기대 출력 (제공된 경우)ctx.metadata- 케이스 메타데이터 (제공된 경우)ctx.duration- 작업 실행 시간 (초)ctx.span_tree- OpenTelemetry 스팬 (Logfire가 구성된 경우)ctx.metrics- 커스텀 메트릭 딕셔너리ctx.attributes- 커스텀 속성 딕셔너리
이를 통해 평가자는 충분한 컨텍스트를 갖고 정보에 기반한 판단을 내릴 수 있어요.
오류 처리
평가자가 예외를 던지면 EvaluatorFailure로 포착돼요:
from dataclasses import dataclass
from pydantic_evals.evaluators import Evaluator, EvaluatorContext
def risky_operation(output: str) -> bool:
# 이 함수는 예외를 던질 수 있어요
if 'error' in output:
raise ValueError('Found error in output')
return True
@dataclass
class RiskyEvaluator(Evaluator):
def evaluate(self, ctx: EvaluatorContext) -> bool:
# 여기서 예외가 발생하면 포착돼요
result = risky_operation(ctx.output)
return result
실패는 report.cases[i].evaluator_failures에 다음 정보와 함께 표시돼요:
- 평가자 이름
- 오류 메시지
- 전체 스택트레이스
일시적인 실패를 처리하려면 재시도 설정을 사용해요 (재시도 전략 참고).
리포트 평가자 (실험 전반)
위의 모든 평가자는 케이스마다 한 번씩 실행돼요. 리포트 평가자는 다르게 동작해요. 모든 케이스가 평가된 후 실험당 한 번 실행되며, 전체 결과 집합을 함께 분석해요.
리포트 평가자는 실험 전반의 통계에 사용해요:
- 혼동 행렬 - 클래스 간 분류 정확도를 시각화
- 정밀도-재현율 곡선 - AUC 점수로 순위 품질 평가
- 스칼라 메트릭 - 전체 정확도, F1, BLEU, 또는 어떤 단일 숫자든
- 요약 테이블 - 클래스별 분석, 오류 범주 요약
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import ConfusionMatrixEvaluator
dataset = Dataset(
name='report_evaluator_example',
cases=[
Case(inputs='meow', expected_output='cat'),
Case(inputs='woof', expected_output='dog'),
],
report_evaluators=[
ConfusionMatrixEvaluator(
predicted_from='output',
expected_from='expected_output',
),
],
)
참고: 내장 리포트 평가자와 커스텀 리포트 평가자 작성법을 포함한 전체 가이드는 리포트 평가자를 참고해요.
다음 단계
- 내장 평가자 - 제공되는 모든 평가자의 완전한 레퍼런스
- LLM 저지 - LLM-as-a-Judge 평가 깊게 살펴보기
- 표준 품질 메트릭 - G-Eval과 흔한 RAG·번역 메트릭용 LLM judge 루브릭
- 타사 통합 - Ragas, DeepEval, 그리고 다른 메트릭 라이브러리 래핑
- 커스텀 평가자 - 직접 평가 로직 작성하기
- 리포트 평가자 - 실험 전반 분석
- 스팬 기반 평가 - OpenTelemetry 스팬으로 평가
- 에이전트형 평가자 - 에이전트를 위한 궤적·툴 정확성·인자·단계 예산 검사
더 알아보기 (Learn more)
- Pydantic Evals 문서: 평가자 개요