300===Dev Framework/Pydantic

Pydantic AI로 AI 에이전트 개발하기 😋

블로글러 2025. 3. 13. 21:04

오늘은 Pydantic AI를 활용해 파이썬으로 똑똑한 AI 에이전트를 개발하는 방법을 알려드릴게요!

Pydantic AI가 뭔가요? 🤔

여러분이 레스토랑 주방을 상상해보세요.

  • 주방에는 요리사(AI 모델)가 있습니다
  • 요리사에게 레시피(시스템 프롬프트)를 줍니다
  • 요리사는 필요한 도구(툴)를 활용해 요리(응답)를 만들어냅니다

Pydantic AI가 바로 이런 역할을 합니다!

  • 개발자가 AI 모델에게 구조화된 지시를 내리고
  • AI가 필요한 정보를 찾아서
  • 체계적인 형식으로 결과를 돌려주는 프레임워크 ✨

왜 Pydantic AI를 써야 할까요? 🌟

  1. Pydantic 팀이 직접 개발

    • Pydantic은 이미 OpenAI, Anthropic SDK, LangChain 등 많은 AI 도구의 검증 계층으로 사용 중
    • 타입 안전성과 검증에 최적화된 라이브러리
  2. 모델 중립적

    • OpenAI, Gemini, Anthropic 등 다양한 모델 지원
    • 하나의 코드로 여러 모델 전환 가능
  3. 구조화된 응답

    • Pydantic 모델을 통해 응답 형식 정의
    • 응답의 유효성을 자동으로 검사
  4. 순수 파이썬 베이스

    • 익숙한 파이썬 코드로 제어 흐름 및 에이전트 구성
    • 기존 파이썬 프로젝트에 쉽게 통합

설치하는 방법 📦

기본 설치

pip install pydantic-ai

특정 모델 지원 설치 (슬림 버전)

# OpenAI 모델 사용
pip install 'pydantic-ai-slim[openai]'

# Gemini 모델 사용
pip install pydantic-ai-slim

# VertexAI(Google Cloud)를 통한 Gemini 사용
pip install 'pydantic-ai-slim[vertexai]'

# Groq 모델 사용
pip install 'pydantic-ai-slim[groq]'

API 키 설정

# OpenAI
export OPENAI_API_KEY='your-api-key'

# Gemini
export GEMINI_API_KEY='your-api-key'

# Groq
export GROQ_API_KEY='your-api-key'

핵심 개념 💡

1. 에이전트 (Agent)

from pydantic_ai import Agent

agent = Agent(
    'openai:gpt-4o',  # 사용할 모델
    system_prompt='당신은 도움이 되는 AI 비서입니다.',  # 시스템 프롬프트
)

이것은 마치 레스토랑을 차리는 것과 같아요:

  • 어떤 요리사(모델)를 고용할지
  • 어떤 종류의 음식(응답)을 제공할지 결정합니다

2. 시스템 프롬프트 (System Prompts)

# 정적 시스템 프롬프트
agent = Agent(
    'gemini-1.5-flash',
    system_prompt='항상 한 문장으로만 답변해주세요.',
)

# 동적 시스템 프롬프트
@agent.system_prompt
def add_user_name(ctx: RunContext[str]) -> str:
    return f"사용자의 이름은 {ctx.deps}입니다. 이름을 사용해 응답해주세요."

이것은 요리 레시피와 같아요:

  • 요리사에게 어떻게 요리를 만들어야 하는지 알려주는 지침서
  • 정적(항상 같은 방법) 또는 동적(상황에 따라 변하는 방법)으로 작성 가능

3. 도구 (Tools)

@agent.tool
async def get_weather(ctx: RunContext[WeatherDeps], location: str) -> dict:
    """주어진 위치의 날씨 정보를 가져옵니다."""
    # API 호출 코드
    return weather_data

이것은 주방 도구와 같아요:

  • 요리사가 필요한 정보(재료)를 얻기 위해 사용하는 도구
  • 외부 API 호출, 데이터베이스 쿼리 등을 수행

4. 의존성 (Dependencies)

@dataclass
class WeatherDeps:
    api_key: str
    default_unit: str = "metric"

이것은 요리 재료와 같아요:

  • 에이전트가 작업을 수행하는 데 필요한 정보
  • API 키, 데이터베이스 연결 등을 포함

5. 결과 (Results)

class WeatherResult(BaseModel):
    location: str
    temperature: float
    condition: str
    humidity: int

이것은 최종 요리와 같아요:

  • 고객에게 제공되는 완성된 형태
  • 구조화된 형식으로 정의되어 있어 항상 일관된 형태로 제공

첫 번째 예제: 안녕 세상! 👋

가장 간단한 형태의 Pydantic AI 에이전트를 만들어봅시다:

from pydantic_ai import Agent

# 에이전트 생성
agent = Agent(
    'openai:gpt-4o',                          # 모델 선택
    system_prompt='짧고 간결하게 한 문장으로 답변해주세요.',  # 시스템 프롬프트
)

# 에이전트 실행
result = agent.run_sync('프로그래밍 언어 파이썬이 무엇인가요?')

# 결과 출력
print(result.data)
# 출력: 파이썬은 간결하고 읽기 쉬운 문법을 가진 고수준 프로그래밍 언어로, 다양한 분야에서 사용되는 범용 언어입니다.

코드 설명

  1. Agent 클래스를 임포트합니다.
  2. openai:gpt-4o 모델을 사용하는 에이전트를 생성합니다.
  3. 시스템 프롬프트로 "짧고 간결하게 한 문장으로 답변해주세요."를 설정합니다.
  4. run_sync 메서드로 에이전트를 실행하고 질문을 전달합니다.
  5. result.data에서 응답을 받아 출력합니다.

실전 예제: 날씨 에이전트 만들기 🌤️

이제 외부 API를 활용해 날씨 정보를 제공하는 더 복잡한 에이전트를 만들어봅시다:

from dataclasses import dataclass
from pydantic import BaseModel
from pydantic_ai import Agent, RunContext
import requests

# 의존성 정의
@dataclass
class WeatherDeps:
    openweather_api_key: str

# 결과 형식 정의
class WeatherResult(BaseModel):
    location: str
    temperature: float
    condition: str
    humidity: int

# 에이전트 생성
weather_agent = Agent(
    'openai:gpt-4o',                           # 모델 선택
    deps_type=WeatherDeps,                     # 의존성 타입
    result_type=WeatherResult,                 # 결과 타입
    system_prompt='날씨 정보를 제공하는 에이전트입니다.', # 시스템 프롬프트
)

# 도구 정의
@weather_agent.tool
async def get_weather(ctx: RunContext[WeatherDeps], location: str) -> dict:
    """주어진 위치의 날씨 정보를의 가져옵니다."""
    api_key = ctx.deps.openweather_api_key
    url = f"https://api.openweathermap.org/data/2.5/weather?q={location}&appid={api_key}&units=metric"
    response = requests.get(url)
    data = response.json()

    return {
        "location": location,
        "temperature": data["main"]["temp"],
        "condition": data["weather"][0]["main"],
        "humidity": data["main"]["humidity"]
    }

# 에이전트 실행
deps = WeatherDeps(openweather_api_key="your-api-key")
result = weather_agent.run_sync('서울의 날씨는 어때요?', deps=deps)

# 결과 출력
print(result.data)
# 출력: location='서울' temperature=21.5 condition='Clear' humidity=65

코드 설명

  1. WeatherDeps 클래스로 OpenWeatherMap API 키를 의존성으로 정의합니다.
  2. WeatherResult 클래스로 응답 형식을 정의합니다.
  3. 에이전트를 생성할 때 의존성 타입(deps_type)과 결과 타입(result_type)을 설정합니다.
  4. get_weather 도구를 정의해 위치 정보를 받아 날씨 데이터를 가져옵니다.
  5. 의존성 객체를 생성하고 에이전트를 실행합니다.

고급 예제: 여러 도구 사용하기 🛠️

여러 도구를 조합해 더 복잡한 작업을 수행할 수 있습니다:

# 위치 좌표를 가져오는 도구
@weather_agent.tool
async def get_lat_lng(ctx: RunContext[WeatherDeps], location: str) -> dict:
    """주어진 위치의 위도와 경도를 반환합니다."""
    # 위치를 지오코딩하는 간단한 구현
    # 실제로는 Google Maps API 등을 사용할 수 있습니다
    locations = {
        "서울": {"lat": 37.5665, "lng": 126.9780},
        "부산": {"lat": 35.1796, "lng": 129.0756},
        "인천": {"lat": 37.4563, "lng": 126.7052},
    }
    return locations.get(location, {"lat": 0, "lng": 0})

# 좌표 기반으로 날씨를 가져오는 도구
@weather_agent.tool
async def get_weather_by_coords(ctx: RunContext[WeatherDeps], lat: float, lng: float) -> dict:
    """위도와 경도를 기반으로 날씨 정보를 가져옵니다."""
    api_key = ctx.deps.openweather_api_key
    url = f"https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lng}&appid={api_key}&units=metric"
    response = requests.get(url)
    data = response.json()

    return {
        "location": data["name"],
        "temperature": data["main"]["temp"],
        "condition": data["weather"][0]["main"],
        "humidity": data["main"]["humidity"]
    }

# 에이전트 실행
result = weather_agent.run_sync('부산의 날씨가 어떤지 알려줘', deps=deps)
print(result.data)

동작 방식 💫

이 에이전트는 다음과 같이 작동합니다:

  1. 사용자가 "부산의 날씨가 어떤지 알려줘"라고 질문합니다.
  2. 에이전트가 get_lat_lng 도구를 호출해 부산의 위도와 경도를 가져옵니다.
  3. 에이전트가 get_weather_by_coords 도구를 호출해 해당 좌표의 날씨를 가져옵니다.
  4. 응답을 WeatherResult 형식으로 변환해 반환합니다.

이 과정을 시각화하면:

사용자: "부산의 날씨가 어떤지 알려줘"
  ↓
에이전트: "위치 정보가 필요해. get_lat_lng 도구를 호출할게."get_lat_lng: "부산의 좌표는 (35.1796, 129.0756)야."
  ↓
에이전트: "이제 날씨 정보를 가져올게. get_weather_by_coords 도구를 호출할게."get_weather_by_coords: "부산의 날씨 데이터를 가져왔어."
  ↓
에이전트: "응답을 WeatherResult 형식으로 변환할게."
  ↓
최종 응답: "location='부산' temperature=22.8 condition='Cloudy' humidity=72"

스트리밍 응답 사용하기 🌊

긴 응답을 실시간으로 스트리밍하려면 run_stream 메서드를 사용할 수 있습니다:

import asyncio

async def main():
    agent = Agent('openai:gpt-4o')

    async with agent.run_stream('파이썬의 역사에 대해 설명해줘') as response:
        async for chunk in response.iter_text():
            print(chunk, end='', flush=True)

asyncio.run(main())

모범 사례 🏆

  1. 타입 힌트 활용

    • 에이전트, 도구, 결과에 명확한 타입 힌트를 제공하면 개발 경험이 향상됩니다
    • 정적 타입 검사기(mypy, pyright)가 오류를 조기에 발견할 수 있습니다
  2. 도구 함수 설계

    • 각 도구는 한 가지 작업을 잘 수행하도록 설계하세요
    • 도구 함수에 명확한 독스트링을 작성하면 LLM이 더 잘 이해합니다
  3. 에이전트 재사용

    • 에이전트를 모듈 전역 변수로 인스턴스화해 애플리케이션 전체에서 재사용하세요
    • FastAPI의 App이나 APIRouter와 유사한 패턴입니다
  4. 올바른 모델 선택

    • 간단한 작업에는 gemini-1.5-flash와 같은 빠르고 저렴한 모델을 사용하세요
    • 복잡한 작업에는 openai:gpt-4o와 같은 더 강력한 모델을 사용하세요
  5. 에러 처리와 재시도

    • 도구 내에서 ModelRetry를 사용해 LLM에게 재시도를 요청할 수 있습니다
    • 외부 API 호출에서는 적절한 예외 처리를 구현하세요

주의할 점 ⚠️

  1. API 키 보안

    • API 키를 코드에 하드코딩하지 말고 환경 변수나 비밀 관리 서비스를 사용하세요
    • 키 노출은 금전적 손실과 서비스 중단으로 이어질 수 있습니다
  2. 토큰 사용량 관리

    • 많은 도구를 가진 복잡한 에이전트는 토큰 사용량이 많아 비용이 증가할 수 있습니다
    • 개발 중에는 더 저렴한 모델로 테스트하고, 프로덕션에서만 강력한 모델을 사용하세요
  3. 무한 루프 방지

    • 도구를 반복적으로 호출하는 에이전트에서 무한 루프가 발생할 수 있습니다
    • retries 매개변수를 설정해 최대 재시도 횟수를 제한하세요
  4. API 속도 제한

    • 외부 API를 사용할 때는 속도 제한을 고려하세요
    • 프로덕션 애플리케이션에서는 요청 속도를 제어하는 메커니즘을 구현하세요
  5. 프로덕션을 위한 모델 선택

    • 프로덕션 환경에서는 "hobby" API 대신 전용 모델별 공급자를 사용하세요
    • 예: Gemini의 경우 generativelanguage.googleapis.com 대신 VertexAI 사용
  6. 비결정적 응답

    • LLM 응답은 비결정적이므로 항상 동일한 입력에 대해 다른 출력을 제공할 수 있습니다
    • 중요한 의사 결정에 LLM만 사용하지 말고 추가 검증을 구현하세요
  7. 의존성 관리

    • 여러 에이전트와 마이크로서비스에서 같은 의존성을 공유할 때 주의하세요
    • 의존성 주입을 사용해 테스트와 프로덕션 코드를 분리하세요

마치며 🎁

Pydantic AI는 마치 잘 조직된 레스토랑 주방처럼 AI 모델의 힘을 구조화되고 안전하며 예측 가능한 방식으로 활용할 수 있게 해줍니다. 기존 Python 개발 경험을 활용하면서 최신 AI 모델의 강력한 기능을 쉽게 통합할 수 있어요!

다음 단계로는 공식 문서의 예제를 더 살펴보고, 테스트와 디버깅에 대해 자세히 알아보는 것을 추천합니다. 여러분만의 AI 에이전트를 만들어 복잡한 문제를 해결해보세요!


궁금하신 점 있으시다면 댓글로 남겨주세요! 😊


참고 자료:

728x90