Google ADK(Agent Development Kit)와 Gemini 모델을 활용하여 Open-Meteo 지오코딩 API와 OpenWeatherMap API를 연동하고, AI 에이전트(Agent)가 도구(Tool)로 삼아 실시간 날씨를 조회할 수 있도록 구현한 파이썬 스크립트입니다.
I. 코드 요약 및 핵심 의미
- 제목: Google ADK와 외부 날씨 API(Open-Meteo & OpenWeatherMap)를 연동한 LLM 에이전트 구현
- 핵심 의미:
- 사용자의 자연어 질문(예: "서울 날씨 어때?")을 이해하고, 에이전트가 스스로 필요한 도구(get_real_current_weather)를 선택해 실행합니다.
- 도시 이름을 위/경도 좌표로 변환하는 지오코딩 과정(Open-Meteo)과 실제 날씨 정보를 가져오는 과정(OpenWeatherMap)을 파이프라인 형태로 연결했습니다.
- Google ADK의 Agent, BuiltInPlanner, PlanReActPlanner 등을 활용하여 인공지능이 논리적인 계획을 세워 도구를 호출할 수 있는 기반을 제공합니다.
II. 코드 라인별 상세 해설
1. 라이브러리 임포트 및 환경 설정
import os
import requests
from typing import Dict, Any
from dotenv import load_dotenv
from google.adk import Agent
from google.genai import types
from google.adk.planners import BuiltInPlanner
from google.adk.planners import PlanReActPlanner
# 1. 환경 설정
load_dotenv(override=True)
# --- API 키 및 설정 ---
OPEN_WEATHER_API_KEY = os.getenv("OPEN_WEATHER_API_KEY", "")
- os, requests, typing: 시스템 환경 변수를 읽어오고, 외부 HTTP API를 호출하며, 타입 힌트를 지정하기 위한 기본 라이브러리입니다.
- load_dotenv(override=True): .env 파일에 정의된 비밀 키(예: OPEN_WEATHER_API_KEY, GOOGLE_MODEL)를 시스템 환경 변수로 불러옵니다. override=True는 기존에 설정된 값이 있더라도 .env 파일의 값으로 덮어쓰겠다는 의미입니다.
- OPEN_WEATHER_API_KEY: OpenWeatherMap 서비스를 이용하기 위한 인증 키를 환경 변수로부터 안전하게 가져옵니다.
2. 도시 이름으로 좌표를 찾는 함수 (get_coordinates)
def get_coordinates(location:str):
"""도시 이름을 받아 Open-Meteo Geocoding API에서 위도(latitude)와 경도(longitude)를 조회한다."""
url = "https://geocoding-api.open-meteo.com/v1/search"
params = {
"name":location,
"count":1,
"language":"en",
"format":"json",
}
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
if not data.get("results"):
raise ValueError(f"도시를 찾을 수 없습니다. {location}")
result = data["results"][0]
return {
"name":result["name"],
"latitude":result["latitude"],
"longitude":result["longitude"],
"country":result.get("country",""),
}
- 역할: OpenWeatherMap의 날씨 API는 기본적으로 위도(latitude)와 경도(longitude) 값을 요구합니다. 사용자가 "서울" 같은 도시 이름을 입력했을 때, 이를 위·경도 좌표로 변환해 주는 무료 지오코딩 API(Open-Meteo)를 호출하는 함수입니다.
- 처리 과정:
- 검색할 도시 이름(name)과 결과 개수(count: 1) 등을 파라미터로 담아 요청을 보냅니다.
- 응답이 정상(200 OK)인지 확인(response.raise_for_status())합니다.
- 결과 데이터에서 첫 번째 장소의 위도, 경도, 국가명 등을 추출하여 딕셔너리 형태로 반환합니다.

3. 실제 날씨를 조회하는 핵심 도구 함수 (get_real_current_weather)
def get_real_current_weather(city_name:str)->Dict[str, Any]:
"""
Open Weather API를 사용해 '현재' 날씨 정보를 JSON으로 조회합니다.
Args:
city_name (str): 도시 이름.
Returns:
Dict[str, Any]: API에서 반환된 원본 JSON 날씨 정보
"""
city_key = get_coordinates(city_name)
latitude=city_key["latitude"]
longitude=city_key["longitude"]
params = {
"lat":latitude,
"lon":longitude,
"appid":OPEN_WEATHER_API_KEY,
}
try:
endpoint="https://api.openweathermap.org/data/2.5/weather"
response = requests.get(endpoint, params=params)
response.raise_for_status() # 오류 발생 시 예외 처리
return response.json()
except requests.RequestException as e:
# LLM이 오류를 이해할 수 있도록 명확한 에러 메시지 반환
raise ValueError(f"API 호출 실패: {e}")
- 역할: LLM 에이전트가 직접 호출할 수 있도록 정의된 툴(Tool) 함수입니다.
- 처리 과정:
- 앞서 만든 get_coordinates(city_name) 함수를 먼저 호출해 입력받은 도시의 위도와 경도를 얻습니다.
- OpenWeatherMap 엔드포인트([https://api.openweathermap.org/data/2.5/weather](https://api.openweathermap.org/data/2.5/weather))에 위도, 경도, API 키를 파라미터로 전달해 현재 날씨 데이터를 요청합니다.
- 반환된 JSON 원본 데이터를 파이썬 딕셔너리 형태로 반환합니다. 예외가 발생할 경우, 에이전트가 상황을 파악할 수 있도록 ValueError로 감싸서 전달합니다.
4. 모델 사고 설정 (ThinkingConfig)
thinking_conf = types.ThinkingConfig(include_thoughts=True, thinking_budget=1024)
- 역할: Gemini 모델의 추론(Thinking) 능력을 활성화하고 제어하기 위한 설정 객체입니다.
- 세부 옵션: include_thoughts=True를 통해 모델이 답을 도출하기까지의 내부 사고 과정을 함께 확인할 수 있도록 하며, thinking_budget을 통해 사고 과정에 사용할 토큰 예산을 지정합니다. (다만 아래 에이전트 선언부에서는 현재 주석 처리되어 있습니다.)
5. 에이전트 정의 및 설정 (real_weather_agent)
real_weather_agent = Agent(
model=os.getenv("GOOGLE_MODEL", "gemini-3.5-flash"),
name ="real_weather_agent",
description="실제 OpenWeather API로 날씨를 알려주는 에이전트입니다.",
# planner=BuiltInPlanner(thinking_config=thinking_conf),
planner=PlanReActPlanner(),
instruction="""
사용자가 날씨를 물어보면, 반드시 Tool을 이용해 현재 날씨를 알려주세요.
""",
tools=[get_real_current_weather]
)
root_agent = real_weather_agent
- model: 사용할 LLM 모델명을 환경 변수에서 가져오며, 지정되지 않은 경우 기본값으로 gemini-3.5-flash를 사용합니다.
- planner: 에이전트가 문제를 해결할 때 사용할 계획 수립 전략을 지정합니다. 여기서는 PlanReActPlanner(Reasoning and Acting 패턴)를 사용하여 모델이 생각하고 행동(도구 호출)하는 단계를 거치도록 설정되어 있습니다.
- instruction: 에이전트가 수행해야 할 프롬프트 지침입니다. 사용자가 날씨를 물어보면 반드시 정의된 Tool을 사용하도록 유도합니다.
- tools: 에이전트가 스스로 판단하여 호출할 수 있는 파이썬 함수 목록을 전달합니다. 여기서는 앞서 작성한 [get_real_current_weather] 함수가 등록되어 있습니다.
- root_agent = real_weather_agent: ADK 프레임워크가 실행될 때 진입점(Root)이 되는 메인 에이전트로 지정합니다.
import os
import requests
from typing import Dict, Any
from dotenv import load_dotenv
from google.adk import Agent
from google.genai import types
from google.adk.planners import BuiltInPlanner
from google.adk.planners import PlanReActPlanner
# 1. 환경 설정
load_dotenv(override=True)
# --- API 키 및 설정 ---
# OpenWeatherMap 무료 API 키를 발급
OPEN_WEATHER_API_KEY = os.getenv("OPEN_WEATHER_API_KEY", "")
# 2. 좌표만들기
def get_coordinates(location:str):
"""도시 이름을 받아 Open-Meteo Geocoding API에서 위도(latitude)와 경도(longitude)를 조회한다."""
params = {
"name":location,
"count":1,
"language":"en",
"format":"json",
}
response = requests.get(url, params=params, timeout=10)
# response: <Response [200]>
response.raise_for_status()
data = response.json()
if not data.get("results"):
raise ValueError(f"도시를 찾을 수 없습니다. {location}")
result = data["results"][0]
return {
"name":result["name"],
"latitude":result["latitude"],
"longitude":result["longitude"],
"country":result.get("country",""),
}
# 3. [핵심] 실제 API를 호출하는 파이썬 함수를 'Tool'로 정의
def get_real_current_weather(city_name:str)->Dict[str, Any]:
"""
Open Weather API를 사용해 '현재' 날씨 정보를 JSON으로 조회합니다.
Args:
city_name (str): 도시 이름.
Returns:
Dict[str, Any]: API에서 반환된 원본 JSON 날씨 정보
"""
city_key = get_coordinates(city_name)
latitude=city_key["latitude"]
longitude=city_key["longitude"]
params = {
"lat":latitude,
"lon":longitude,
"appid":OPEN_WEATHER_API_KEY,
}
try:
# "https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={API key}"
response = requests.get(endpoint, params=params)
response.raise_for_status() # 오류 발생 시 예외 처리
return response.json()
except requests.RequestException as e:
# LLM이 오류를 이해할 수 있도록 명확한 에러 메시지 반환
raise ValueError(f"API 호출 실패: {e}")
## 추가1. [설정] ThinkingConfig 정의
# 모델의 사고(Thinking) 기능을 활성화하고 제어하기 위한 설정 객체다.
# include_thoughts=True로 설정하면 모델이 어떻게 답을 도출했는지 사고 과정을 포함할 수 있다.
thinking_conf = types.ThinkingConfig(include_thoughts=True, thinking_budget=1024)
# 4. Agent에 'tools'로 등록
## 추가2.[Agent 정의] BuiltInPlanner 장착
# [중요] BuiltInPlanner는 'Thinking'을 지원하는 모델을 사용해야 효과적이다.
# Thinking을 지원하는 모델인지 확인이 필요하다.
real_weather_agent = Agent(
model=os.getenv("GOOGLE_MODEL", "gemini-3.5-flash"),
name ="real_weather_agent",
description="실제 OpenWeather API로 날씨를 알려주는 에이전트입니다.",
# planner=BuiltInPlanner(thinking_config=thinking_conf),
planner=PlanReActPlanner(),
instruction="""
사용자가 날씨를 물어보면, 반드시 Tool을 이용해 현재 날씨를 알려주세요.
""",
tools=[get_real_current_weather]
)
root_agent = real_weather_agent
[참조]
Google ADK(Agent Development Kit)에서 Planner(플래너)는 에이전트가 복잡한 작업을 처리할 때 단순히 즉흥적으로 답변하지 않고, 어떤 순서로 생각하고 행동할지(Reasoning & Acting) 체계적인 계획을 세우도록 돕는 핵심 모듈입니다. BuiltInPlanner와 PlanReAtPlanner는 이 목표를 달성하기 위해 완전히 다른 접근 방식을 취합니다. 각각의 특징과 적합한 사용 상황을 정리합니다.
1. BuiltInPlanner (모델의 네이티브 사고 활용)
- 동작 방식: 모델 자체에 내장된 '싱킹(Thinking)' 기능을 그대로 활용합니다. 프롬프트를 별도로 조작하는 프롬프트 엔지니어링 방식이 아니라, API 요청 설정(types.ThinkingConfig)을 통해 모델 내부에 사고 예산(Token Budget)과 사고 과정 포함 여부(include_thoughts=True)를 전달합니다.
- 특징:
- ADK가 별도의 텍스트 파싱이나 강제적인 구조화 프롬프트를 덧붙이지 않습니다.
- 모델 본연의 추론 능력을 사용하므로 속도가 빠르고 자연스럽습니다.
- 사고 과정(Thoughts)이 로그에 별도로 분리되어 기록되므로 디버깅이 용이합니다.
🐦🔥 언제 쓰나요?
- 지원 모델 사용 시: Gemini 같이 네이티브 싱킹(Thinking)을 지원하는 최신 모델을 사용할 때 가장 적합합니다.
- 가볍고 효율적인 설정이 필요할 때: 복잡한 텍스트 파싱 오버헤드 없이 모델 고유의 추론 능력을 최대한 가볍게 끌어내고 싶을 때 사용합니다.
2. PlanReActPlanner (ReAct 프롬프트 구조화 패턴)
- 동작 방식: 전통적인 ReAct (Reason + Act) 패러다임을 프롬프트와 파서(Prompt-plus-Parser) 조합으로 구현한 방식입니다.
- 특징:
- 모델에게 요청을 보내기 전, 에이전트가 내부적으로 특정 태그나 구조(계획 수립, 추론, 행동, 관찰 등)를 따르도록 강제하는 시스템 지침(System Instruction)을 자동으로 앞에 덧붙입니다.
- 모델이 응답한 텍스트를 파서가 분석하여 단계별 계획과 도구 호출을 제어합니다.
- 모델 자체에 내장된 싱킹 기능 유무와 상관없이 어떤 모델이든 일관된 형태의 단계별 계획 루프를 강제할 수 있습니다.
🐦🔥 언제 쓰나요?
- 네이티브 싱킹이 없는 모델을 쓸 때: 모델 자체에 내장된 추론 기능이 없거나 제한적인 모델을 사용하면서도 구조화된 다단계 추론이 필요할 때 유용합니다.
- 엄격한 단계별 가시성이 필요할 때: 에이전트가 어떤 의사결정 경로를 거쳤는지 텍스트 태그 기반의 명확한 플랜(Plan) 형태로 강제 출력받고 통제하고 싶을 때 적합합니다.
⚖️ 한눈에 비교 요약
| 구분 | BuiltInPlanner | PlanReActPlanner |
| 작동 원리 | 모델 내부의 Native Thinking API 설정 활용 (ThinkingConfig) | 프롬프트 주입 및 응답 텍스트 파싱 (Prompt + Parser) |
| 모델 의존성 | 네이티브 싱킹을 지원하는 모델 필요 (예: 최신 Gemini) | 모델 종류와 무관하게 적용 가능 |
| 추천 상황 | 고성능 모델을 사용하며 가볍고 자연스러운 추론을 원할 때 | 모델 제약 없이 엄격한 구조의 ReAct 프로세스를 강제하고 싶을 때 |
위 코드에서 주석 처리되어 있던 thinking_conf와 BuiltInPlanner는 전자의 방식에 해당하며, 현재 활성화되어 있는 PlanReActPlanner는 후자의 방식으로 에이전트를 구동하고 있는 것입니다.
반응형