Last active
August 27, 2026 21:28
-
-
Save bsnux/16b2bb5fda716964bcf7f3b73fc20203 to your computer and use it in GitHub Desktop.
Simple weather AI agent using Google ADK
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| Simple interaction w/ this agent: | |
| User -> what's the time in Moscow? | |
| Agent -> The current time in Moscow is 23:14:03 MSK on 2026-08-27. | |
| User -> what's the weather in Toronto, Canada? | |
| Agent -> The weather in Toronto is 26.2°C, with 51% humidity and wind at 16.2 km/h. | |
| User -> compare the weather in Toronto and San Juan, PR | |
| Agent -> In Toronto, the weather is 26.2°C, with 51% humidity and wind at 16.2 km/h. In San Juan, it is 29.6°C, with 72% humidity and wind at 26.0 km/h. | |
| User -> where is the humidity higher in Tokyo or in Munich? | |
| Agent -> The humidity in Tokyo is 97%, while in Munich it is 69%. So, the humidity is higher in Tokyo. | |
| */ | |
| package main | |
| import ( | |
| "context" | |
| "encoding/json" | |
| "fmt" | |
| "log" | |
| "net/http" | |
| "net/url" | |
| "os" | |
| "time" | |
| "google.golang.org/genai" | |
| "google.golang.org/adk/v2/agent" | |
| "google.golang.org/adk/v2/agent/llmagent" | |
| "google.golang.org/adk/v2/cmd/launcher" | |
| "google.golang.org/adk/v2/cmd/launcher/full" | |
| "google.golang.org/adk/v2/model/gemini" | |
| "google.golang.org/adk/v2/tool" | |
| "google.golang.org/adk/v2/tool/functiontool" | |
| ) | |
| const modelName = "gemini-2.5-flash" | |
| // CityArgs is the input schema shared by both tools. | |
| type CityArgs struct { | |
| City string `json:"city"` | |
| } | |
| // geoResult holds the fields we need from the Open-Meteo geocoding response. | |
| type geoResult struct { | |
| Latitude float64 `json:"latitude"` | |
| Longitude float64 `json:"longitude"` | |
| Timezone string `json:"timezone"` // IANA tz, e.g. "America/New_York" | |
| Name string `json:"name"` | |
| } | |
| // geocode resolves a city name to coordinates and timezone using the free | |
| // Open-Meteo geocoding API (no key required). | |
| func geocode(city string) (*geoResult, error) { | |
| apiURL := "https://geocoding-api.open-meteo.com/v1/search?count=1&name=" + url.QueryEscape(city) | |
| resp, err := http.Get(apiURL) | |
| if err != nil { | |
| return nil, fmt.Errorf("geocoding request failed: %w", err) | |
| } | |
| defer resp.Body.Close() | |
| var payload struct { | |
| Results []geoResult `json:"results"` | |
| } | |
| if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { | |
| return nil, fmt.Errorf("geocoding decode failed: %w", err) | |
| } | |
| if len(payload.Results) == 0 { | |
| return nil, fmt.Errorf("city %q not found", city) | |
| } | |
| return &payload.Results[0], nil | |
| } | |
| // getWeather fetches current weather for a city using the free Open-Meteo | |
| // forecast API (no key required). | |
| func getWeather(_ agent.Context, args CityArgs) (map[string]any, error) { | |
| geo, err := geocode(args.City) | |
| if err != nil { | |
| return map[string]any{"status": "error", "error_message": err.Error()}, nil | |
| } | |
| apiURL := fmt.Sprintf( | |
| "https://api.open-meteo.com/v1/forecast?latitude=%f&longitude=%f¤t=temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code&temperature_unit=celsius&wind_speed_unit=kmh", | |
| geo.Latitude, geo.Longitude, | |
| ) | |
| resp, err := http.Get(apiURL) | |
| if err != nil { | |
| return map[string]any{"status": "error", "error_message": err.Error()}, nil | |
| } | |
| defer resp.Body.Close() | |
| var payload struct { | |
| Current struct { | |
| Temperature float64 `json:"temperature_2m"` | |
| Humidity int `json:"relative_humidity_2m"` | |
| WindSpeed float64 `json:"wind_speed_10m"` | |
| WeatherCode int `json:"weather_code"` | |
| } `json:"current"` | |
| } | |
| if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { | |
| return map[string]any{"status": "error", "error_message": err.Error()}, nil | |
| } | |
| c := payload.Current | |
| report := fmt.Sprintf( | |
| "Weather in %s: %.1f°C, humidity %d%%, wind %.1f km/h, condition code %d.", | |
| geo.Name, c.Temperature, c.Humidity, c.WindSpeed, c.WeatherCode, | |
| ) | |
| return map[string]any{"status": "success", "report": report}, nil | |
| } | |
| // getCurrentTime returns the current local time for any city by resolving its | |
| // IANA timezone from the Open-Meteo geocoding API. | |
| func getCurrentTime(_ agent.Context, args CityArgs) (map[string]any, error) { | |
| geo, err := geocode(args.City) | |
| if err != nil { | |
| return map[string]any{"status": "error", "error_message": err.Error()}, nil | |
| } | |
| tz, err := time.LoadLocation(geo.Timezone) | |
| if err != nil { | |
| return map[string]any{"status": "error", "error_message": fmt.Sprintf("unknown timezone %q", geo.Timezone)}, nil | |
| } | |
| now := time.Now().In(tz) | |
| report := fmt.Sprintf("The current time in %s is %s.", geo.Name, now.Format("2006-01-02 15:04:05 MST")) | |
| return map[string]any{"status": "success", "report": report}, nil | |
| } | |
| func main() { | |
| // Use Vertex AI with Application Default Credentials. | |
| // Run once to authenticate locally: gcloud auth application-default login | |
| os.Setenv("GOOGLE_GENAI_USE_VERTEXAI", "TRUE") | |
| os.Setenv("GOOGLE_CLOUD_PROJECT", "g-hlf-np-platform-svc") | |
| os.Setenv("GOOGLE_CLOUD_LOCATION", "us-central1") | |
| os.Unsetenv("GOOGLE_API_KEY") | |
| ctx := context.Background() | |
| model, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{}) | |
| if err != nil { | |
| log.Fatalf("Failed to create model: %v", err) | |
| } | |
| weatherTool, err := functiontool.New[CityArgs, map[string]any]( | |
| functiontool.Config{ | |
| Name: "get_weather", | |
| Description: "Returns the current weather (temperature, humidity, wind) for any city.", | |
| }, | |
| getWeather, | |
| ) | |
| if err != nil { | |
| log.Fatalf("Failed to create get_weather tool: %v", err) | |
| } | |
| timeTool, err := functiontool.New[CityArgs, map[string]any]( | |
| functiontool.Config{ | |
| Name: "get_current_time", | |
| Description: "Returns the current local time for any city.", | |
| }, | |
| getCurrentTime, | |
| ) | |
| if err != nil { | |
| log.Fatalf("Failed to create get_current_time tool: %v", err) | |
| } | |
| a, err := llmagent.New(llmagent.Config{ | |
| Name: "weather_time_agent", | |
| Model: model, | |
| Description: "Agent to answer questions about the time and weather in any city.", | |
| Instruction: "You are a helpful agent who answers questions about the current time and weather in any city.", | |
| Tools: []tool.Tool{weatherTool, timeTool}, | |
| }) | |
| if err != nil { | |
| log.Fatalf("Failed to create agent: %v", err) | |
| } | |
| config := &launcher.Config{ | |
| AgentLoader: agent.NewSingleLoader(a), | |
| } | |
| l := full.NewLauncher() | |
| if err = l.Execute(ctx, config, os.Args[1:]); err != nil { | |
| log.Fatalf("Run failed: %v\n\n%s", err, l.CommandLineSyntax()) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment