Skip to content

Instantly share code, notes, and snippets.

@bsnux
Last active August 27, 2026 21:28
Show Gist options
  • Select an option

  • Save bsnux/16b2bb5fda716964bcf7f3b73fc20203 to your computer and use it in GitHub Desktop.

Select an option

Save bsnux/16b2bb5fda716964bcf7f3b73fc20203 to your computer and use it in GitHub Desktop.
Simple weather AI agent using Google ADK
/*
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&current=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