ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Pydantic Field로 LLM 출력 스키마를 제약하는 방법
    IT 2026. 6. 23. 23:00
    Pydantic Field로 LLM 출력 스키마를 제약하는 방법

    이전 글에서는 LLM 구조화 출력을 구현하는 두 전략 — ProviderStrategy와 ToolStrategy — 중 어떤 것을 선택해야 하는지 다뤘다. 전략을 정했다면 다음 질문은 스키마를 어떻게 설계하느냐다. 스키마가 부실하면 전략이 무엇이든 LLM이 잘못된 값을 채운다. 그 스키마의 도구가 Pydantic이다.

    Pydantic이란

    Pydantic은 Python 타입 어노테이션을 그대로 검증 규칙으로 쓰는 데이터 검증 라이브러리다. 2017년 Samuel Colvin이 만들었고, FastAPI가 요청/응답 스키마를 Pydantic으로 선언하면서 폭발적으로 보급됐다. 2023년에 v2로 재작성되면서 내부 엔진이 Rust로 바뀌었고 검증 속도가 v1 대비 5~10배 빨라졌다.

    Pydantic이 LLM 생태계에 깊숙이 들어온 것은 자연스러운 흐름이었다. LangChain, LlamaIndex 같은 프레임워크가 LLM 출력 파싱에 Pydantic을 표준으로 채택하면서, "LLM에게 어떤 형태로 답하게 할 것인가"를 Python 클래스로 선언하는 패턴이 정착했다. BaseModel을 상속한 클래스 하나가 LLM의 응답 형식을 완전히 결정한다.

    diagram

    Pydantic이 구조화 출력 파이프라인에서 하는 역할. 선언한 모델은 두 방향으로 활용된다 — LLM에게는 "이렇게 채워라"는 명세가 되고, 응답이 돌아오면 검증기가 된다. 타입이 맞지 않거나 범위를 벗어난 값이 오면 Pydantic이 에러를 낸다.

    Field의 세 가지 제약

    Field는 Pydantic의 필드 선언 함수다. 기본값 외에 LLM에게 전달되는 힌트검증 규칙을 함께 담을 수 있다. 세 가지가 핵심이다.

    diagram

    Field 파라미터 역할 계층도. description은 LLM이 필드의 의도를 이해하는 단서다. LLM은 스키마 전체를 프롬프트에서 읽고 "이 필드에 어떤 값을 채워야 하는가"를 판단한다. 설명이 모호하면 LLM이 임의로 해석한다. Literal은 허용 값의 열거형이다 — 스키마를 받은 LLM은 자동으로 이 목록에서 하나를 고른다. ge/le는 수치 필드의 경계다 — LLM이 경계 밖의 값을 생성해도 Pydantic이 검증 단계에서 잡아낸다.

    MovieReview 스키마 전체

    세 제약을 모두 담은 영화 리뷰 분석 스키마다.

    from pydantic import BaseModel, Field
    from typing import Literal
    
    class MovieReview(BaseModel):
        # description은 LLM이 읽는 힌트 — 정확할수록 출력 품질이 높아진다
        genre: Literal["action", "romance", "horror", "comedy", "documentary"] = Field(
            description="영화 장르. 반드시 5가지 중 하나"
        )
        star_rating: int = Field(
            description="별점. 1(매우 별로)~5(매우 만족)",
            ge=1,   # greater than or equal: 1 이상
            le=5,   # less than or equal: 5 이하
        )
        keywords: list[str] = Field(description="리뷰에서 추출한 핵심 키워드 목록")
        recommend: bool = Field(description="다른 사람에게 추천할 만한 영화인지 여부")
    

    코드 구조 풀이. genreLiteral로 5가지 값만 허용한다. LLM은 이 목록을 보고 "action", "romance" 등에서 적합한 것을 고른다 — "SF 액션"처럼 자유 텍스트를 생성하지 않는다. star_ratingge=1, le=5로 범위를 잡는다. description에 "(매우 별로)~(매우 만족)" 같은 척도 설명을 붙이면 LLM이 리뷰 감정의 강도를 숫자로 매핑한다. recommend는 bool이라 true/false만 나온다 — LLM이 "볼 만한 것 같습니다" 같은 모호한 표현을 쓸 여지가 없다.

    중첩 스키마 — 리스트 안의 모델

    단일 모델로 충분하지 않을 때는 스키마를 중첩한다. 시청 목록처럼 "항목의 배열"이 필요한 경우가 대표적이다.

    from pydantic import BaseModel, Field
    
    class MovieEntry(BaseModel):
        title: str = Field(description="영화 제목")
        star_rating: int = Field(description="별점 1~5", ge=1, le=5)
    
    class WatchList(BaseModel):
        # MovieEntry 모델의 리스트를 movies 필드에 담는다
        movies: list[MovieEntry] = Field(description="시청한 영화 목록")
        total_count: int = Field(description="총 시청 편수", ge=0)
        comment: str = Field(description="전체적인 소감. 없으면 빈 문자열")
    

    중첩 스키마 구조 풀이. WatchList.movieslist[MovieEntry]다. LLM이 이 스키마를 받으면 movies 배열의 각 원소를 MovieEntry 형식에 맞게 채운다. "인터스텔라 5점, 기생충 4점"이라는 입력이 들어오면 [{"title": "인터스텔라", "star_rating": 5}, {"title": "기생충", "star_rating": 4}]으로 구조화된다. 함정: comment처럼 "없으면 빈 문자열"을 명시하지 않으면 LLM이 null을 넣거나 필드를 생략할 수 있다. Pydantic 기본 설정에서 str 타입은 None을 허용하지 않으므로 검증 에러가 난다 — description에서 기본값 처리를 명시하는 게 안전하다.

    필수 vs 선택 필드 — Optional과 기본값

    모든 필드가 항상 채워지는 것은 아니다. 정보가 부족하거나 상황에 따라 값이 없을 수 있는 필드는 Optional로 선언하고 기본값을 지정해야 한다. 이 처리 없이 None이 들어오면 Pydantic이 검증 에러를 낸다.

    class MovieDetailReview(BaseModel):
        star_rating: int = Field(description="별점 1~5", ge=1, le=5)
        summary: str = Field(description="리뷰 요약 (1~2문장)")
        # 선택적 필드: 항상 존재하지 않아도 됨
        sequel_expected: bool | None = Field(
            default=None,
            description="속편이 나올 것 같은지 여부. 리뷰에서 판단하기 어려우면 null"
        )
    

    Optional 필드 설계 원칙. sequel_expectedbool | None으로 선언되어 있다. LLM이 리뷰 내용만으로 속편 여부를 판단하기 어려울 때 null을 반환할 수 있고, Pydantic은 이를 None으로 받아들인다. 핵심은 description에 "판단하기 어려우면 null"이라고 명시하는 것이다 — 이 지시가 없으면 LLM이 null 대신 false를 추측해 넣거나, 반대로 true를 임의로 선택한다. default=None이 있으므로 LLM이 이 필드를 아예 생략해도 Pydantic 검증을 통과한다. 반면 star_ratingsummary는 기본값이 없어 LLM이 반드시 값을 채워야 한다 — 이 비대칭이 필수·선택 필드의 의미를 명확히 한다.

    description이 출력 품질을 결정한다

    스키마를 정의할 때 가장 많이 빠지는 함정은 description을 형식적으로 쓰는 것이다. "영화 장르"처럼 필드 이름을 반복하는 description은 LLM에게 정보를 주지 못한다.

    좋은 description은 세 가지를 담는다. 첫째, 이 필드가 무엇을 표현하는가. 둘째, 어떤 기준으로 값을 결정하는가. 셋째, 경계 조건은 어떻게 처리하는가. "별점"보다 "별점. 1(매우 별로)~5(매우 만족). 지루하면 1~2, 무난하면 3, 인상적이면 4~5"가 훨씬 낫다. LLM은 이 설명을 읽고 리뷰어의 감정 강도를 숫자로 매핑한다.

    결국 Pydantic 스키마는 LLM과의 계약서다. Literal로 허용 값을 열거하고, ge/le로 수치 범위를 잡고, description으로 의미를 전달하면 LLM의 자유도는 줄어들고 출력의 예측 가능성은 올라간다. 이전 글에서 전략을 고르는 것이 "어떤 도로를 달릴까"라면, 스키마를 잘 설계하는 것은 "교통 표지판을 제대로 세우는 것"이다. 파이프라인이 안정적으로 동작하려면 LLM을 신뢰하기보다 스키마가 LLM을 제약해야 한다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.