핵심 개념
핵심 개념 (Core Concepts)
이 페이지는 Pydantic Evals의 핵심 개념과 그것들이 어떻게 함께 작동하는지 설명해요.
Pydantic Evals는 다음과 같은 핵심 개념을 중심으로 만들어져요.
Dataset— 테스트 케이스와 평가자를 담은 정적 정의Case— 입력과 선택적 예상 출력을 가진 단일 테스트 시나리오Evaluator— 개별 출력을 채점하거나 검증하는 로직ReportEvaluator— 전체 실험 결과(예: 혼동 행렬, 정확도)를 분석하는 로직- Experiment — 데이터셋의 모든 케이스에 대해 작업 함수를 실행하는 행위. (이는
Dataset.evaluate호출에 해당해요.) EvaluationReport— 실험 실행의 결과
핵심 구분은 다음과 같아요.
- 정의(Definition) (
Case,Evaluator,ReportEvaluator를 가진Dataset) — 무엇을 테스트할지 - 실행(Execution) (Experiment) — 그 테스트들에 대해 당신의 작업을 실행하는 것
- 결과(Results) (케이스 결과와 실험 전체 분석을 가진
EvaluationReport) — 실험 중 무슨 일이 있었는지
출처: 문서
본문
단위 테스트 비유 (Unit Testing Analogy)
Pydantic Evals를 생각하는 유용한 방법:
| 단위 테스트 | Pydantic Evals |
|---|---|
| 테스트 함수 | Case + Evaluator |
| 테스트 스위트 | Dataset |
테스트 실행 (pytest) |
Experiment (dataset.evaluate(task)) |
| 테스트 보고서 | EvaluationReport |
assert |
bool을 반환하는 Evaluator |
핵심 차이: AI 시스템은 확률적이므로 단순한 통과/실패 대신 평가는 다음을 가질 수 있어요.
- 정량적 점수 (0.0 ~ 1.0)
- 정성적 라벨 ("good", "acceptable", "poor")
- 설명 이유가 있는 통과/실패 어서션
같은 테스트 스위트에서 pytest를 여러 번 실행할 수 있듯, 같은 데이터셋에서 여러 실험을 실행해 서로 다른 구현을 비교하거나 시간에 따른 변화를 추적할 수 있어요.
데이터셋 (Dataset)
Dataset은 평가 스위트를 정의하는 테스트 케이스와 평가자의 모음이에요.
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import IsInstance
dataset = Dataset(
name='my_eval_suite',
cases=[
Case(inputs='test input', expected_output='test output'),
],
evaluators=[
IsInstance(type_name='str'),
],
)
핵심 기능 (Key Features)
- 타입 안전:
InputsT,OutputT,MetadataT타입에 대해 제네릭 - 직렬화 가능: YAML 또는 JSON 파일로 저장/로드 가능
- 평가 가능: 일치하는 입출력 타입을 가진 어떤 함수에 대해서도 실행 가능
Dataset 수준 vs Case 수준 평가자
평가자는 두 수준에서 정의할 수 있어요.
Dataset수준: 데이터셋의 모든 케이스에 적용Case수준: 특정 케이스에만 적용
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import EqualsExpected, IsInstance
dataset = Dataset(
name='case_level_evaluators',
cases=[
Case(
name='special_case',
inputs='test',
expected_output='TEST',
evaluators=[
# This evaluator only runs for this case
EqualsExpected(),
],
),
],
evaluators=[
# This evaluator runs for ALL cases
IsInstance(type_name='str'),
],
)
실험 (Experiments)
Experiment는 데이터셋의 모든 케이스에 대해 작업 함수를 실행할 때 일어나는 일이에요. 이것이 정적 테스트 정의(Dataset)와 결과(EvaluationReport) 사이의 다리예요.
실험 실행 (Running an Experiment)
데이터셋에서 evaluate() 또는 evaluate_sync()를 호출해 실험을 실행해요.
from pydantic_evals import Case, Dataset
# Define your dataset (static definition)
dataset = Dataset(
name='uppercase_experiment',
cases=[
Case(inputs='hello', expected_output='HELLO'),
Case(inputs='world', expected_output='WORLD'),
],
)
# Define your task
def uppercase_task(text: str) -> str:
return text.upper()
# Run the experiment (execution)
report = dataset.evaluate_sync(uppercase_task)
실험 중 무슨 일이 일어나나요 (What Happens During an Experiment)
실험을 실행하면:
- 설정(Setup): 데이터셋이 모든 케이스, 평가자, 보고서 평가자를 로드
- 실행(Execution): 각 케이스에 대해:
case.inputs로 작업 함수를 호출- 실행 시간을 측정하고 (
logfire가 구성된 경우) OpenTelemetry 스팬을 캡처 - 각 케이스에 대한 작업 함수의 출력을 기록
- 케이스 평가(Case Evaluation): 각 케이스 출력에 대해:
- 모든 데이터셋 수준 평가자를 실행
- 케이스별 평가자를 실행 (있으면)
- 결과를 수집 (점수, 어서션, 라벨)
- 보고서 평가(Report Evaluation): 보고서 평가자가 구성되어 있으면 전체 결과 집합에 대해 실행해 실험 전체 분석을 생성 (혼동 행렬, 정밀도-재현율 곡선, 스칼라 지표, 표 등)
- 보고(Reporting): 모든 결과가
EvaluationReport로 집계. 케이스별 결과와 실험 전체 분석 모두 포함
하나의 데이터셋에서 여러 실험 (Multiple Experiments from One Dataset)
Pydantic Evals의 핵심 기능은 같은 데이터셋을 서로 다른 작업 구현에 대해 실행할 수 있다는 거예요.
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import EqualsExpected
dataset = Dataset(
name='comparison_test',
cases=[
Case(inputs='hello', expected_output='HELLO'),
],
evaluators=[EqualsExpected()],
)
# Original implementation
def task_v1(text: str) -> str:
return text.upper()
# Improved implementation (with exclamation)
def task_v2(text: str) -> str:
return text.upper() + '!'
# Compare results
report_v1 = dataset.evaluate_sync(task_v1)
report_v2 = dataset.evaluate_sync(task_v2)
avg_v1 = report_v1.averages()
avg_v2 = report_v2.averages()
print(f'V1 pass rate: {avg_v1.assertions if avg_v1 and avg_v1.assertions else 0}')
#> V1 pass rate: 1.0
print(f'V2 pass rate: {avg_v2.assertions if avg_v2 and avg_v2.assertions else 0}')
#> V2 pass rate: 0
이를 통해 다음을 할 수 있어요.
- 버전 간 구현 비교
- 시간에 따른 성능 추적
- 서로 다른 접근법의 A/B 테스트
- 배포 전 변경 검증
케이스 (Case)
Case는 특정 입력과 선택적 예상 출력을 가진 단일 테스트 시나리오를 나타내요.
from pydantic_evals import Case
from pydantic_evals.evaluators import EqualsExpected
case = Case(
name='test_uppercase', # Optional, but recommended for reporting
inputs='hello world', # Required: inputs to your task
expected_output='HELLO WORLD', # Optional: expected output
metadata={'category': 'basic'}, # Optional: arbitrary metadata
evaluators=[EqualsExpected()], # Optional: case-specific evaluators
)
케이스 구성 요소 (Case Components)
입력 (Inputs)
평가 대상 작업에 전달할 입력이에요. 어떤 타입이든 될 수 있어요.
from pydantic import BaseModel
from pydantic_evals import Case
class MyInputModel(BaseModel):
field1: str
# Simple types
Case(inputs='hello')
Case(inputs=42)
# Complex types
Case(inputs={'query': 'What is AI?', 'max_tokens': 100})
Case(inputs=MyInputModel(field1='value'))
예상 출력 (Expected Output)
EqualsExpected 같은 평가자가 사용하는 예상 결과예요.
from pydantic_evals import Case
Case(
inputs='2 + 2',
expected_output='4',
)
expected_output이 제공되지 않으면, 그것을 요구하는 평가자(예: EqualsExpected)는 그 케이스를 건너뛰어요. None이 "제공되지 않음"을 뜻하므로 expected_output=None으로 작성된 케이스도 건너뛰워져요. 작업이 None을 반환하도록 단언하려면 대신 Equals(value=None)을 사용하세요.
메타데이터 (Metadata)
평가자가 EvaluatorContext를 통해 접근할 수 있는 임의 데이터예요.
from pydantic_evals import Case
Case(
inputs='question',
metadata={
'difficulty': 'hard',
'category': 'math',
'source': 'exam_2024',
},
)
메타데이터는 다음에 유용해요.
- 분석 중 케이스 필터링
- 평가자에 컨텍스트 제공
- 테스트 스위트 구성
평가자 (Evaluators)
케이스는 그 특정 케이스에 대해서만 실행되는 자체 평가자를 가질 수 있어요. 이는 케이스마다 요구사항이 다른 포괄적 평가 스위트를 구축하는 데 특히 강력해요. 모든 케이스에 완벽히 작동하는 평가자 루브릭 하나를 쓸 수 있다면, 그냥 에이전트 지시에 넣으면 되거든요. 케이스별 LLMJudge 평가자는 각 시나리오에 대해 "좋음"이 무엇인지 설명함으로써 유지 관리 가능한 골든 데이터셋을 빠르게 구축하는 데 특히 유용해요. 자세한 설명과 예제는 케이스별 평가자를 참고하세요.
평가자 (Evaluator)
Evaluator는 작업의 출력을 평가하고 하나 이상의 점수, 라벨 또는 어서션을 반환해요. 각 점수·라벨·어서션에는 선택적으로 문자열 값의 이유(reason)를 연결할 수도 있어요.
평가자 유형 (Evaluator Types)
평가자는 서로 다른 유형의 결과를 반환해요.
| 반환 타입 | 용도 | 예시 |
|---|---|---|
bool |
어서션(Assertion) — 통과/실패 검사 | True → ✔, False → ✗ |
int 또는 float |
점수(Score) — 수치 품질 지표 | 0.95, 87 |
str |
라벨(Label) — 범주형 결과 | "correct", "hallucination" |
from dataclasses import dataclass
from pydantic_evals.evaluators import Evaluator, EvaluatorContext
@dataclass
class ExactMatch(Evaluator):
def evaluate(self, ctx: EvaluatorContext) -> bool:
return ctx.output == ctx.expected_output # Assertion
@dataclass
class Confidence(Evaluator):
def evaluate(self, ctx: EvaluatorContext) -> float:
# Analyze output and return confidence score
return 0.95 # Score
@dataclass
class Classifier(Evaluator):
def evaluate(self, ctx: EvaluatorContext) -> str:
if 'error' in ctx.output.lower():
return 'error' # Label
return 'success'
평가자는 EvaluationReason 인스턴스와, 라벨을 출력 값에 매핑하는 dict도 반환할 수 있어요. 자세한 내용은 사용자 정의 평가자 반환 타입 문서를 참고하세요.
EvaluatorContext
모든 평가자는 다음을 포함한 EvaluatorContext를 받아요.
name: 케이스 이름 (선택)inputs: 작업 입력metadata: 케이스 메타데이터 (선택)expected_output: 예상 출력 (선택)output: 작업의 실제 출력duration: 작업 실행 시간(초)span_tree: OpenTelemetry 스팬 (logfire가 구성된 경우)attributes: 사용자 정의 속성 dictmetrics: 사용자 정의 지표 dict
여러 평가 (Multiple Evaluations)
평가자는 dict를 반환해 여러 결과를 제공할 수 있어요.
from dataclasses import dataclass
from pydantic_evals.evaluators import Evaluator, EvaluatorContext
@dataclass
class MultiCheck(Evaluator):
def evaluate(self, ctx: EvaluatorContext) -> dict[str, bool | float | str]:
return {
'is_valid': isinstance(ctx.output, str), # Assertion
'length': len(ctx.output), # Metric
'category': 'long' if len(ctx.output) > 100 else 'short', # Label
}
평가 이유 (Evaluation Reasons)
EvaluationReason을 사용해 평가에 설명을 추가해요.
from dataclasses import dataclass
from pydantic_evals.evaluators import EvaluationReason, Evaluator, EvaluatorContext
@dataclass
class SmartCheck(Evaluator):
def evaluate(self, ctx: EvaluatorContext) -> EvaluationReason:
if ctx.output == ctx.expected_output:
return EvaluationReason(
value=True,
reason='Exact match with expected output',
)
return EvaluationReason(
value=False,
reason=f'Expected {ctx.expected_output!r}, got {ctx.output!r}',
)
이유는 include_reasons=True를 사용할 때 보고서에 나타나요.
평가 보고서 (Evaluation Report)
EvaluationReport은 실험 실행의 결과예요. 데이터셋의 케이스에 대해 작업을 실행하고 모든 평가자를 실행해 얻은 모든 데이터를 포함해요.
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import EqualsExpected
dataset = Dataset(
name='report_example',
cases=[Case(inputs='hello', expected_output='HELLO')],
evaluators=[EqualsExpected()],
)
def my_task(text: str) -> str:
return text.upper()
# Run an experiment
report = dataset.evaluate_sync(my_task)
# Print to console
report.print()
"""
Evaluation Summary: my_task
┏━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Case ID ┃ Assertions ┃ Duration ┃
┡━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━┩
│ Case 1 │ ✔ │ 10ms │
├──────────┼────────────┼──────────┤
│ Averages │ 100.0% ✔ │ 10ms │
└──────────┴────────────┴──────────┘
"""
# Access data programmatically
for case in report.cases:
print(f'{case.name}: {case.scores}')
#> Case 1: {}
보고서 구조 (Report Structure)
EvaluationReport은 다음을 포함해요.
name: 실험 이름cases: 성공한 케이스 평가 목록failures: 실패한 실행 목록analyses: 보고서 평가자로부터의 실험 전체 분석 목록 (혼동 행렬, PR 곡선, 스칼라, 표)trace_id: OpenTelemetry 트레이스 ID (선택)span_id: OpenTelemetry 스팬 ID (선택)
ReportCase
각 성공한 케이스 결과는 다음을 포함해요.
케이스 데이터:
name: 케이스 이름inputs: 작업 입력metadata: 케이스 메타데이터 (선택)expected_output: 예상 출력 (선택)output: 작업의 실제 출력
평가 결과:
scores: 평가자의 수치 점수 dictlabels: 평가자의 범주형 라벨 dictassertions: 평가자의 통과/실패 어서션 dict
성능 데이터:
task_duration: 작업 실행 시간total_duration: 평가자 포함 총 시간
추가 데이터:
metrics: 사용자 정의 지표 dictattributes: 사용자 정의 속성 dict
트레이싱:
trace_id: OpenTelemetry 트레이스 ID (선택)span_id: OpenTelemetry 스팬 ID (선택)
오류:
evaluator_failures: 평가자 오류 목록
데이터 모델 관계 (Data Model Relationships)
핵심 개념들이 어떻게 서로 관련되는지예요.
정적 정의 (Static Definition)
- Dataset은 다음을 포함해요:
- 많은 Case (입력과 예상 출력을 가진 테스트 시나리오)
- 많은 Evaluator (개별 출력을 채점하는 로직)
- 많은 Report Evaluator (전체 실험 결과를 분석하는 로직)
실행 (Experiment)
dataset.evaluate(task)를 호출하면 Experiment가 실행돼요.
- Task 함수가 Dataset의 모든 Case에 대해 실행
- 모든 Evaluator가 (데이터셋 수준과 케이스별 모두) 각 출력에 대해 적절히 실행
- 최종 출력으로 EvaluationReport 하나가 생성
결과 (Results)
- EvaluationReport은 다음을 포함해요:
- 각 Case의 결과 (입력, 출력, 점수, 어서션, 라벨)
- 보고서 평가자로부터의 실험 전체 분석 (혼동 행렬, PR 곡선, 스칼라, 표)
- 요약 통계 (평균, 통과율)
- 성능 데이터 (기간)
- 트레이싱 정보 (OpenTelemetry 스팬)
핵심 관계 (Key Relationships)
- 하나의 Dataset → 많은 Experiment: 같은 데이터셋을 서로 다른 작업 구현에 대해 실행하거나 여러 번 실행해 변화를 추적할 수 있어요
- 하나의 Experiment → 하나의 Report:
dataset.evaluate(...)를 호출할 때마다 보고서 하나를 받아요 - 하나의 Experiment → 많은 Case 결과: 보고서는 데이터셋의 모든 케이스에 대한 결과를 포함해요
다음 단계 (Next Steps)
- 평가자 개요 — 서로 다른 평가자 유형을 언제 쓸지
- 내장 평가자 — 제공되는 평가자의 전체 참조
- 사용자 정의 평가자 — 자신만의 평가 로직 작성
- 보고서 평가자 — 실험 전체 분석 (혼동 행렬, PR 곡선 등)
- 데이터셋 관리 — 데이터셋 저장, 로드, 생성