LLM의 원본 출력은 앱을 망칩니다. JSON을 요청하면 마크다운이 돌아오고, 1부터 10 사이의 숫자를 요청하면 "물론이죠! 여기 숫자가 있습니다: seven"이 돌아옵니다. LLM API로 실제 뭔가를 만들어본 사람이라면 자신의 경력 선택을 의심하게 만드는 방어적인 파싱 코드를 작성해봤을 겁니다. Pydantic AI는 이를 해결합니다 -- Pydantic과 FastAPI의 같은 팀이 만든 타입 안전 에이전트 프레임워크입니다. "AI 에이전트를 위한 FastAPI"라고 생각하면 됩니다: Python 타입 힌트로 원하는 것을 정의하면, 프레임워크가 검증, 재시도, 도구 호출을 처리합니다.

이 Pydantic AI 가이드는 이미 첫 번째 LLM 호출을 실행했고 프로덕션 패턴을 원하는 개발자를 위한 것입니다: 깨지지 않는 구조화된 출력, 테스트 가능한 에이전트를 위한 의존성 주입, 날씨 API 이상의 실제 도구. 끝까지 읽으면 도구, 의존성 주입, 스트리밍, 테스트가 있는 작동하는 에이전트를 갖게 될 것입니다.

한눈에 보는 Pydantic AI

눈에 띄는 기능은 구조화된 출력(Pydantic 모델로 검증됨), 의존성 주입(FastAPI의 Depends와 같음), 그리고 TestModel(API 호출 없이 테스트하기 위한 모의 LLM)입니다. LangChain에서 오고 있고 "더 깔끔한 게 있나?"라고 궁금하다면, 아마 이것이 답일 겁니다.

설치 및 첫 번째 에이전트

5줄로 첫 번째 에이전트를 만드는 방법:

from pydantic_ai import Agent

agent = Agent('openai:gpt-4o')
result = agent.run_sync('Tell me about planets')
print(result.output)

끝입니다. Agent가 모델을 래핑하고, run_sync가 프롬프트를 보내고 결과를 반환합니다. result.output은 여기서는 평문 문자열이지만, 곧 바뀔 것입니다.

구조화된 출력 -- Pydantic AI가 존재하는 이유

이것이 핵심 기능입니다. LLM에서 문자열을 받아서 유효한 JSON이기를 바라는 대신, Pydantic 모델을 정의하고 에이전트가 검증된 Python 객체를 반환합니다.

이전: 원본 LLM 출력

result = agent.run_sync('Review this movie: Inception')
print(result.output)
# "Movie: Inception, Rating: 9/10. Excellent sci-fi thriller..."

이후: Pydantic AI로 구조화

from pydantic import BaseModel

class MovieReview(BaseModel):
    title: str
    rating: int  # 1-10
    summary: str

agent = Agent('openai:gpt-4o', result_type=MovieReview)
result = agent.run_sync('Review this movie: Inception')
print(result.output.rating)  # 9 (정수, 문자열이 아님)

차이는 하늘과 땅입니다. result.output은 실제 MovieReview 객체입니다. LLM이 rating: "eight" 대신 rating: 8을 반환하면, Pydantic의 검증이 이를 잡습니다. 다양한 제공자 간에 이것이 어떻게 작동하는지에 대한 더 깊은 이해를 위해, LLM 제공자 간 구조화된 출력 가이드를 참조하세요.

검증이 실패할 때는?

여기가 다른 튜토리얼이 보여주지 않는 부분입니다: LLM이 실수할 때 무슨 일이 일어나나요?

# LLM이 잘못된 JSON을 반환하거나 rating="eight"이면:
# Pydantic AI는 자동으로 재시도하고 LLM에게 피드백을 제공합니다
# "rating must be an integer between 1-10"

result = agent.run_sync('Review this movie: Inception')
# 실패해도 대부분의 경우 유효한 데이터가 반환됩니다

이 검증 피드백 루프는 Pydantic AI의 킬러 기능입니다. LLM은 자신의 검증 오류로부터 배웁니다. 재시도 로직을 작성할 필요가 없습니다 -- 프레임워크가 처리합니다.

평결: 구조화된 출력이 Pydantic AI를 원본 API 호출보다 선택해야 할 단 하나의 최고의 이유입니다. LLM JSON을 직접 파싱하고 있다면, 멈추세요.

도구와 함수 호출

도구를 사용하면 에이전트가 Python 함수를 호출해서 실제 데이터를 얻을 수 있습니다. LLM이 사실을 환상하는 대신, 데이터베이스를 쿼리하고, 문서를 검색하거나, API를 호출할 수 있습니다.

도구 등록

@agent.tool
def get_weather(city: str) -> str:
    """Get weather for a city"""
    return f"Sunny in {city}"

@agent.tool 데코레이터가 함수를 등록합니다. Pydantic AI는 함수의 타입 힌트와 문서 문자열을 읽어서 도구가 무엇을 하는지, 어떤 인자를 취하고, 무엇을 반환하는지 LLM에 알립니다. 수동 스키마 작성이 없습니다 -- 타입 힌트가 스키마입니다. LLM 함수 호출이 내부적으로 어떻게 작동하는지에 대한 배경은 전용 가이드를 참조하세요.

RunContext: 도구에 데이터 전달하기

여기가 Pydantic AI가 다른 프레임워크와 갈라지는 곳입니다. RunContext를 사용하면 런타임 데이터(데이터베이스 연결, 사용자 정보, API 클라이언트)를 전역 상태 없이 도구에 전달할 수 있습니다.

from pydantic_ai import RunContext

@agent.tool
def query_database(ctx: RunContext[MyDeps], query: str) -> str:
    """Query the database"""
    return ctx.deps.db.execute(query)

ctx.deps는 도구에 런타임에 전달한 모든 것에 대한 접근을 제공합니다. 도구는 전역 데이터베이스 연결을 가져오지 않습니다 -- 하나를 받습니다. 이것이 의존성 주입이고, 에이전트를 테스트 가능하게 만드는 것입니다.

실제 도구 예제

class MyDeps:
    db: Database
    api_client: APIClient

agent = Agent('openai:gpt-4o', deps_type=MyDeps)

@agent.tool
def search_docs(ctx: RunContext[MyDeps], query: str) -> str:
    return ctx.deps.db.search(query)

# 런타임에 의존성을 전달합니다
result = agent.run_sync(
    'Find customer John Smith',
    deps=MyDeps(db=my_db, api_client=my_client)
)

평결: Pydantic AI의 도구 호출은 타입 힌트가 무거운 일을 하기 때문에 어떤 다른 프레임워크보다 깔끔합니다. 타입 주석이 있는 일반 Python 함수를 작성합니다. 프레임워크가 나머지를 파악합니다.

의존성 주입 -- LangChain이 갖고 싶어 하는 기능

FastAPI의 Depends를 사용해봤다면, 이미 Pydantic AI의 DI 시스템을 이해하고 있습니다. 아니라면, 짧은 버전은: 에이전트가 필요한 것을 직접 잡는 대신(전역 데이터베이스 연결, API 클라이언트, 구성), 런타임에 모든 것을 전달합니다.

의존성 정의

from dataclasses import dataclass

@dataclass
class MyDeps:
    db: Database
    user_id: str
    config: Config

도구에서 의존성 사용하기

@agent.tool
def get_user_data(ctx: RunContext[MyDeps]) -> str:
    user = ctx.deps.db.get_user(ctx.deps.user_id)
    return f"User: {user.name}"

DI가 에이전트를 테스트 가능하게 만드는 이유

이것이 진짜 페이오프입니다. LangChain에서는 chain kwargs나 클로저를 통해 컨텍스트를 전달할 것입니다 -- 표준 패턴이 없습니다. Pydantic AI에서는 실제 의존성을 테스트 더블로 바꾸는 것이 사소합니다:

# 프로덕션
result = agent.run_sync(
    'Get user data',
    deps=MyDeps(db=production_db, user_id='123')
)

# 테스트
result = agent.run_sync(
    'Get user data',
    deps=MyDeps(db=mock_db, user_id='123')  # 그냥 mock_db를 전달합니다
)

몽키-패칭 없음. 전역 임포트 모킹 없음. 그냥 다른 deps를 전달합니다.

평결: 의존성 주입이 경험 많은 Python 개발자가 Pydantic AI를 선호하는 이유입니다. FastAPI의 영향이 드러나고 있습니다.

모델 제공자 -- OpenAI, Anthropic, Gemini, Ollama

Pydantic AI는 모델 불가지론적입니다. 제공자 전환은 한 줄 변경입니다:

# OpenAI
agent = Agent('openai:gpt-4o')

# Anthropic
agent = Agent('claude-3-5-sonnet-20241022')

# Gemini
agent = Agent('gemini-2.0-flash')

# Ollama
agent = Agent('ollama:llama2')

다른 모든 것 -- 도구, 구조화된 출력, DI -- 동일하게 유지됩니다. 모델을 전환할 때 비즈니스 로직은 변경되지 않습니다.

평결: 모델 불가지론적 설계는 한 제공자에 잠기지 않음을 의미합니다. 편의를 위해 OpenAI로 시작하고, Anthropic과 벤치마크하고, 로컬 개발을 위해 Ollama를 사용합니다.

스트리밍 응답

채팅 UI와 실시간 애플리케이션의 경우 스트리밍이 필수입니다. Pydantic AI는 타입 안전을 유지하면서 이를 지원합니다:

with agent.run_stream('Generate a story') as response:
    for text in response.stream_text():
        print(text, end='', flush=True)

FastAPI의 StreamingResponse와 함께 아름답게 작동합니다 -- 같은 생태계, 같은 패턴. Pydantic AI 에이전트 문서에서는 stream_text()를 사용한 텍스트 전용 스트리밍을 포함한 고급 스트리밍 옵션을 다룹니다.

Pydantic AI vs LangGraph vs OpenAI Agents SDK

여기 있으니, 아마 이렇게 묻고 있을 겁니다: "Pydantic AI를 사용할까요, 아니면 LangGraph를 사용할까요?" 솔직한 대답: 다른 문제를 해결하며, 둘 다 사용할 수도 있습니다.

기능 비교 표

기능 Pydantic AI LangGraph OpenAI Agents SDK
구조화된 출력
의존성 주입
모델 불가지론적
테스트 모델
조건부 분기
상태 관리 기본 고급 기본
사람 참여

각각을 언제 사용할까

깔끔하고 타입 안전한 에이전트 코드를 원할 때 Pydantic AI를 선택하세요. 도구가 있는 단일 에이전트 작업(고객 지원 봇, 데이터 추출, 코드 검토 에이전트) 및 테스트 가능성이 중요한 상황에 이상적입니다. 팀이 이미 FastAPI와 Pydantic을 사용 중이라면, 학습 곡선은 거의 평탄합니다.

복잡한 다단계 워크플로우가 조건부 분기, 사람이 참여한 승인, 정교한 상태 관리가 필요할 때 LangGraph를 선택하세요. LangGraph는 개별 에이전트 품질이 아닌 여러 단계를 오케스트레이션하는 데 뛰어납니다. 깊은 다이빙을 원하면, 전체 LangGraph vs CrewAI vs OpenAI Agents SDK 비교를 참조하세요.

OpenAI에 100% 몸담고 있고, 최대한 간단한 설정을 원하며, 다중 제공자 지원이나 DI가 필요 없을 때 OpenAI Agents SDK를 선택하세요.

조합 패턴

경험이 많은 팀이 실제로 하는 것: 개별 에이전트에는 Pydantic AI 사용(깔끔한 코드, 테스트 가능, 타입 출력), 에이전트 간 오케스트레이션에는 LangGraph 사용(라우팅, 상태 머신, 조건부 로직). 이들은 경쟁하지 않습니다 -- 상호 보완적인 계층입니다.

평결: 깔끔하고 테스트 가능한 에이전트 코드를 원하면 Pydantic AI를 선택하세요. 복잡한 다단계 워크플로우는 LangGraph를 선택하세요. 상호 배타적이지 않습니다.

TestModel로 에이전트 테스트하기

이것이 초보자 가이드와 프로덕션 가이드를 분리하는 섹션입니다. 모든 실제 코드베이스에는 테스트가 필요하고, 에이전트 테스트는 악명 높게 어렵습니다 -- LLM 호출은 느리고, 비싸고, 비결정론적입니다. Pydantic AI는 해결책을 제공합니다: TestModel.

from pydantic_ai import Agent
from pydantic_ai.models import TestModel

agent = Agent('openai:gpt-4o', result_type=MovieReview)

# 테스트에서
test_agent = Agent(TestModel(seed=42), result_type=MovieReview)
result = test_agent.run_sync('Review this movie: Inception')
# result.output은 유효한 MovieReview 인스턴스입니다

TestModel은 API 호출 없이 result_type과 일치하는 유효한 데이터를 생성합니다. 비용 없음, 결정론적, 빠름. Pydantic AI 테스팅 문서에서는 사용자 정의 응답을 위한 FunctionModel과 도구 호출을 검사하기 위한 capture_run_messages 같은 고급 패턴을 다룹니다.

도구와 DI 함께 테스트하기

def test_user_lookup():
    test_agent = Agent(TestModel(), deps_type=MyDeps)

    mock_db = MockDatabase({'123': {'name': 'John'}})
    result = test_agent.run_sync(
        'Find user 123',
        deps=MyDeps(db=mock_db)
    )

    assert 'John' in result.output

API 호출 없음. 불안정한 테스트 없음. 비용 없음. 나머지 테스트 스위트와 함께 CI/CD에서 이를 실행하세요.

이것이 전체 SERP의 #1 콘텐츠 갭입니다. 다른 Pydantic AI 가이드는 테스팅을 다루지 않습니다. 프로덕션용 에이전트를 만드는 경우, 이것이 필요한 것입니다.

관찰성 -- 5분 안에 Logfire 통합

...

출처 바로가기