데이터셋 관리
데이터셋 관리 (Dataset Management)
평가 데이터셋을 만들고, 저장하고, 불러오고, 생성해요.
출처: 문서
본문
데이터셋 만들기
코드로부터
데이터셋을 Python에서 직접 정의해요:
from typing import Any
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import EqualsExpected, IsInstance
dataset = Dataset[str, str, Any](
name='my_eval_suite',
cases=[
Case(
name='test_1',
inputs='input 1',
expected_output='output 1',
),
Case(
name='test_2',
inputs='input 2',
expected_output='output 2',
),
],
evaluators=[
IsInstance(type_name='str'),
EqualsExpected(),
],
)
케이스 동적으로 추가하기
from typing import Any
from pydantic_evals import Dataset
from pydantic_evals.evaluators import IsInstance
dataset = Dataset[str, str, Any](name='dynamic_dataset', cases=[], evaluators=[])
# 케이스 하나씩 추가
dataset.add_case(
name='dynamic_case',
inputs='test input',
expected_output='test output',
)
# 평가자 추가
dataset.add_evaluator(IsInstance(type_name='str'))
데이터셋 저장
상세 직렬화 가이드
직렬화 형식, JSON 스키마 생성, 커스텀 평가자에 대한 완전한 상세 내용은 데이터셋 직렬화를 참고해요.
YAML로 저장
from typing import Any
from pydantic_evals import Case, Dataset
dataset = Dataset[str, str, Any](name='my_eval_suite', cases=[Case(name='test', inputs='example')])
dataset.to_file('my_dataset.yaml')
# 스키마 파일도 저장됨: my_dataset_schema.json
출력 (my_dataset.yaml):
# yaml-language-server: $schema=my_dataset_schema.json
name: my_eval_suite
cases:
- name: test_1
inputs: input 1
expected_output: output 1
evaluators:
- EqualsExpected
- name: test_2
inputs: input 2
expected_output: output 2
evaluators:
- EqualsExpected
evaluators:
- IsInstance: str
JSON으로 저장
from typing import Any
from pydantic_evals import Case, Dataset
dataset = Dataset[str, str, Any](name='my_eval_suite', cases=[Case(name='test', inputs='example')])
dataset.to_file('my_dataset.json')
# 스키마 파일도 저장됨: my_dataset_schema.json
커스텀 스키마 경로
from pathlib import Path
from typing import Any
from pydantic_evals import Case, Dataset
dataset = Dataset[str, str, Any](name='my_eval_suite', cases=[Case(name='test', inputs='example')])
# 커스텀 스키마 위치
Path('data').mkdir(exist_ok=True)
Path('data/schemas').mkdir(parents=True, exist_ok=True)
dataset.to_file(
'data/my_dataset.yaml',
schema_path='schemas/my_schema.json',
)
# 스키마 파일 없음
dataset.to_file('my_dataset.yaml', schema_path=None)
데이터셋 불러오기
YAML/JSON에서
from typing import Any
from pydantic_evals import Dataset
# 확장자에서 형식 추론
dataset = Dataset[str, str, Any].from_file('my_dataset.yaml')
dataset = Dataset[str, str, Any].from_file('my_dataset.json')
# 비표준 확장자용 명시적 형식
dataset = Dataset[str, str, Any].from_file('data.txt', fmt='yaml')
문자열에서
from typing import Any
from pydantic_evals import Dataset
yaml_content = """
name: my_tests
cases:
- name: test
inputs: hello
expected_output: HELLO
evaluators:
- EqualsExpected
"""
dataset = Dataset[str, str, Any].from_text(yaml_content, fmt='yaml')
Dict에서
from typing import Any
from pydantic_evals import Dataset
data = {
'name': 'my_tests',
'cases': [
{
'name': 'test',
'inputs': 'hello',
'expected_output': 'HELLO',
},
],
'evaluators': [{'EqualsExpected': {}}],
}
dataset = Dataset[str, str, Any].from_dict(data)
커스텀 평가자와 함께
커스텀 평가자를 사용하는 데이터셋을 불러올 때는 from_file()에 그들을 전달해야 해요:
from dataclasses import dataclass
from typing import Any
from pydantic_evals import Dataset
from pydantic_evals.evaluators import Evaluator, EvaluatorContext
@dataclass
class MyCustomEvaluator(Evaluator):
threshold: float = 0.5
def evaluate(self, ctx: EvaluatorContext) -> bool:
return True
# 커스텀 평가자 레지스트리로 불러오기
dataset = Dataset[str, str, Any].from_file(
'my_dataset.yaml',
custom_evaluator_types=[MyCustomEvaluator],
)
커스텀 평가자와의 직렬화에 대한 완전한 상세 내용은 데이터셋 직렬화를 참고해요.
데이터셋 생성
Pydantic Evals는 generate_dataset로 LLM을 사용해 테스트 데이터셋을 생성하게 해줘요.
데이터셋을 JSON이나 YAML로 생성해요. 두 형식 모두에서 Pydantic Evals는 데이터셋 옆에 JSON Schema를 쓰고 그 파일에서 참조해요. 지원되는 편집기는 그런 다음 타입 확인과 자동 완성을 제공할 수 있어요.
generate_dataset_example.py
from __future__ import annotations
from pathlib import Path
from pydantic import BaseModel, Field
from pydantic_evals import Dataset
from pydantic_evals.generation import generate_dataset
class QuestionInputs(BaseModel, use_attribute_docstrings=True): # (1)
"""Model for question inputs."""
question: str
"""A question to answer"""
context: str | None = None
"""Optional context for the question"""
class AnswerOutput(BaseModel, use_attribute_docstrings=True): # (2)
"""Model for expected answer outputs."""
answer: str
"""The answer to the question"""
confidence: float = Field(ge=0, le=1)
"""Confidence level (0-1)"""
class MetadataType(BaseModel, use_attribute_docstrings=True): # (3)
"""Metadata model for test cases."""
difficulty: str
"""Difficulty level (easy, medium, hard)"""
category: str
"""Question category"""
async def main():
dataset = await generate_dataset( # (4)
dataset_type=Dataset[QuestionInputs, AnswerOutput, MetadataType],
n_examples=2,
extra_instructions="""
Generate question-answer pairs about world capitals and landmarks.
Make sure to include both easy and challenging questions.
""",
)
output_file = Path('questions_cases.yaml')
dataset.to_file(output_file) # (5)
print(output_file.read_text(encoding='utf-8'))
"""
# yaml-language-server: $schema=questions_cases_schema.json
name: generated
cases:
- name: Easy Capital Question
inputs:
question: What is the capital of France?
context: null
metadata:
difficulty: easy
category: Geography
expected_output:
answer: Paris
confidence: 0.95
evaluators:
- EqualsExpected
- name: Challenging Landmark Question
inputs:
question: Which world-famous landmark is located on the banks of the Seine River?
context: null
metadata:
difficulty: hard
category: Landmarks
expected_output:
answer: Eiffel Tower
confidence: 0.9
evaluators:
- EqualsExpected
evaluators: []
report_evaluators: []
"""
작업 입력에 대한 스키마를 정의해요.
작업의 기대 출력에 대한 스키마를 정의해요.
테스트 케이스의 메타데이터에 대한 스키마를 정의해요.
generate_dataset을 호출해 스키마에 맞는 2개 케이스가 있는 Dataset을 만든다. (4)
데이터셋을 YAML 파일에 저장해요. 이렇게 하면 questions_cases.yaml을 더 쉽게 편집할 수 있도록 JSON Schema인 questions_cases_schema.json도 써져요. 마법 같은 yaml-language-server 주석은 vscode, jetbrains/pycharm에서 최소한 지원돼요 (자세한 내용은 여기).
(이 예시를 실행하려면 asyncio를 import하고 asyncio.run(main())을 추가해요. 다른 변경은 필요 없어요.)
데이터셋을 JSON 파일로도 쓸 수 있어요:
generate_dataset_example_json.py
from pathlib import Path
from pydantic_evals import Dataset
from pydantic_evals.generation import generate_dataset
from generate_dataset_example import AnswerOutput, MetadataType, QuestionInputs
async def main():
dataset = await generate_dataset( # (1)
dataset_type=Dataset[QuestionInputs, AnswerOutput, MetadataType],
n_examples=2,
extra_instructions="""
Generate question-answer pairs about world capitals and landmarks.
Make sure to include both easy and challenging questions.
""",
)
output_file = Path('questions_cases.json')
dataset.to_file(output_file) # (2)
print(output_file.read_text(encoding='utf-8'))
"""
{
"$schema": "questions_cases_schema.json",
"name": "generated",
"cases": [
{
"name": "Easy Capital Question",
"inputs": {
"question": "What is the capital of France?",
"context": null
},
"metadata": {
"difficulty": "easy",
"category": "Geography"
},
"expected_output": {
"answer": "Paris",
"confidence": 0.95
},
"evaluators": [
"EqualsExpected"
]
},
{
"name": "Challenging Landmark Question",
"inputs": {
"question": "Which world-famous landmark is located on the banks of the Seine River?",
"context": null
},
"metadata": {
"difficulty": "hard",
"category": "Landmarks"
},
"expected_output": {
"answer": "Eiffel Tower",
"confidence": 0.9
},
"evaluators": [
"EqualsExpected"
]
}
],
"evaluators": [],
"report_evaluators": []
}
"""
위와 똑같이 Dataset을 생성해요.
데이터셋을 JSON 파일에 저장해요. 이렇게 하면 questions_cases.json의 JSON Schema인 questions_cases_schema.json도 써져요. $schema 키는 파일을 편집하는 동안 편집기에 어떤 스키마를 사용할지 알려줘요. 공식 명세는 아니지만 VS Code와 PyCharm에서 동작해요. 토론을 참고해요.
(이 예시를 실행하려면 asyncio를 import하고 asyncio.run(main())을 추가해요. 다른 변경은 필요 없어요.)
타입 안전 데이터셋
타입 안전을 위해 제네릭 타입 매개변수를 사용해요:
from typing_extensions import TypedDict
from pydantic_evals import Case, Dataset
class MyInput(TypedDict):
query: str
max_results: int
class MyOutput(TypedDict):
results: list[str]
class MyMetadata(TypedDict):
category: str
# 타입 안전 데이터셋
dataset: Dataset[MyInput, MyOutput, MyMetadata] = Dataset(
name='typed_dataset',
cases=[
Case(
name='test',
inputs={'query': 'test', 'max_results': 10},
expected_output={'results': ['a', 'b']},
metadata={'category': 'search'},
),
],
)
스키마 생성
IDE 지원을 위한 JSON Schema를 생성해요:
from typing import Any
from pydantic_evals import Case, Dataset
dataset = Dataset[str, str, Any](name='my_eval_suite', cases=[Case(name='test', inputs='example')])
# 스키마와 함께 저장
dataset.to_file('my_dataset.yaml') # my_dataset_schema.json 생성
# 스키마가 가능하게 하는 것:
# - VS Code/PyCharm에서 자동 완성
# - 편집 중 검증
# - 인라인 문서화
수동 스키마 생성:
import json
from dataclasses import dataclass
from typing import Any
from pydantic_evals import Dataset
from pydantic_evals.evaluators import Evaluator, EvaluatorContext
@dataclass
class MyCustomEvaluator(Evaluator):
threshold: float = 0.5
def evaluate(self, ctx: EvaluatorContext) -> bool:
return True
schema = Dataset[str, str, Any].model_json_schema_with_evaluators(
custom_evaluator_types=[MyCustomEvaluator],
)
print(json.dumps(schema, indent=2)[:66] + '...')
"""
{
"$defs": {
"Case": {
"additionalProperties": false,
...
"""
모범 사례
1. 명확한 이름 사용하기
from pydantic_evals import Case
# 좋음
Case(name='uppercase_basic_ascii', inputs='hello')
Case(name='uppercase_unicode_emoji', inputs='hello 😀')
Case(name='uppercase_empty_string', inputs='')
# 나쁨
Case(name='test1', inputs='hello')
Case(name='test2', inputs='world')
Case(name='test3', inputs='foo')
2. 난이도로 구성하기
from pydantic_evals import Case, Dataset
dataset = Dataset(
name='organized_by_difficulty',
cases=[
Case(name='easy_1', inputs='test', metadata={'difficulty': 'easy'}),
Case(name='easy_2', inputs='test2', metadata={'difficulty': 'easy'}),
Case(name='medium_1', inputs='test3', metadata={'difficulty': 'medium'}),
Case(name='hard_1', inputs='test4', metadata={'difficulty': 'hard'}),
],
)
3. 작게 시작하고 점진적으로 키우기
from pydantic_evals import Case, Dataset
# 대표 케이스로 시작
dataset = Dataset(
name='starting_small',
cases=[
Case(name='happy_path', inputs='test'),
Case(name='edge_case', inputs=''),
Case(name='error_case', inputs='invalid'),
],
)
# 문제를 발견하면서 더 추가
dataset.add_case(name='newly_discovered_edge_case', inputs='edge')
4. 적절한 곳에 케이스별 평가자 사용하기
케이스별 평가자는 각 케이스가 서로 다른 평가 기준을 갖게 해줘서, 포괄적인 "테스트 커버리지"에 필수적이에요. 만능 평가자를 쓰려고 하지 말고, 각 시나리오에서 "좋다"는 것이 무엇인지 정확히 지정할 수 있어요. 특히 LLMJudge 평가자와 함께 강력한데, 케이스마다 세밀한 요구사항을 서술할 수 있어 골든 데이터셋을 쉽게 만들고 유지할 수 있어요. 자세한 지침은 케이스별 평가자를 참고해요.
5. 목적별로 데이터셋 분리하기
from typing import Any
from pydantic_evals import Case, Dataset
# 먼저 몇 가지 테스트 데이터셋 만들기
for name in ['smoke_tests', 'comprehensive_tests', 'regression_tests']:
test_dataset = Dataset[str, Any, Any](name=name, cases=[Case(name='test', inputs='example')])
test_dataset.to_file(f'{name}.yaml')
# Smoke 테스트 (빠르고, 중요한 경로)
smoke_tests = Dataset[str, Any, Any].from_file('smoke_tests.yaml')
# 포괄 테스트 (느리고, 철저)
comprehensive = Dataset[str, Any, Any].from_file('comprehensive_tests.yaml')
# 회귀 테스트 (특정 버그)
regression = Dataset[str, Any, Any].from_file('regression_tests.yaml')
다음 단계
더 알아보기 (Learn more)
- Pydantic Evals 문서: 데이터셋 관리