from datetime import UTC, datetime, timedelta, timezone import requests from personal_agent.tools.base import Tool, ToolError, ToolResult class WeatherTool(Tool): @property def name(self) -> str: return "weather" @property def description(self) -> str: return "Get current weather conditions for a location" @property def schema(self) -> dict[str, object]: return { "type": "object", "properties": { "location": { "type": "string", "description": ("The city or location to get weather for."), }, }, "required": ["location"], } def execute(self, arguments: dict[str, object]) -> ToolResult: location = arguments.get("location") if not isinstance(location, str): raise ToolError("location must be a string") geocoding_response = requests.get( "https://geocoding-api.open-meteo.com/v1/search", params={ "name": location, "count": 1, "language": "en", "format": "json", }, timeout=10, ) geocoding_response.raise_for_status() geocoding_data = geocoding_response.json() results = geocoding_data.get("results") if not results: raise ToolError(f"location not found: {location}") place = results[0] latitude = place["latitude"] longitude = place["longitude"] timezone_name = place["timezone"] forecast_response = requests.get( "https://api.open-meteo.com/v1/forecast", params={ "latitude": latitude, "longitude": longitude, "hourly": ( "temperature_2m," "precipitation_probability," "precipitation," "weather_code," "wind_speed_10m" ), "timezone": timezone_name, "forecast_days": 2, }, timeout=10, ) forecast_response.raise_for_status() forecast = forecast_response.json() hourly = forecast["hourly"] tomorrow = ( (datetime.now(UTC) + timedelta(days=1)) .astimezone(timezone(timedelta(seconds=forecast["utc_offset_seconds"]))) .date() ) temperatures = [] precipitation_probabilities = [] precipitation = [] wind_speeds = [] weather_codes = [] for index, timestamp in enumerate(hourly["time"]): forecast_date = datetime.fromisoformat(timestamp).date() if forecast_date != tomorrow: continue temperatures.append(hourly["temperature_2m"][index]) precipitation_probabilities.append( hourly["precipitation_probability"][index] ) precipitation.append(hourly["precipitation"][index]) wind_speeds.append(hourly["wind_speed_10m"][index]) weather_codes.append(hourly["weather_code"][index]) if not temperatures: raise ToolError("no forecast data available for tomorrow") return ToolResult( content=( f"Location: {place['name']}, {place['country']}\n" f"Forecast date: {tomorrow}\n" f"Temperature: {min(temperatures):.1f}–" f"{max(temperatures):.1f} °C\n" f"Maximum precipitation probability: " f"{max(precipitation_probabilities)}%\n" f"Total precipitation: {sum(precipitation):.1f} mm\n" f"Maximum wind speed: {max(wind_speeds):.1f} km/h\n" f"Weather codes: {sorted(set(weather_codes))}" ) )