package weather import ( "context" "encoding/json" "fmt" "net/http" "net/url" "strings" "trankilou.fr/lassistanoque/backend/internal/domain" ) type WeatherTool struct { } type WeatherConfig struct { } func (t *WeatherTool) Name() string { return "weather" } func (t *WeatherTool) Description() string { return "Get the current weather for a given location" } func (t *WeatherTool) Params() []domain.ToolParam { return []domain.ToolParam{} } func (t *WeatherTool) Definition(ctx context.Context) *domain.ToolDefinition { return &domain.ToolDefinition{ Type: "function", Function: &domain.ToolFunction{ Name: t.Name(), Description: t.Description(), Parameters: map[string]any{ "type": "object", "properties": map[string]any{ "location": map[string]any{ "type": "string", "description": "The city name, e.g., 'Paris' or 'New York'", }, }, "required": []string{"location"}, }, }, } } func (t *WeatherTool) Execute(ctx context.Context, input []byte, configuration string) ([]byte, error) { var params struct { Location string `json:"location"` } err := json.Unmarshal(input, ¶ms) if err != nil { return nil, err } resp, err := http.Get("https://geocoding-api.open-meteo.com/v1/search?name=" + url.QueryEscape(params.Location)) if err != nil { return nil, err } defer resp.Body.Close() var location struct { Results []struct { Latitude float64 `json:"latitude"` Longitude float64 `json:"longitude"` } `json:"results"` } err = json.NewDecoder(resp.Body).Decode(&location) if err != nil { return nil, err } if len(location.Results) == 0 { return nil, fmt.Errorf("location not found : %s", input) } lon := location.Results[0].Longitude lat := location.Results[0].Latitude weatherResp, err := http.Get(fmt.Sprintf("https://api.open-meteo.com/v1/forecast?latitude=%f&longitude=%f¤t_weather=true&daily=weather_code,temperature_2m_max,temperature_2m_min,rain_sum,wind_speed_10m_max&hourly=temperature_2m&forecast_days=15", lat, lon)) if err != nil { return nil, err } defer weatherResp.Body.Close() var weatherData struct { CurrentWeatherUnits struct { Temperature string `json:"temperature"` WindSpeed string `json:"windspeed"` } `json:"current_weather_units"` CurrentWeather struct { Time string `json:"time"` Temperature float64 `json:"temperature"` WeatherCode int `json:"weathercode"` } `json:"current_weather"` DailyUnits struct { TemperatureMax string `json:"temperature_2m_max"` TemperatureMin string `json:"temperature_2m_min"` Rain string `json:"rain_sum"` WindSpeed string `json:"wind_speed_10m_max"` } `json:"daily_units"` Daily struct { Time []string `json:"time"` TemperatureMax []float64 `json:"temperature_2m_max"` TemperatureMin []float64 `json:"temperature_2m_min"` Rain []float64 `json:"rain_sum"` WindSpeed []float64 `json:"wind_speed_10m_max"` } `json:"daily"` } err = json.NewDecoder(weatherResp.Body).Decode(&weatherData) if err != nil { return nil, err } var builder strings.Builder fmt.Fprintf(&builder, "Current weather in %s\n", params.Location) fmt.Fprintf(&builder, "- Time: %s\n", weatherData.CurrentWeather.Time) fmt.Fprintf(&builder, "- Temperature: %.2f %s\n", weatherData.CurrentWeather.Temperature, weatherData.CurrentWeatherUnits.Temperature) fmt.Fprintf(&builder, "- Weather code: %d - %s\n", weatherData.CurrentWeather.WeatherCode, wmoCodeToDescription(weatherData.CurrentWeather.WeatherCode)) if len(weatherData.Daily.WindSpeed) > 0 { fmt.Fprintf(&builder, "- Wind speed: %.2f %s\n", weatherData.Daily.WindSpeed[0], weatherData.DailyUnits.WindSpeed) } if len(weatherData.Daily.TemperatureMax) > 0 { fmt.Fprintf(&builder, "- Max temperature: %.2f %s\n", weatherData.Daily.TemperatureMax[0], weatherData.DailyUnits.TemperatureMax) } if len(weatherData.Daily.TemperatureMin) > 0 { fmt.Fprintf(&builder, "- Min temperature: %.2f %s\n", weatherData.Daily.TemperatureMin[0], weatherData.DailyUnits.TemperatureMin) } if len(weatherData.Daily.Rain) > 0 { fmt.Fprintf(&builder, "- Rainfall: %.2f %s\n", weatherData.Daily.Rain[0], weatherData.DailyUnits.Rain) } fmt.Fprintf(&builder, "\nForecast for the next days:\n") // Iterate over the daily data and append to the repor for i := 0; i < len(weatherData.Daily.Time); i++ { fmt.Fprintf(&builder, "%s: Max Temp: %.2f %s, Min Temp: %.2f %s, Rain: %.2f %s, Wind Speed: %.2f %s\n", weatherData.Daily.Time[i], weatherData.Daily.TemperatureMax[i], weatherData.DailyUnits.TemperatureMax, weatherData.Daily.TemperatureMin[i], weatherData.DailyUnits.TemperatureMin, weatherData.Daily.Rain[i], weatherData.DailyUnits.Rain, weatherData.Daily.WindSpeed[i], weatherData.DailyUnits.WindSpeed) } return []byte(builder.String()), nil } func wmoCodeToDescription(code int) string { switch code { case 0: return "Clear sky" case 1, 2, 3: return "Mainly clear, partly cloudy, and overcast" case 45, 48: return "Fog and depositing rime fog" case 51, 53, 55: return "Drizzle: Light, moderate, and dense intensity" case 56, 57: return "Freezing Drizzle: Light and dense intensity" case 61, 63, 65: return "Rain: Slight, moderate and heavy intensity" case 66, 67: return "Freezing Rain: Light and heavy intensity" case 71, 73, 75: return "Snowfall: Slight, moderate, and heavy intensity" case 77: return "Snow grains" case 80, 81, 82: return "Rain showers: Slight, moderate, and violent" case 85, 86: return "Snow showers slight and heavy" case 95: return "Thunderstorm: Slight and heavy intensity" case 96, 99: return "Thunderstorm with slight and heavy hail" default: return "N/A" } }