본문 바로가기
AI 튜토리얼

pydantic-ai 완벽 가이드: 타입 안전 Python AI 에이전트 (2026)

by 정부우르사 2026. 5. 24.
반응형

LangChain으로 에이전트를 짜다 보면 출력이 매번 다른 형태로 튀어나와 try/except를 도배하게 된다. CrewAI는 멀티 에이전트 오케스트레이션은 강력하지만, 단일 함수 한 개에 LLM을 끼우려는 상황에선 과한 추상화가 걸린다. Pydantic 팀이 만든 pydantic-ai는 이 사이의 빈자리를 정확히 노린다.

"LangChain 말고 더 가벼운 게 없을까?"라는 질문에서 시작한 30분짜리 핸즈온이다. 설치부터 첫 Agent, Tool 호출, TestModel 단위 테스트, 프로덕션 체크리스트까지 한 바퀴를 돈다.


📌 핵심 3줄 요약

  • pydantic-ai는 Pydantic 모델로 출력 스키마를 강제하는 단일 에이전트 프레임워크다.
  • LangChain·CrewAI와 달리 그래프·체인 추상화 없이 함수 한 개 단위로 끼울 수 있다.
  • TestModel로 실제 LLM 호출 없이 단위 테스트가 가능해 CI 비용이 0에 수렴한다.

1. pydantic-ai가 LangChain·CrewAI와 다른 점

LangChain은 만능 툴킷, CrewAI는 멀티 에이전트 오케스트레이션에 강하다. pydantic-ai의 포지션은 정반대다. 단일 에이전트, 단일 함수, 타입 안전성. 이 세 가지에 집중한다.

항목 pydantic-ai LangChain CrewAI
주 타깃 단일 에이전트·함수 체인·그래프 멀티 에이전트 협업
출력 검증 Pydantic 모델 강제 사용자 구현 사용자 구현
학습 곡선 30분 며칠 하루
테스트 도구 TestModel 내장 LangSmith 별도 없음
의존성 주입 RunContext[Deps] 직접 구현 Agent 속성

두 프레임워크를 모두 운영해 본 입장에서 가장 체감이 컸던 부분은 출력 검증이다. LangChain에서 PydanticOutputParser를 붙여도 모델이 JSON을 살짝 깨뜨리면 재시도 로직을 따로 짜야 한다. pydantic-ai는 이 재시도를 라이브러리 내부에서 처리하고, 검증 실패 시 LLM에게 오류 메시지를 그대로 돌려 보내 자기 교정시킨다.


2. Windows·macOS 설치 (uv 권장)

Python 3.10 이상이 필요하다. 의존성 해석이 빠른 uv를 권장한다. pip도 그대로 쓸 수 있다.

# uv (권장)
uv add pydantic-ai

# 또는 pip
pip install pydantic-ai

# 특정 프로바이더만 골라 쓰는 슬림 버전
pip install "pydantic-ai-slim[openai]"

pydantic-ai는 OpenAI·Anthropic·Gemini·Groq·Mistral·Cohere 등 주요 프로바이더를 한 번에 끌어온다. 컨테이너 이미지 크기를 줄이고 싶다면 pydantic-ai-slim + extras 패턴이 정답이다.

설치 후 API 키를 환경 변수로 등록한다.

# macOS / Linux
export OPENAI_API_KEY=sk-...

# Windows PowerShell
$env:OPENAI_API_KEY = "sk-..."

Windows에서 uv add가 PATH를 못 찾는다면 winget install astral-sh.uv로 재설치한 뒤 새 터미널을 띄우면 해결된다.


3. 첫 Agent 5줄과 출력 검증

가장 짧은 코드 예시부터 본다. 도시 이름을 받아 위도·경도를 반환하는 에이전트다.

from pydantic import BaseModel
from pydantic_ai import Agent

class City(BaseModel):
    name: str
    lat: float
    lon: float

agent = Agent("openai:gpt-4o-mini", output_type=City)
result = agent.run_sync("서울의 좌표를 알려줘")
print(result.output)
# name='Seoul' lat=37.5665 lon=126.978

output_type=City만으로 LLM 응답이 City 인스턴스로 보장된다. 모델이 JSON을 어설프게 뱉으면 pydantic-ai가 검증 오류를 다시 LLM에 돌려 보내 자기 수정을 유도한다. 별도 재시도 코드가 필요 없다.

여러 타입 중 하나가 나올 수 있다면 output_type=City | str 같은 유니온도 그대로 인식한다. 이 한 줄 차이가 LangChain 대비 코드 라인 수를 절반으로 줄여 준다.


4. Tool 호출과 의존성 주입 패턴

실전 에이전트는 외부 데이터를 가져와야 한다. pydantic-ai의 Tool은 데코레이터 한 줄로 끝난다. 의존성은 Agent[Deps] 제네릭과 RunContext로 명시한다.

from dataclasses import dataclass
from pydantic_ai import Agent, RunContext
import httpx

@dataclass
class Deps:
    http: httpx.AsyncClient
    api_key: str

agent = Agent(
    "anthropic:claude-sonnet-4-5",
    deps_type=Deps,
    system_prompt="너는 날씨 비서다. 필요한 도구를 호출해 답해라.",
)

@agent.tool
async def get_weather(ctx: RunContext[Deps], city: str) -> dict:
    """주어진 도시의 현재 날씨를 반환."""
    r = await ctx.deps.http.get(
        "https://api.weather.example/v1/now",
        params={"q": city},
        headers={"Authorization": f"Bearer {ctx.deps.api_key}"},
    )
    return r.json()

async def main():
    async with httpx.AsyncClient() as client:
        deps = Deps(http=client, api_key="...")
        result = await agent.run("부산 날씨 어때?", deps=deps)
        print(result.output)

RunContext[Deps]는 Tool 함수의 첫 인자로 들어와 의존성을 그대로 꺼낼 수 있게 한다. 이 패턴 덕분에 DB 커넥션·API 클라이언트·인증 토큰을 전역 변수로 빼지 않고도 깔끔하게 주입된다.

docstring은 Tool 설명으로 자동 추출돼 LLM 함수 호출 스키마에 들어간다. 따라서 docstring을 한국어로 쓰면 한국어 모델이, 영어로 쓰면 영어 모델이 더 잘 고른다.


5. TestModel로 LLM 호출 없이 단위 테스트

에이전트 코드의 가장 큰 적은 테스트 비용이다. 한 번 돌릴 때마다 API 비용이 나가고, 응답이 비결정적이라 회귀 테스트가 어렵다. pydantic-ai는 이를 TestModelFunctionModel 두 가지 도구로 푼다.

import pytest
from pydantic_ai import models
from pydantic_ai.models.test import TestModel
from my_app import agent  # 위에서 만든 Agent

@pytest.fixture(autouse=True)
def block_real_models():
    # 테스트 중 실수로 실제 API 호출이 나가지 않게 잠금
    models.ALLOW_MODEL_REQUESTS = False

async def test_weather_flow():
    with agent.override(model=TestModel()):
        result = await agent.run("서울 날씨", deps=fake_deps())
        # TestModel은 등록된 도구를 모두 호출하고 더미 응답을 만든다
        tool_calls = [
            c.tool_name for c in result.all_messages()
            if c.kind == "tool-call"
        ]
        assert "get_weather" in tool_calls

TestModel은 등록된 모든 Tool을 더미 인자로 호출하면서 흐름을 검증한다. 시나리오 분기별 출력을 직접 지정하고 싶다면 FunctionModel로 응답을 람다로 정의한다.

💡 안전벨트 한 줄

models.ALLOW_MODEL_REQUESTS = False를 fixture에 박아 두면 누군가 override를 빠뜨려도 즉시 예외가 떠 실수로 비용이 새는 사고를 막는다.


6. 프로덕션 체크리스트

핸즈온 코드를 그대로 서비스에 올리기 전 확인할 항목 6가지다.

  • 재시도 한도 설정Agent(..., retries=3)으로 검증 실패 재시도를 명시적으로 제한
  • 타임아웃 — agent.run(..., model_settings={"timeout": 30})로 외부 API hang 차단
  • 구조화 로깅 — logfire.instrument_pydantic_ai() 한 줄로 OpenTelemetry 트레이스 자동 수집
  • 비용 계측 — result.usage()로 입출력 토큰·요청 수 집계, 모니터링 대시보드에 송출
  • 민감정보 마스킹 — system_prompt에 API 키·DB URI를 직접 박지 말고 Deps 주입
  • 모델 변경 영향 평가 — TestModel·FunctionModel로 모델 교체 시 회귀 시나리오부터 먼저 돌려 본다.

특히 첫 항목 retries 한도는 잊기 쉬운데, 기본값으로 두면 검증 실패 시 무한 재시도에 가까운 호출이 발생할 수 있다. 비용 사고 1순위라 가장 먼저 잠가야 한다.


⚠️ 단점과 주의할 점

  • 멀티 에이전트 협업 워크플로엔 부족 — 본격적인 그래프 분기는 LangGraph·CrewAI가 여전히 우위.
  • 한국어 자료가 아직 적음 — 공식 문서는 영어, 한국어 트러블슈팅 글은 손에 꼽힐 정도.
  • 버전 0.x 계열로 API 변경 가능성 — 프로덕션 도입 시 버전 고정 필수.

🚀 지금 바로 할 일

  1. uv add pydantic-ai로 설치하고 OpenAI API 키를 환경 변수에 등록한다.
  2. 위 5줄짜리 City 에이전트를 그대로 복사해 run_sync로 돌려 본다.
  3. Tool 1개 + TestModel 픽스처까지 묶어 자신의 미니 프로젝트에 끼워 본다.

💬 의견

LangChain·CrewAI·LangGraph 중 무엇을 가장 자주 쓰는지 댓글로 알려 주면 다음 글에서 pydantic-ai와의 실전 비교 예제를 다루겠다.


참고 자료


작성자: AI/기술 블로그 운영자

반응형