Pydantic Evals
Pydantic Evals
Pydantic Evals는 간단한 LLM 호출부터 복잡한 멀티 에이전트 애플리케이션까지, AI 시스템을 체계적으로 테스트하고 평가하기 위한 강력한 평가 프레임워크예요. 에이전트의 최종 출력과 그 궤적(trajectory)(도구 호출의 순서와 인자)을 코드의 데이터셋에 대해, 또는 온라인 평가를 통해 실제 운영 트래픽의 샘플에 대해 채점해요.
설계 철학 (Design Philosophy)
코드 우선 접근 (Code-First Approach)
Pydantic Evals는 모든 평가 구성 요소를 Python으로 정의하는 코드 우선 철학을 따라요. 웹 기반 구성을 쓰는 플랫폼과는 다르죠. 평가를 코드로 작성해 실행하고, 결과를 디스크에 쓰거나 터미널 또는 Pydantic Logfire에서 볼 수 있어요.
Evals는 떠오르는 실천 (Evals are an Emerging Practice)
단위 테스트와 달리 evals는 떠오르는 예술/과학이에요. 당신의 evals가 정확히 어떻게 정의되어야 하는지 아는 척하는 사람은 안심하고 무시해도 돼요. 우리는 Pydantic Evals를 너무 독단적이지 않으면서도 유연하고 유용하게 설계했어요.
빠른 탐색 (Quick Navigation)
시작하기 (Getting Started):
평가자 (Evaluators):
- 평가자 개요 — 평가자 유형을 비교하고 각 접근법을 언제 쓸지 학습하기
- 내장 평가자 — exact match, 인스턴스 검사 등 바로 쓸 수 있는 평가자 전체 참조
- LLM as a Judge — 주관적 품질, 복잡한 기준, 자연어 출력을 평가할 때 LLM 사용
- 사용자 정의 평가자 — 도메인 특화 채점 로직과 사용자 정의 평가 지표 구현
- Span 기반 평가 — OpenTelemetry 트레이스를 사용해 내부 에이전트 동작(도구 호출, 실행 흐름) 평가. 정답이 최종 출력뿐 아니라 어떻게 도달했는지에 달린 복잡한 에이전트에 필수적이에요. 에벌 어서션과 운영 텔레메트리가 일치하게도 보장하죠.
- Agentic 평가자 — 에이전트의 궤적, 즉 도구 호출의 순서와 인자를 최종 출력뿐 아니라 채점
온라인 평가 (Online Evaluation):
- 온라인 평가 — 평가자를 운영 또는 스테이징 트래픽에 붙여 모든 호출 또는 샘플링된 부분집합을 백그라운드로 채점
How-To 가이드:
- Logfire 연동 — 결과 시각화
- 데이터셋 관리 — 저장, 로드, 생성
- 동시성 및 성능 — 병렬 실행 제어
- 재시도 전략 — 일시적 실패 처리
- 지표 및 속성 — 사용자 정의 데이터 추적
- 케이스 수명주기 훅 — 케이스별 설정, 정리, 컨텍스트 강화
예제 (Examples):
- 단순 검증 — 기본 예제
참조 (Reference):
출처: 문서
본문
코드 우선 평가 (Code-First Evaluation)
Pydantic Evals는 코드 우선 접근을 따라요. 모든 평가 구성 요소(데이터셋, 실험, 작업, 케이스, 평가자)를 Python 코드로 정의하거나, Python 코드가 로드하는 직렬화된 데이터로 정의하죠. 완전히 웹 기반 구성을 쓰는 플랫폼과는 달라요.
_Experiment_를 실행하면 진행 표시기를 보고, Python 코드를 실행하는 곳(IDE, 터미널 등) 어디에서든 결과를 출력할 수 있어요. 또한 직렬화해서 저장하거나 노트북이나 다른 애플리케이션으로 보내 추가 시각화·분석할 수 있는 보고서 객체도 받아요.
Pydantic Logfire를 쓰고 있다면, 실험 결과가 자동으로 Logfire 웹 인터페이스에 나타나 시각화·비교·공동 분석이 가능해져요. Logfire는 관측 가능성 계층으로 기능해요. 코드로 평가를 작성·실행하고, 결과를 웹 UI에서 보고 분석하는 식이죠.
설치 (Installation)
Pydantic Evals 패키지를 설치하려면 다음을 실행하세요.
Terminal
pip install pydantic-evals
Terminal
uv add pydantic-evals
pydantic-evals는 pydantic-ai에 의존하지 않지만, 평가에 OpenTelemetry 트레이스를 쓰거나 평가 결과를 logfire로 보내고 싶다면 logfire에 선택적 의존성이 있어요.
Terminal
pip install 'pydantic-evals[logfire]'
Terminal
uv add 'pydantic-evals[logfire]'
Pydantic Evals 데이터 모델
Pydantic Evals는 단순한 데이터 모델을 중심으로 만들어져요.
데이터 모델 다이어그램
Dataset (1) ──────────── (Many) Case
│ │
│ │
└─── (Many) Experiment ──┴─── (Many) Case results
│
└─── (1) Task
│
└─── (Many) Evaluator
핵심 관계 (Key Relationships)
- Dataset → Cases: 하나의 Dataset은 많은 Case를 포함해요
- Dataset → Experiments: 하나의 Dataset은 시간에 따라 여러 Experiment에 걸쳐 사용될 수 있어요
- Experiment → Case results: 하나의 Experiment는 각 Case를 실행해 결과를 만들어요
- Experiment → Task: 하나의 Experiment는 정의된 하나의 Task를 평가해요
- Experiment → Evaluators: 하나의 Experiment는 여러 Evaluator를 사용해요. Dataset 전체 평가자는 모든 Case에 대해 실행되고, Case별 평가자는 해당 Case에 대해 실행돼요
데이터 흐름 (Data Flow)
- 데이터셋 생성: YAML/JSON으로, 또는 Python에서 직접 케이스와 평가자를 정의
- 실험 실행:
dataset.evaluate_sync(task_function)실행 - 케이스 실행: 각 Case가 Task에 대해 실행
- 평가: 평가자가 각 Case의 Task 출력을 채점
- 결과: 모든 Case 결과가 요약 보고서로 모아짐
은유 (A metaphor)
유용한 은유(완벽하진 않지만)로 evals를 단위 테스트(Unit Testing) 프레임워크처럼 생각하면 돼요.
- Cases + Evaluators는 개별 단위 테스트예요. 각각이 테스트하려는 특정 시나리오(입력과 예상 결과 포함)를 정의하죠. 단위 테스트처럼 케이스는 "이 입력이 주어졌을 때, 내 시스템이 올바른 출력을 내나요?" 를 묻는 거예요.
- Datasets는 테스트 스위트와 같아요. 단위 테스트를 함께 묶어주는 발판이죠. 관련 케이스들을 그룹화하고 스위트의 모든 테스트에 적용되어야 할 공유 평가 기준을 정의해요.
- Experiments는 전체 테스트 스위트를 실행해 보고서를 얻는 것과 같아요.
dataset.evaluate_sync(my_ai_function)을 실행하면 모든 케이스를 AI 시스템에 대해 실행하고 결과를 모으는 거죠.pytest를 실행해 통과·실패·성능 지표 요약을 얻는 것과 마찬가지예요.
전통적 단위 테스트와의 핵심 차이는 AI 시스템이 확률적이라는 점이에요. 타입 검사를 하면 여전히 단순한 통과/실패를 얻겠지만, 텍스트 출력의 점수는 아마 정성적/범주적일 가능성이 높고 더 주관적으로 해석될 여지가 있어요.
더 깊이 이해하려면 핵심 개념을 참고하세요.
데이터셋과 케이스 (Datasets and Cases)
Pydantic Evals에서 모든 것은 Dataset과 Case로 시작해요.
Dataset: 특정 작업이나 함수 평가를 위해 설계된 테스트 Case의 모음Case: Task 입력에 해당하는 단일 테스트 시나리오. 선택적으로 예상 출력, 메타데이터, 케이스별 평가자 포함
simple_eval_dataset.py
from pydantic_evals import Case, Dataset
case1 = Case(
name='simple_case',
inputs='What is the capital of France?',
expected_output='Paris',
metadata={'difficulty': 'easy'},
)
dataset = Dataset(name='capital_quiz', cases=[case1])
(이 예제는 완전해서 그대로 실행할 수 있어요)
데이터셋의 저장·로드·생성에 대해 알려면 데이터셋 관리를 참고하세요.
평가자 (Evaluators)
Evaluator는 Task를 Case에 대해 테스트했을 때의 결과를 분석하고 채점해요.
이것은 결정적 코드 기반 검사(정규식으로 모델 출력 형식 테스트, PII나 민감 데이터 등장 확인)일 수도 있고, 정확성, 정밀도/재현율, 환각, 지시 준수 같은 품질을 위해 비결정적 모델 출력을 평가하는 것일 수도 있어요.
두 종류의 테스트 모두 LLM 시스템에서 유용하지만, 고전적인 코드 기반 테스트는 모델 출력에 대한 사람 또는 기계 검토가 필요한 테스트보다 저렴하고 쉬워요.
Pydantic Evals는 몇 가지 내장 평가자를 포함하며 사용자 정의 평가자도 정의할 수 있어요.
simple_eval_evaluator.py
from dataclasses import dataclass
from pydantic_evals.evaluators import Evaluator, EvaluatorContext
from pydantic_evals.evaluators.common import IsInstance
from simple_eval_dataset import dataset
dataset.add_evaluator(IsInstance(type_name='str')) # (1)
@dataclass
class MyEvaluator(Evaluator):
async def evaluate(self, ctx: EvaluatorContext[str, str]) -> float: # (2)
if ctx.output == ctx.expected_output:
return 1.0
elif (
isinstance(ctx.output, str)
and ctx.expected_output.lower() in ctx.output.lower()
):
return 0.8
else:
return 0.0
dataset.add_evaluator(MyEvaluator())
add_evaluator 메서드로 데이터셋에 내장 평가자를 추가할 수 있어요.
이 사용자 정의 평가자는 출력이 예상 출력과 일치하는지에 따라 단순한 점수를 반환해요.
(이 예제는 완전해서 그대로 실행할 수 있어요)
더 알아보기:
- 평가자 개요 — 서로 다른 유형을 언제 쓸지
- 내장 평가자 — 전체 참조
- LLM Judge — LLM을 평가자로 사용
- 사용자 정의 평가자 — 자신만의 로직 작성
- Span 기반 평가 — 실행 트레이스 분석
- Agentic 평가자 — 에이전트의 궤적 채점
실험 실행 (Running Experiments)
평가를 수행하는 것은 데이터셋의 모든 케이스에 대해 작업을 실행하는 것, 즉 "실험"을 실행하는 것이에요.
위 두 예제를 합치고 더 선언적인 evaluators kwarg를 Dataset에 사용하면:
simple_eval_complete.py
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import Evaluator, EvaluatorContext, IsInstance
case1 = Case( # (1)
name='simple_case',
inputs='What is the capital of France?',
expected_output='Paris',
metadata={'difficulty': 'easy'},
)
class MyEvaluator(Evaluator[str, str]):
def evaluate(self, ctx: EvaluatorContext[str, str]) -> float:
if ctx.output == ctx.expected_output:
return 1.0
elif (
isinstance(ctx.output, str)
and ctx.expected_output.lower() in ctx.output.lower()
):
return 0.8
else:
return 0.0
dataset = Dataset(
name='capital_quiz',
cases=[case1],
evaluators=[IsInstance(type_name='str'), MyEvaluator()], # (2)
)
async def guess_city(question: str) -> str: # (3)
return 'Paris'
report = dataset.evaluate_sync(guess_city) # (4)
report.print(include_input=True, include_output=True, include_durations=False) # (5)
"""
Evaluation Summary: guess_city
┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Case ID ┃ Inputs ┃ Outputs ┃ Scores ┃ Assertions ┃
┡━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ simple_case │ What is the capital of France? │ Paris │ MyEvaluator: 1.00 │ ✔ │
├─────────────┼────────────────────────────────┼─────────┼───────────────────┼────────────┤
│ Averages │ │ │ MyEvaluator: 1.00 │ 100.0% ✔ │
└─────────────┴────────────────────────────────┴─────────┴───────────────────┴────────────┘
"""
위와 같이 테스트 케이스를 만듭니다.
테스트 케이스와 evaluators를 가진 Dataset을 만듭니다.
평가 대상 함수입니다.
evaluate_sync로 평가를 실행합니다. 이 함수는 데이터셋의 모든 테스트 케이스에 대해 함수를 실행하고 EvaluationReport 객체를 반환해요.
print로 보고서를 출력합니다. 평가 결과를 보여주죠. 출력이 실행마다 변하지 않도록 여기서는 duration을 생략했어요.
(이 예제는 완전해서 그대로 실행할 수 있어요)
더 많은 예제는 빠른 시작을, 병렬 실행 제어는 동시성 및 성능을 참고하세요.
API 참조 (API Reference)
모든 클래스, 메서드, 구성 옵션을 포괄적으로 다루려면 자세한 API 참조 문서를 참고하세요.
다음 단계 (Next Steps)
- 빠른 시작으로 단순한 평가부터 시작
- 핵심 개념으로 데이터 모델 이해
- 내장 평가자에서 내장 평가자 탐색
- 시각화를 위해 Logfire 연동으로 Logfire 통합
- 데이터셋 관리로 포괄적 테스트 스위트 구축
- 도메인 특화 지표를 위해 사용자 정의 평가자 구현