mirror of https://github.com/dapr/dapr-agents.git
191 lines
8.4 KiB
Python
191 lines
8.4 KiB
Python
from typing import Optional
|
|
from dapr_agents import AgentTool
|
|
from datetime import datetime
|
|
import requests
|
|
import time
|
|
|
|
class WeatherForecast(AgentTool):
|
|
name: str = 'WeatherForecast'
|
|
description: str = 'A tool for retrieving the weather/temperature for a given city.'
|
|
|
|
# Default user agent
|
|
user_agent: str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15"
|
|
|
|
def handle_error(self, response: requests.Response, url: str, stage: str) -> None:
|
|
"""Handles error responses and raises a ValueError with detailed information."""
|
|
if response.status_code != 200:
|
|
raise ValueError(
|
|
f"Failed to get data during {stage}. Status: {response.status_code}. "
|
|
f"URL: {url}. Response: {response.text}"
|
|
)
|
|
if not response.json():
|
|
raise ValueError(
|
|
f"No data found during {stage}. URL: {url}. Response: {response.text}"
|
|
)
|
|
|
|
def _run(self, city: str, state: Optional[str] = None, country: Optional[str] = "usa") -> dict:
|
|
"""
|
|
Retrieves weather data by first fetching geocode data for the city and then fetching weather data.
|
|
|
|
Args:
|
|
city (str): The name of the city to get weather for.
|
|
state (Optional[str]): The two-letter state abbreviation (optional).
|
|
country (Optional[str]): The two-letter country abbreviation. Defaults to 'usa'.
|
|
|
|
Returns:
|
|
dict: A dictionary containing the city, state, country, and current temperature.
|
|
"""
|
|
headers = {
|
|
"User-Agent": self.user_agent
|
|
}
|
|
|
|
# Construct the geocode URL, conditionally including the state if it's provided
|
|
geocode_url = f"https://nominatim.openstreetmap.org/search?city={city}&country={country}"
|
|
if state:
|
|
geocode_url += f"&state={state}"
|
|
geocode_url += "&limit=1&format=jsonv2"
|
|
|
|
# Geocode request
|
|
geocode_response = requests.get(geocode_url, headers=headers)
|
|
self.handle_error(geocode_response, geocode_url, "geocode lookup")
|
|
|
|
# Add delay between requests
|
|
time.sleep(2)
|
|
|
|
geocode_data = geocode_response.json()
|
|
lat, lon = geocode_data[0]["lat"], geocode_data[0]["lon"]
|
|
|
|
# Use different APIs based on the country
|
|
if country.lower() == "usa":
|
|
# Weather.gov request for USA
|
|
weather_gov_url = f"https://api.weather.gov/points/{lat},{lon}"
|
|
weather_response = requests.get(weather_gov_url, headers=headers)
|
|
self.handle_error(weather_response, weather_gov_url, "weather lookup")
|
|
|
|
# Add delay between requests
|
|
time.sleep(2)
|
|
|
|
weather_data = weather_response.json()
|
|
forecast_url = weather_data["properties"]["forecast"]
|
|
|
|
# Forecast request
|
|
forecast_response = requests.get(forecast_url, headers=headers)
|
|
self.handle_error(forecast_response, forecast_url, "forecast lookup")
|
|
|
|
forecast_data = forecast_response.json()
|
|
today_forecast = forecast_data["properties"]["periods"][0]
|
|
|
|
# Return the weather data along with the city, state, and country
|
|
return {
|
|
"city": city,
|
|
"state": state,
|
|
"country": country,
|
|
"temperature": today_forecast["temperature"],
|
|
"unit": "Fahrenheit"
|
|
}
|
|
|
|
else:
|
|
# Met.no API for non-USA countries
|
|
met_no_url = f"https://api.met.no/weatherapi/locationforecast/2.0/compact?lat={lat}&lon={lon}"
|
|
weather_response = requests.get(met_no_url, headers=headers)
|
|
self.handle_error(weather_response, met_no_url, "Met.no weather lookup")
|
|
|
|
weather_data = weather_response.json()
|
|
temperature_unit = weather_data["properties"]["meta"]["units"]["air_temperature"]
|
|
today_forecast = weather_data["properties"]["timeseries"][0]["data"]["instant"]["details"]["air_temperature"]
|
|
|
|
# Return the weather data along with the city, state, and country
|
|
return {
|
|
"city": city,
|
|
"state": state,
|
|
"country": country,
|
|
"temperature": today_forecast,
|
|
"unit": temperature_unit
|
|
}
|
|
|
|
class HistoricalWeather(AgentTool):
|
|
name: str = 'HistoricalWeather'
|
|
description: str = 'A tool for retrieving historical weather data (temperature) for a given city.'
|
|
|
|
# Default user agent
|
|
user_agent: str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15"
|
|
|
|
def handle_error(self, response: requests.Response, url: str, stage: str) -> None:
|
|
"""Handles error responses and raises a ValueError with detailed information."""
|
|
if response.status_code != 200:
|
|
raise ValueError(
|
|
f"Failed to get data during {stage}. Status: {response.status_code}. "
|
|
f"URL: {url}. Response: {response.text}"
|
|
)
|
|
if not response.json():
|
|
raise ValueError(
|
|
f"No data found during {stage}. URL: {url}. Response: {response.text}"
|
|
)
|
|
|
|
def _run(self, city: str, state: Optional[str] = None, country: Optional[str] = "usa", start_date: Optional[str] = None, end_date: Optional[str] = None) -> dict:
|
|
"""
|
|
Retrieves historical weather data for the city by first fetching geocode data and then historical weather data.
|
|
|
|
Args:
|
|
city (str): The name of the city to get weather for.
|
|
state (Optional[str]): The two-letter state abbreviation (optional).
|
|
country (Optional[str]): The two-letter country abbreviation. Defaults to 'usa'.
|
|
start_date (Optional[str]): Start date for historical data (YYYY-MM-DD format).
|
|
end_date (Optional[str]): End date for historical data (YYYY-MM-DD format).
|
|
|
|
Returns:
|
|
dict: A dictionary containing the city, state, country, and historical temperature data.
|
|
"""
|
|
headers = {
|
|
"User-Agent": self.user_agent
|
|
}
|
|
|
|
# Validate dates
|
|
current_date = datetime.now().strftime('%Y-%m-%d')
|
|
if start_date >= current_date or end_date >= current_date:
|
|
raise ValueError("Both start_date and end_date must be earlier than the current date.")
|
|
|
|
if (datetime.strptime(end_date, "%Y-%m-%d") - datetime.strptime(start_date, "%Y-%m-%d")).days > 30:
|
|
raise ValueError("The time span between start_date and end_date cannot exceed 30 days.")
|
|
|
|
# Construct the geocode URL, conditionally including the state if it's provided
|
|
geocode_url = f"https://nominatim.openstreetmap.org/search?city={city}&country={country}"
|
|
if state:
|
|
geocode_url += f"&state={state}"
|
|
geocode_url += "&limit=1&format=jsonv2"
|
|
|
|
# Geocode request
|
|
geocode_response = requests.get(geocode_url, headers=headers)
|
|
self.handle_error(geocode_response, geocode_url, "geocode lookup")
|
|
|
|
# Add delay between requests
|
|
time.sleep(2)
|
|
|
|
geocode_data = geocode_response.json()
|
|
lat, lon = geocode_data[0]["lat"], geocode_data[0]["lon"]
|
|
|
|
# Historical weather request
|
|
historical_weather_url = f"https://archive-api.open-meteo.com/v1/archive?latitude={lat}&longitude={lon}&start_date={start_date}&end_date={end_date}&hourly=temperature_2m"
|
|
weather_response = requests.get(historical_weather_url, headers=headers)
|
|
self.handle_error(weather_response, historical_weather_url, "historical weather lookup")
|
|
|
|
weather_data = weather_response.json()
|
|
|
|
# Extract time and temperature data
|
|
timestamps = weather_data["hourly"]["time"]
|
|
temperatures = weather_data["hourly"]["temperature_2m"]
|
|
temperature_unit = weather_data["hourly_units"]["temperature_2m"]
|
|
|
|
# Combine timestamps and temperatures into a dictionary
|
|
temperature_data = {timestamps[i]: temperatures[i] for i in range(len(timestamps))}
|
|
|
|
# Return the structured weather data along with the city, state, country
|
|
return {
|
|
"city": city,
|
|
"state": state,
|
|
"country": country,
|
|
"start_date": start_date,
|
|
"end_date": end_date,
|
|
"temperature_data": temperature_data,
|
|
"unit": temperature_unit
|
|
} |