평가자 개요

평가자 개요 (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',
        ),
    ],
)

참고: 내장 리포트 평가자와 커스텀 리포트 평가자 작성법을 포함한 전체 가이드는 리포트 평가자를 참고해요.

다음 단계

더 알아보기 (Learn more)