- FastAPI 입문의 2단계에서 Pydantic의 기초를 살펴보았다 —
BaseModel로 요청/응답 스키마를 정의하고, 타입 힌트만으로 자동 검증이 이루어지는 것을 확인했다. - 이 포스트에서는 Pydantic을 심화하여 다룬다: Field 제약 조건, 자동 타입 변환, 커스텀 validator, Enum, JSON 직렬화, 설정 관리(BaseSettings), 그리고 ABC와 결합한 Agent 계약 패턴까지 정리한다.
1 Pydantic의 역할
- Pydantic은 Python의 타입 힌트를 검증 규칙으로 사용하는 라이브러리이다.
- API의 요청/응답뿐 아니라 설정 파일 로딩, 데이터 파이프라인의 스키마 정의, 에이전트 간 계약 등 “외부에서 들어오는 데이터의 형식을 보장해야 하는 모든 곳”에서 사용된다.
FastAPI 입문에서 본 기본 패턴을 다시 확인하자:
from pydantic import BaseModel
class RunRequest(BaseModel):
text: str # 필수
history: list[dict] = [] # 선택 (기본값: 빈 리스트)
user_id: str | None = None # 선택 (기본값: None)
request = RunRequest(text="RAG란?") # 검증 + 변환이 한 줄에 끝난다타입 힌트 자체가 검증 규칙이 되므로 별도 if 문이 필요 없다. 이 포스트에서는 이 기본 패턴 위에 쌓이는 심화 기능을 하나씩 다룬다.
2 타입별 자동 변환
Pydantic v2는 가능한 경우 자동 타입 변환을 수행한다.
class Config(BaseModel):
chunk_size: int
temperature: float
verbose: bool
# 문자열 → 숫자/불리언 자동 변환
c = Config(chunk_size="1500", temperature="0.7", verbose="true")
print(c.chunk_size) # 1500 (int)
print(c.temperature) # 0.7 (float)
print(c.verbose) # True (bool)이 자동 변환 덕분에 환경 변수(항상 문자열)나 쿼리 파라미터를 별도 파싱 없이 모델에 바로 넣을 수 있다.
3 Field: 필드 제약 조건
Field로 기본값, 범위, 설명 등 세부 제약을 지정한다.
from pydantic import BaseModel, Field
class RAGConfig(BaseModel):
chunk_size: int = Field(default=1500, ge=100, le=10000, description="청크 크기 (문자 수)")
chunk_overlap: int = Field(default=400, ge=0, description="청크 겹침 크기")
top_k: int = Field(default=5, ge=1, le=50, description="검색 결과 수")
temperature: float = Field(default=0.7, ge=0, le=2, description="LLM 생성 온도")
model_name: str = Field(default="gpt-4.1", pattern=r"^gpt-", description="모델 이름")
# 범위 위반
RAGConfig(chunk_size=50) # ValidationError: ge=100 위반
RAGConfig(temperature=3) # ValidationError: le=2 위반
RAGConfig(model_name="claude-3") # ValidationError: pattern 위반| 제약 | 설명 | 적용 타입 |
|---|---|---|
ge, gt |
이상, 초과 | 숫자 |
le, lt |
이하, 미만 | 숫자 |
min_length, max_length |
문자열/리스트 길이 | str, list |
pattern |
정규식 매칭 | str |
default |
기본값 | 모든 타입 |
description |
OpenAPI 문서에 표시 | 모든 타입 |
4 중첩 모델
모델 안에 다른 모델을 포함하여 복잡한 구조를 표현한다.
class Citation(BaseModel):
source: str
page: int | None = None
section: str | None = None
class Response(BaseModel):
text: str
citations: list[Citation] = []
run_id: str
latency_ms: int
input_tokens: int = 0
output_tokens: int = 0
# 중첩 JSON을 자동으로 파싱한다
data = {
"text": "RAG는 검색 증강 생성이다.",
"citations": [
{"source": "guide.pdf", "page": 42, "section": "3.1"},
{"source": "manual.md"}
],
"run_id": "abc-123",
"latency_ms": 1500
}
resp = Response(**data)
print(resp.citations[0].source) # "guide.pdf"
print(resp.citations[1].page) # None (기본값)5 JSON 직렬화/역직렬화
# Python 객체 → JSON 문자열
json_str = resp.model_dump_json()
# '{"text":"RAG는...","citations":[...],"run_id":"abc-123",...}'
# Python 객체 → dict
d = resp.model_dump()
# {'text': 'RAG는...', 'citations': [...], 'run_id': 'abc-123', ...}
# JSON 문자열 → Python 객체
resp2 = Response.model_validate_json(json_str)
# dict → Python 객체
resp3 = Response.model_validate(d)Pydantic v2에서는 .dict() → .model_dump(), .json() → .model_dump_json(), .parse_raw() → .model_validate_json() 으로 메서드명이 변경되었다. v1 메서드는 deprecated이다.
6 Validator: 커스텀 검증 로직
타입과 범위 검증만으로 부족할 때 커스텀 validator를 정의한다.
from pydantic import BaseModel, field_validator
class RunRequest(BaseModel):
text: str
history: list[dict] = []
temperature: float = 0.7
@field_validator("text")
@classmethod
def text_must_not_be_empty(cls, v: str) -> str:
if not v.strip():
raise ValueError("빈 문자열은 허용되지 않는다")
return v.strip()
@field_validator("history")
@classmethod
def validate_history_format(cls, v: list[dict]) -> list[dict]:
for item in v:
if "role" not in item or "content" not in item:
raise ValueError("history 항목에 role과 content가 필요하다")
return v
RunRequest(text=" ") # ValidationError: 빈 문자열은 허용되지 않는다
RunRequest(text="질문", history=[{"msg": "hi"}]) # ValidationError: role과 content 필요7 Enum으로 선택지 제한
from enum import Enum
class AgentMode(str, Enum):
data = "data"
code = "code"
class AgentParams(BaseModel):
mode: AgentMode = AgentMode.data
stream: bool = False
# 유효한 값만 허용
AgentParams(mode="data") # OK
AgentParams(mode="code") # OK
AgentParams(mode="chat") # ValidationError: 'chat'은 AgentMode에 없다str과 Enum을 동시에 상속하면 JSON에서 문자열로 직접 비교할 수 있다.
8 FastAPI와의 결합 요약
FastAPI 입문의 2~3단계에서 다룬 내용을 요약한다. Pydantic 모델은 FastAPI에서 세 가지 역할을 한다.
8.1 요청 모델 (Request Body)
8.2 응답 모델 (Response Model)
class RunResponse(BaseModel):
response: Response
experiment_id: str | None = None
arm_id: str | None = None
@app.post("/agents/qna_chatbot/run", response_model=RunResponse)
def run_agent(request: RunRequest):
result = agent.run(request)
return RunResponse(response=result)response_model을 지정하면 FastAPI가 반환값을 해당 모델로 직렬화한다. 모델에 정의되지 않은 필드는 응답에서 자동 제거되어 내부 구현이 노출되지 않는다.
8.3 OpenAPI 스키마 자동 생성
Pydantic 모델의 필드명, 타입, Field의 description이 Swagger UI에 자동으로 표시된다. API 문서를 별도로 작성할 필요가 없다.
9 설정 관리: BaseSettings
환경 변수를 Pydantic으로 관리한다.
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
azure_openai_endpoint: str
azure_openai_key: str
llm_provider: str = "azure"
warmup_on_startup: bool = True
log_level: str = "INFO"
model_config = {"env_file": ".env", "env_file_encoding": "utf-8"}
settings = Settings()
# .env 파일 또는 환경변수에서 자동으로 값을 읽어온다BaseSettings는 BaseModel을 상속하므로 동일한 타입 검증이 적용된다. 환경 변수는 항상 문자열이지만 Pydantic이 bool, int 등으로 자동 변환한다.
10 ABC + Pydantic: Agent 계약 패턴
AI Agent 시스템에서 Pydantic은 에이전트 간 계약(contract)을 정의하는 데 사용된다. 입력(Query)과 출력(Response)의 스키마를 Pydantic으로 고정하고, 에이전트 구현은 ABC(Abstract Base Class)로 강제한다.
from abc import ABC, abstractmethod
from pydantic import BaseModel
class Query(BaseModel):
text: str
history: list[dict] = []
user_id: str | None = None
class Response(BaseModel):
text: str
citations: list[Citation] = []
run_id: str
class BaseAgent(ABC):
name: str
@abstractmethod
def run(self, query: Query) -> Response:
...이 패턴의 이점:
- 새 에이전트를 추가할 때
run(Query) -> Response인터페이스만 구현하면 된다 - FastAPI 라우터는 에이전트의 내부 구현을 몰라도 동일한 요청/응답 스키마로 처리할 수 있다
- Query/Response 스키마가 변경되면 타입 에러가 컴파일 타임에 잡힌다
11 관련 주제
선행 지식
- API 기초 – JSON, HTTP, REST 개념
- FastAPI 입문 – 2단계에서 Pydantic 기초(BaseModel, 요청/응답 모델)를 다룬다. 이 포스트는 그 심화편이다
후속 주제 * CORS와 Proxy – 프론트엔드-백엔드 통신의 벽 * SSE – 실시간 스트리밍의 가벼운 선택지 * React 기초 – 컴포넌트, State, Props, Hook * React Router – SPA에서 페이지 전환 * React에서 API 호출 – fetch, 타입 안전 클라이언트 * ASGI와 uvicorn – Python 웹 서버의 구동 원리
다른 카테고리 연결
- Python 추상 베이스 클래스 (ABC) – ABC + Pydantic 계약 패턴의 기반