오늘은 Pydantic AI를 활용해 파이썬으로 똑똑한 AI 에이전트를 개발하는 방법을 알려드릴게요!
Pydantic AI가 뭔가요? 🤔
여러분이 레스토랑 주방을 상상해보세요.
- 주방에는 요리사(AI 모델)가 있습니다
- 요리사에게 레시피(시스템 프롬프트)를 줍니다
- 요리사는 필요한 도구(툴)를 활용해 요리(응답)를 만들어냅니다
Pydantic AI가 바로 이런 역할을 합니다!
- 개발자가 AI 모델에게 구조화된 지시를 내리고
- AI가 필요한 정보를 찾아서
- 체계적인 형식으로 결과를 돌려주는 프레임워크 ✨
왜 Pydantic AI를 써야 할까요? 🌟
Pydantic 팀이 직접 개발
- Pydantic은 이미 OpenAI, Anthropic SDK, LangChain 등 많은 AI 도구의 검증 계층으로 사용 중
- 타입 안전성과 검증에 최적화된 라이브러리
모델 중립적
- OpenAI, Gemini, Anthropic 등 다양한 모델 지원
- 하나의 코드로 여러 모델 전환 가능
구조화된 응답
- Pydantic 모델을 통해 응답 형식 정의
- 응답의 유효성을 자동으로 검사
순수 파이썬 베이스
- 익숙한 파이썬 코드로 제어 흐름 및 에이전트 구성
- 기존 파이썬 프로젝트에 쉽게 통합
설치하는 방법 📦
기본 설치
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)
# 출력: 파이썬은 간결하고 읽기 쉬운 문법을 가진 고수준 프로그래밍 언어로, 다양한 분야에서 사용되는 범용 언어입니다.
코드 설명
Agent
클래스를 임포트합니다.openai:gpt-4o
모델을 사용하는 에이전트를 생성합니다.- 시스템 프롬프트로 "짧고 간결하게 한 문장으로 답변해주세요."를 설정합니다.
run_sync
메서드로 에이전트를 실행하고 질문을 전달합니다.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
코드 설명
WeatherDeps
클래스로 OpenWeatherMap API 키를 의존성으로 정의합니다.WeatherResult
클래스로 응답 형식을 정의합니다.- 에이전트를 생성할 때 의존성 타입(
deps_type
)과 결과 타입(result_type
)을 설정합니다. get_weather
도구를 정의해 위치 정보를 받아 날씨 데이터를 가져옵니다.- 의존성 객체를 생성하고 에이전트를 실행합니다.
고급 예제: 여러 도구 사용하기 🛠️
여러 도구를 조합해 더 복잡한 작업을 수행할 수 있습니다:
# 위치 좌표를 가져오는 도구
@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)
동작 방식 💫
이 에이전트는 다음과 같이 작동합니다:
- 사용자가 "부산의 날씨가 어떤지 알려줘"라고 질문합니다.
- 에이전트가
get_lat_lng
도구를 호출해 부산의 위도와 경도를 가져옵니다. - 에이전트가
get_weather_by_coords
도구를 호출해 해당 좌표의 날씨를 가져옵니다. - 응답을
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())
모범 사례 🏆
타입 힌트 활용
- 에이전트, 도구, 결과에 명확한 타입 힌트를 제공하면 개발 경험이 향상됩니다
- 정적 타입 검사기(mypy, pyright)가 오류를 조기에 발견할 수 있습니다
도구 함수 설계
- 각 도구는 한 가지 작업을 잘 수행하도록 설계하세요
- 도구 함수에 명확한 독스트링을 작성하면 LLM이 더 잘 이해합니다
에이전트 재사용
- 에이전트를 모듈 전역 변수로 인스턴스화해 애플리케이션 전체에서 재사용하세요
- FastAPI의 App이나 APIRouter와 유사한 패턴입니다
올바른 모델 선택
- 간단한 작업에는
gemini-1.5-flash
와 같은 빠르고 저렴한 모델을 사용하세요 - 복잡한 작업에는
openai:gpt-4o
와 같은 더 강력한 모델을 사용하세요
- 간단한 작업에는
에러 처리와 재시도
- 도구 내에서
ModelRetry
를 사용해 LLM에게 재시도를 요청할 수 있습니다 - 외부 API 호출에서는 적절한 예외 처리를 구현하세요
- 도구 내에서
주의할 점 ⚠️
API 키 보안
- API 키를 코드에 하드코딩하지 말고 환경 변수나 비밀 관리 서비스를 사용하세요
- 키 노출은 금전적 손실과 서비스 중단으로 이어질 수 있습니다
토큰 사용량 관리
- 많은 도구를 가진 복잡한 에이전트는 토큰 사용량이 많아 비용이 증가할 수 있습니다
- 개발 중에는 더 저렴한 모델로 테스트하고, 프로덕션에서만 강력한 모델을 사용하세요
무한 루프 방지
- 도구를 반복적으로 호출하는 에이전트에서 무한 루프가 발생할 수 있습니다
retries
매개변수를 설정해 최대 재시도 횟수를 제한하세요
API 속도 제한
- 외부 API를 사용할 때는 속도 제한을 고려하세요
- 프로덕션 애플리케이션에서는 요청 속도를 제어하는 메커니즘을 구현하세요
프로덕션을 위한 모델 선택
- 프로덕션 환경에서는 "hobby" API 대신 전용 모델별 공급자를 사용하세요
- 예: Gemini의 경우 generativelanguage.googleapis.com 대신 VertexAI 사용
비결정적 응답
- LLM 응답은 비결정적이므로 항상 동일한 입력에 대해 다른 출력을 제공할 수 있습니다
- 중요한 의사 결정에 LLM만 사용하지 말고 추가 검증을 구현하세요
의존성 관리
- 여러 에이전트와 마이크로서비스에서 같은 의존성을 공유할 때 주의하세요
- 의존성 주입을 사용해 테스트와 프로덕션 코드를 분리하세요
마치며 🎁
Pydantic AI는 마치 잘 조직된 레스토랑 주방처럼 AI 모델의 힘을 구조화되고 안전하며 예측 가능한 방식으로 활용할 수 있게 해줍니다. 기존 Python 개발 경험을 활용하면서 최신 AI 모델의 강력한 기능을 쉽게 통합할 수 있어요!
다음 단계로는 공식 문서의 예제를 더 살펴보고, 테스트와 디버깅에 대해 자세히 알아보는 것을 추천합니다. 여러분만의 AI 에이전트를 만들어 복잡한 문제를 해결해보세요!
궁금하신 점 있으시다면 댓글로 남겨주세요! 😊