
Pydantic AI: 보장을 선호하는 엔지니어를 위한 타입 지정 및 테스트 가능한 에이전트
요약
Pydantic AI는 확률적인 LLM의 출력을 타입 시스템을 통해 검증하고 제어할 수 있게 돕는 에이전트 프레임워크입니다. 제네릭 타입과 Pydantic의 검증 기능을 활용하여 에이전트의 의존성과 출력 타입을 명확히 정의하고 프로덕션 환경에서의 안정성을 높입니다.
핵심 포인트
- LLM의 확률적 특성과 코드의 결정론적 특성 사이의 간극을 타입 시스템으로 해결
- Agent[Deps, Output] 구조를 통해 의존성과 출력 타입을 엄격하게 관리
- Python 타입 힌트를 활용하여 모델용 도구 스키마를 자동으로 생성
- 런타임 검증을 통해 모델의 환각(Hallucination)으로 인한 오류 방지
- 도서: Agents in Production — Building, Tracing, and Shipping Multi-Step AI You Can Trust
- 저자의 다른 저서: Observability for LLM Applications — The AI Engineer's Library (2권 시리즈)의 동반 도서
- 내 프로젝트: Hermes IDE | GitHub — Claude Code 및 기타 AI 코딩 도구를 사용하여 제품을 출시하는 개발자를 위한 IDE
- 자기소개: xgabriel.com | GitHub
당신은 결제 분쟁을 해결하는 에이전트를 출시했습니다. 데모에서는 잘 작동했습니다. 2주 후 고객 지원 티켓이 접수됩니다: 에이전트가 19달러 결제 건에 대해 4,000달러를 환불하려고 시도한 것입니다. 당신은 트레이스(trace)를 읽어봅니다. 모델은 JSON 블롭(blob)을 반환했고, 당신의 코드는 json.loads를 수행하여 amount를 추출한 뒤 이를 결제 API에 그대로 전달했습니다. 제한(cap)도 없었습니다. 타입(type)도 없었습니다. 검증(check)도 없었습니다. 모델이 숫자를 환각(hallucination)했고, 당신의 코드는 그것을 신뢰했습니다.
모델은 확률적(stochastic)입니다. 하지만 당신의 코드는 그럴 필요가 없습니다. 이 두 사실 사이의 간극에 대부분의 프로덕션 에이전트 버그가 존재하며, 바로 그 간극을 메우기 위해 Pydantic AI가 만들어졌습니다.
핵심은 타입(types)입니다
대부분의 에이전트 프레임워크는 Agent 객체와 문자열 뭉치를 제공합니다. Pydantic AI는 Agent[Deps, Output]를 제공합니다. 이는 의존성 타입(dependency type)과 출력 타입(output type)에 의해 매개변수화된 제네릭(generic)입니다. IDE와 타입 체커(type checker)가 이 매개변수들을 읽습니다. 런타임(runtime)도 마찬가지입니다.
설치 시 프레임워크와 선택 사항인 트레이싱(tracing) 추가 기능을 가져옵니다:
pip install "pydantic-ai[logfire]"
가장 기본적인 프로그램 예시:
from dataclasses import dataclass
from pydantic import BaseModel
from pydantic_ai import Agent, RunContext
...
도구(tool)는 일반 함수이며, 이 함수의 타입 힌트(type hints)는 모델이 보는 스키마(schema)가 되고, 실행 결과는 검증된 SupportReply를 반환합니다:
@agent.tool
def customer_name(ctx: RunContext[Deps]) -> str:
return ctx.deps.customer_name
...
그곳에는 세 가지 핵심적인 요소가 있습니다. deps_type은 에이전트가 사용자로부터 무엇을 필요로 하는지 선언합니다. output_type은 에이전트가 반드시 반환해야 하는 것을 선언합니다. @agent.tool은 일반 Python 함수를 래핑(wrap)하고, 해당 함수의 타입 힌트(type hints)를 읽어 모델이 인식할 수 있는 도구 스키마(tool schema)를 구축합니다.
Pydantic AI는 암시적인 기본 모델을 제공하지 않으므로, 항상 모델 문자열을 전달해야 합니다. 이 포스트에서 Anthropic의 Claude를 선택한 데에는 이유가 있습니다. Claude는 도구 스키마(tool schemas)를 밀접하게 따르며 잘 형성된 구조화된 출력(structured output)을 반환하는데, 이는 아래의 검증 계층(validation layer)이 의존하는 바로 그 기능입니다.
출력 검증은 권장 사항이 아닙니다
모델이 SupportReply로 파싱되지 않는 무언가를 반환할 때, Pydantic AI는 깨진 객체를 사용자에게 전달하지 않습니다. 대신 ValidationError를 포착하여 형식을 맞춘 뒤, 이를 수정 요청으로서 모델에 다시 보냅니다. 사용자는 검증된 객체를 받거나 깔끔한 예외(exception)를 받게 됩니다. JSON 펜스(JSON fence)가 붙어 있는 문자열을 받는 일은 절대 없습니다.
이 개념을 결제 에이전트(billing agent)에 적용하면, 타입(types)은 더 이상 단순한 문서가 아니게 됩니다. 타입은 가이드 레일(rails)이 됩니다.
from dataclasses import dataclass
from typing import Literal
from pydantic import BaseModel, Field
...
도구(tools)는 ctx.deps로부터 데이터를 읽으며, 도구의 반환 타입은 모델이 읽는 스키마로 직접 전달됩니다:
@agent.tool
def last_charge(ctx: RunContext[BillingDeps]) -> int:
"""마지막 청구 금액을 센트(cents) 단위로 반환합니다."""
...
이를 실행하면 출력은 검증된 BillingAction이거나 예외이며, 절대 가공되지 않은 문자열(raw string)이 아닙니다:
result = agent.run_sync(
"내 카드가 결제되었지만 주문 상품이 발송되지 않았습니다. 해결해 주세요.",
...
모든 어노테이션은 실제로 작동합니다. output_type=BillingAction은 반환값이 BillingAction이거나 예외임을 보장합니다. Literal["refund", "retry", "escalate"]는 액션 세트를 닫아 모델이 네 번째 옵션을 임의로 생성하는 것을 막습니다. Field(ge=0, le=20_000)은 반환 금액을 타입 시스템 내에서 200달러로 제한하며, 이는 나중에 작성하기 쉬운 사후 검사가 아닙니다. 그리고 도구 반환 타입은 모델이 읽는 스키마의 일부가 됩니다: charge_status에 대해 `
API 키도, 지연 시간(latency)도, 토큰 비용도 들지 않습니다. 테스트는 계약(contract)이 유지되는지(제한된 범위 내의 금액을 가진 BillingAction)를 확인하며, CI(지속적 통합) 환경에서 밀리초 단위로 실행됩니다.
정확한 동작을 고정해야 할 때, FunctionModel을 사용하면 주어진 메시지 세트에 대해 모델이 무엇을 반환할지 스크립트로 작성할 수 있습니다:
from pydantic_ai.models.function import FunctionModel, AgentInfo
from pydantic_ai.messages import (
ModelMessage,
...
여러분은 도구 연결(tool wiring), 의존성 주입(dependency injection), 검증(validation)과 같은 여러분 자신의 로직을 테스트하고 있는 것입니다. 모델은 고정됩니다. 확률적인(stochastic) 부분은 모킹(mocked out)되어 테스트가 결정론적(deterministic)이고 빠릅니다. 이는 여러분이 이미 데이터베이스나 HTTP 클라이언트에 적용하고 있는 것과 동일한 원칙입니다. 경계(boundary)에서 실제 의존성을 가짜(fake)로 교체하는 것입니다. agent.override가 바로 그 경계입니다.
실제 경계(seam)가 존재하는 곳
정적 타입(Static types)이 언어 모델을 결정론적으로 만들 수는 없습니다. 하지만 정적 타입이 할 수 있는 일은, 모델의 출력이 비용을 발생시키거나 상태를 변경(mutate)하는 무언가에 닿기 전에 그 출력을 제한하는 것입니다. 모델은 제안하고, 타입 시스템은 결정합니다. Literal은 집합을 닫고, Field는 범위를 제한하며, output_type은 잘못된 형태를 거부합니다. 모델이 반환하는 모든 것은 여러분이 Python에서 정의한 게이트를 통과하며, 배포 전에는 Pyright에 의해, 런타임에는 Pydantic에 의해 검사됩니다.
이미 Pydantic을 사용 중인 환경 — 2026년의 대부분의 FastAPI 백엔드 — 에서 얻는 이점은 에이전트가 마치 라우트(route)처럼 느껴지기 시작한다는 것입니다. 동일한 타입 힌트(type hints), 동일한 IDE 지원, 동일한 검증 계약(validation contract), 동일한 테스트 인체공학(test ergonomics)을 갖게 됩니다. 에이전트는 더 이상 시스템 측면에 볼트로 조여진 특별하고 무서운 존재가 아닙니다. 여러분이 논리적으로 추론할 수 있는 또 다른 타입 지정 함수(typed function)일 뿐입니다.
에이전트 하나로 시작해 보세요. 모델이 잘못되었을 때 문제를 일으킬 수 있는 단 하나의 값에 대해 Field 제약 조건이 있는 output_type을 부여하세요. 이를 위한 TestModel 테스트를 작성하세요. 그리고 배포하세요. 여러분은 4,000달러의 환불을 발생시켰던 바로 그 간극을 메웠으며, 그 간극이 계속 닫혀 있음을 증명하는 테스트를 갖게 될 것입니다.
더 넓은 관점, 즉 타입이 지정된 에이전트(typed agents)가 다른 프레임워크들과 어떻게 공존하는지, 에이전트가 실행될 때 어떻게 추적(trace)하는지, 그리고 비용을 어떻게 정직하게 유지하는지에 대해 알고 싶다면, 그것이 바로 _The AI Engineer's Library_가 다루는 내용입니다. _Agents in Production_는 프레임워크의 지형과 다단계 에이전트(multi-step agents)를 구축하고 배포하기 위한 패턴을 안내합니다. _Observability for LLM Applications_는 추적(tracing), 평가(evals), 그리고 비용에 관한 동반서입니다. 두 도서 모두 이 포스트의 목적과 동일한 것을 지향합니다. 즉, 에이전트가 무엇을 하는지 보고 제약할 수 있기 때문에 신뢰할 수 있는 에이전트를 만드는 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기