Google Cloud NEXT Writing Challenge

At Google Cloud NEXT '26, Google announced that 330 of its customers each processed over one trillion tokens in the past 12 months. At the center of it is the Agent Development Kit (ADK), with support for Python, TypeScript, Go, and Java.
This tutorial is a hands-on first look at ADK from a developer who built with it. You will build a real working weather agent from scratch using a free live weather API, then extend it step by step into a multi-agent system where agents delegate to each other, remember user preferences across turns.
By the end, you will have one project and a clear picture of how ADK works end-to-end.
What Is Gemini Enterprise Agent Platform
For developers, the relevant change from Vertex AI is this: the Agent Development Kit is now the primary way to build on the platform. It is open-source, model-agnostic, and ships with stable releases in Python, TypeScript, Go, and Java. This tutorial uses Python.
The platform organizes work across two entry points. Agent Studio is a visual low-code canvas for prototyping and non-technical builders. ADK is the code path for production systems. If you have shipped anything with an LLM API before, ADK will feel familiar within the first five minutes.
Prerequisites
Before starting, make sure you have the following:
- Python 3.10 or later
- pip installed
- A free Gemini API key from Google AI Studio
You do not need a Google Cloud account or billing setup. The free AI Studio API key is sufficient for everything in this tutorial.
Step 1: Set Up Your Environment
Create a virtual environment. This keeps ADK's dependencies isolated from anything else on your system.
python3 -m venv .venv _
_source .venv/bin/activate
Once your virtual environment is active, install ADK and the requests library. requests is what the weather tool will use to call the live weather API.
pip install google-adk requests
Verify the installation:
You should see a version number. If you see 'command not found', your virtual environment is not active. Run the source command above again.
Step 2: Create the Project
ADK's scaffolding command generates a ready-to-run project structure:
adk create weather_agent
When prompted, choose 1 (Gemini) for the model and (Google AI) for the backend, and also your api key that you got earlier. This configures the project to use your AI Studio API key directly, with no cloud billing required.
The command creates the following structure:
weather_agent/
agent.py # where all your agent code lives
.env # API keys and environment config
__init.py
Step 3: Write the Weather Tool
This tutorial uses Open-Meteo, a free and open-source weather API. It requires no account and no API key. You call it, and it returns real, live weather data. done.
Open weather_agent/agent.py in your editor and replace it with the following code:
import requests
from google.adk.agents import Agent
MODEL = "gemini-flash-latest"
def get_weather(city: str) -> dict:
"""Retrieves the current weather for a specified city.
Uses Open-Meteo's free geocoding and weather APIs. No API key required.
Geocoding API: https://open-meteo.com/en/docs/geocoding-api
Forecast API: https://open-meteo.com/en/docs
Args:
city (str): The name of the city, e.g. "Lagos", "London", "Tokyo".
Returns:
dict: Contains 'status' ('success' or 'error').
On success, includes a 'report' key with a human-readable summary.
On error, includes an 'error_message' key describing what went wrong.
"""
# Step 1: Convert the city name to latitude/longitude coordinates
geo_url = (
"https://geocoding-api.open-meteo.com/v1/search"
f"?name={city}&count=1&language=en&format=json"
)
try:
geo_response = requests.get(geo_url, timeout=10)
geo_data = geo_response.json()
except requests.RequestException as e:
return {"status": "error", "error_message": f"Network error during geocoding: {e}"}
if not geo_data.get("results"):
return {
"status": "error",
"error_message": f"Could not find a location matching '{city}'.",
}
location = geo_data["results"][0]
lat = location["latitude"]
lon = location["longitude"]
location_name = location["name"]
country = location.get("country", "")
# Step 2: Fetch current weather using those coordinates
weather_url = (
"https://api.open-meteo.com/v1/forecast"
f"?latitude={lat}&longitude={lon}"
"¤t=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m"
"&timezone=auto"
)
try:
weather_response = requests.get(weather_url, timeout=10)
weather_data = weather_response.json()
except requests.RequestException as e:
return {"status": "error", "error_message": f"Network error fetching weather: {e}"}
current = weather_data.get("current", {})
temp_c = current.get("temperature_2m")
humidity = current.get("relative_humidity_2m")
wind_speed = current.get("wind_speed_10m")
# WMO weather interpretation codes
# Full list: https://open-meteo.com/en/docs#weathervariables
weather_descriptions = {
0: "clear sky",
1: "mainly clear", 2: "partly cloudy", 3: "overcast",
45: "fog", 48: "rime fog",
51: "light drizzle", 53: "moderate drizzle", 55: "dense drizzle",
61: "light rain", 63: "moderate rain", 65: "heavy rain",
71: "light snow", 73: "moderate snow", 75: "heavy snow",
80: "light showers", 81: "moderate showers", 82: "heavy showers",
95: "thunderstorm",
}
condition = weather_descriptions.get(
current.get("weather_code", -1), "unknown conditions"
)
report = (
f"Current weather in {location_name}, {country}: {condition}. "
f"Temperature: {temp_c}°C, Humidity: {humidity}%, Wind: {wind_speed} km/h."
)
return {"status": "success", "report": report}
root_agent = Agent(
name="weather_agent",
model=MODEL,
description="Provides real-time weather information for any city in the world.",
instruction=(
"You are a helpful weather assistant. "
"When the user asks about the weather in a city, use the 'get_weather' tool. "
"If the tool returns an error, let the user know politely and suggest trying "
"a different city name. Present successful results in a clear, conversational way."
),
tools=[get_weather],
)
A few things are worth understanding before you run this.
_get_weather _ makes two HTTP calls in sequence. The first call goes to Open-Meteo's Geocoding API, which converts the city name into latitude and longitude. The second call goes to the Forecast API, which returns current conditions for those coordinates. This two-step approach means the tool works for any city name in any language that Open-Meteo's geocoder supports.
The docstring on get_weather is not documentation for you. The Gemini model reads it at runtime to understand what the tool does, when to call it, what argument it expects, and what shape the response takes. A vague docstring leads to the model misusing the tool or skipping it entirely. Make sure you keep docstrings precise.
The variable name root_agent is required. ADK looks for exactly this name in agent.py when you run adk web or adk run. If you name it something else, ADK will not find it.
Step 4: Run the Agent
From the directory that contains your weather_agent folder (not from inside it), run: adk web --port 8001
Open http://localhost:8001 in your browser. Select weather_agent from the dropdown in the upper left corner and send this message: What is the weather in Tokyo right now?
The agent calls get_weather("Tokyo"), receives live data from Open-Meteo, and responds with the actual current conditions.
Step 5: Add Specialist Sub-Agents
The weather agent handles weather questions well. But when a user says "Hello" or "Thanks, goodbye", it has nothing useful to do with it. You could add greeting and farewell logic directly to the existing agent, but that approach does not scale. As tasks multiply, a single agent with too many responsibilities becomes harder to debug and tune.
ADK handles this through multi-agent delegation. You build specialist agents for focused tasks and wire them to a root agent. The root agent receives every message and routes it to the appropriate specialist based on the user's request.
Open weather_agent/agent.py. Keep everything you have already written. Add the following two tool functions directly after get_weather, before the root_agent definition:
def say_hello(name: str = None) -> str:
"""Greets the user by name if provided, otherwise gives a general greeting.
Args:
name (str, optional): The name of the person to greet.
Returns:
str: A friendly greeting.
"""
if name:
return f"Hello, {name}! How can I help you today?"
return "Hello! How can I help you today?"
def say_goodbye() -> str:
"""Provides a polite farewell message to close the conversation.
Returns:
str: A goodbye message.
"""
return "Goodbye! Have a great day."
Now add the two specialist agent definitions directly after those tools, still before root_agent:
greeting_agent = Agent(
model=MODEL,
name="greeting_agent",
description="Handles greetings and hellos using the 'say_hello' tool.",
instruction=(
"You are the Greeting Agent. Your only job is to greet the user. "
"Use the 'say_hello' tool. If the user shares their name, pass it to the tool. "
"Do not respond to any other type of request."
),
tools=[say_hello],
)
farewell_agent = Agent(
model=MODEL,
name="farewell_agent",
description="Handles farewells and goodbyes using the 'say_goodbye' tool.",
instruction=(
"You are the Farewell Agent. Your only job is to say goodbye. "
"Use the 'say_goodbye' tool when the user is ending the conversation. "
"Do not respond to any other type of request."
),
tools=[say_goodbye],
)
Update the root_agent definition at the bottom of the file to include the sub_agents parameter:
root_agent = Agent(
name="weather_agent",
model=MODEL,
description="Provides real-time weather information for any city in the world.",
instruction=(
"You are the main Weather Agent coordinating a small team. "
"For weather questions, use the 'get_weather' tool yourself. "
"For greetings like 'hi' or 'hello', delegate to 'greeting_agent'. "
"For farewells like 'bye' or 'goodbye', delegate to 'farewell_agent'. "
"Do not handle requests outside these three categories."
),
tools=[get_weather],
sub_agents=[greeting_agent, farewell_agent],
)
Save the file. Stop the adk web with Ctrl+C, then restart it from your project's parent directory:
adk web --port 8001
Open http://localhost:8001, select weather_agent, and send these three messages one at a time:
- Hello, my name is Ada.
- What is the weather in New York?
- Thanks, goodbye!
After each message, open the Trace panel and watch where execution goes. For the greeting, you will see the root agent hand off to greeting_agent, which calls say_hello("Ada"). For the weather question, the root agent calls get_weather directly without delegating. For the farewell, control transfers to farewell_agent, which calls say_goodbye.
How the routing works: The description field on each sub-agent is what the root agent's model reads when deciding where to send a message. It is not documentation for you. It is the text the model uses to match intent to the agent. If the description is vague or overlapping, routing will be inconsistent. Write it precisely and make sure to test edge cases.
Step 6: Add Session State
The agents currently treat every turn as if the conversation just started. If the user says "I prefer Fahrenheit" in one message, the next weather result still comes back in Celsius because the tool has no memory of that preference. ADK's session state solves this.
Session state is a dictionary attached to a running session. Any tool can read from it and write to it through a ToolContext parameter that ADK injects automatically. The state persists through every turn in that session, so preferences set early in a conversation remain available for the rest of the conversation.
Two additions are needed in agent.py for this step: a new import and an update to get_weather.
Add the import at the top of the file, alongside the existing imports:
from google.adk.tools.tool_context import ToolContext
Update the get_weather _signature to accept _tool_context as its last parameter. ADK detects this parameter by name and injects it at runtime. You never pass it yourself.
Inside the function, add a line near the top to read the temperature preference from the state and add temperature conversion logic before building the report string. Add one more line at the end to write the queried city back into the state.
def get_weather(city: str, tool_context: ToolContext) -> dict:
"""Retrieves the current weather for a specified city.
Reads 'temperature_unit' from session state ('celsius' or 'fahrenheit').
Defaults to Celsius if the preference has not been set.
Writes 'last_city_checked' to session state after every successful lookup.
Uses Open-Meteo's free geocoding and weather APIs. No API key required.
Geocoding API: https://open-meteo.com/en/docs/geocoding-api
Forecast API: https://open-meteo.com/en/docs
Args:
city (str): The name of the city, e.g. "Lagos", "London", "Tokyo".
tool_context (ToolContext): Injected by ADK. Read/write access to session state.
Returns:
dict: Contains 'status' ('success' or 'error').
On success, includes a 'report' key with a human-readable summary.
On error, includes an 'error_message' key describing what went wrong.
"""
# Read the user's temperature preference from session state
preferred_unit = tool_context.state.get("temperature_unit", "celsius")
# Step 1: Convert the city name to latitude/longitude coordinates
geo_url = (
"https://geocoding-api.open-meteo.com/v1/search"
f"?name={city}&count=1&language=en&format=json"
)
try:
geo_response = requests.get(geo_url, timeout=10)
geo_data = geo_response.json()
except requests.RequestException as e:
return {"status": "error", "error_message": f"Network error during geocoding: {e}"}
if not geo_data.get("results"):
return {
"status": "error",
"error_message": f"Could not find a location matching '{city}'.",
}
location = geo_data["results"][0]
lat = location["latitude"]
lon = location["longitude"]
location_name = location["name"]
country = location.get("country", "")
# Step 2: Fetch current weather using those coordinates
weather_url = (
"https://api.open-meteo.com/v1/forecast"
f"?latitude={lat}&longitude={lon}"
"¤t=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m"
"&timezone=auto"
)
try:
weather_response = requests.get(weather_url, timeout=10)
weather_data = weather_response.json()
except requests.RequestException as e:
return {"status": "error", "error_message": f"Network error fetching weather: {e}"}
current = weather_data.get("current", {})
temp_c = current.get("temperature_2m")
humidity = current.get("relative_humidity_2m")
wind_speed = current.get("wind_speed_10m")
# Convert temperature based on the user's session preference
if preferred_unit.lower() == "fahrenheit":
temp_display = round((temp_c * 9 / 5) + 32, 1)
unit_symbol = "°F"
else:
temp_display = temp_c
unit_symbol = "°C"
weather_descriptions = {
0: "clear sky",
1: "mainly clear", 2: "partly cloudy", 3: "overcast",
45: "fog", 48: "rime fog",
51: "light drizzle", 53: "moderate drizzle", 55: "dense drizzle",
61: "light rain", 63: "moderate rain", 65: "heavy rain",
71: "light snow", 73: "moderate snow", 75: "heavy snow",
80: "light showers", 81: "moderate showers", 82: "heavy showers",
95: "thunderstorm",
}
condition = weather_descriptions.get(
current.get("weather_code", -1), "unknown conditions"
)
report = (
f"Current weather in {location_name}, {country}: {condition}. "
f"Temperature: {temp_display}{unit_symbol}, Humidity: {humidity}%, "
f"Wind: {wind_speed} km/h."
)
# Write the queried city to session state
tool_context.state["last_city_checked"] = city
return {"status": "success", "report": report}
Also, add output_key to the root_agent. This tells ADK to automatically save the agent's final text response into the session.state["last_weather_report"] after every turn:
root_agent = Agent(
name="weather_agent",
model=MODEL,
description="Provides real-time weather information for any city in the world.",
instruction=(
"You are the main Weather Agent coordinating a small team. "
"For weather questions, use the 'get_weather' tool yourself. "
"For greetings like 'hi' or 'hello', delegate to 'greeting_agent'. "
"For farewells like 'bye' or 'goodbye', delegate to 'farewell_agent'. "
"Do not handle requests outside these three categories."
),
tools=[get_weather],
sub_agents=[greeting_agent, farewell_agent],
output_key="last_weather_report",
)
Save the file and restart the ADK web:
adk web --port 8001
Send: What is the weather in London?
The response will come back in Celsius. Look at the State panel. Two keys will be written automatically: last_city_checked set to "london" by the tool, and last_weather_report, set to the full response text by_ output_key_.
Now, click into the State panel and add a new entry:
json
{"temperature_unit": "fahrenheit"}
Send: What is the weather in New York?
The response will come back in Fahrenheit. The tool read temperature_unit from the state, ran the conversion, and returned the updated report. Nothing in the agent's instructions changed. The behaviour was adapted entirely from what was in the session dictionary.
Your final weather_agent/agent.py file should look exactly like this. If anything got out of order during the step-by-step additions, use this as the reference:
import requests
from google.adk.agents import Agent
from google.adk.tools.tool_context import ToolContext
MODEL = "gemini-flash-latest"
def get_weather(city: str, tool_context: ToolContext) -> dict:
"""Retrieves the current weather for a specified city.
Reads 'temperature_unit' from session state ('celsius' or 'fahrenheit').
Defaults to Celsius if the preference has not been set.
Writes 'last_city_checked' to session state after every successful lookup.
Uses Open-Meteo's free geocoding and weather APIs. No API key required.
Geocoding API: https://open-meteo.com/en/docs/geocoding-api
Forecast API: https://open-meteo.com/en/docs
Args:
city (str): The name of the city, e.g. "Lagos", "London", "Tokyo".
tool_context (ToolContext): Injected by ADK. Read/write access to session state.
Returns:
dict: Contains 'status' ('success' or 'error').
On success, includes a 'report' key with a human-readable summary.
On error, includes an 'error_message' key describing what went wrong.
"""
# Read the user's temperature preference from session state
preferred_unit = tool_context.state.get("temperature_unit", "celsius")
# Step 1: Convert the city name to latitude/longitude coordinates
geo_url = (
"https://geocoding-api.open-meteo.com/v1/search"
f"?name={city}&count=1&language=en&format=json"
)
try:
geo_response = requests.get(geo_url, timeout=10)
geo_data = geo_response.json()
except requests.RequestException as e:
return {"status": "error", "error_message": f"Network error during geocoding: {e}"}
if not geo_data.get("results"):
return {
"status": "error",
"error_message": f"Could not find a location matching '{city}'.",
}
location = geo_data["results"][0]
lat = location["latitude"]
lon = location["longitude"]
location_name = location["name"]
country = location.get("country", "")
# Step 2: Fetch current weather using those coordinates
weather_url = (
"https://api.open-meteo.com/v1/forecast"
f"?latitude={lat}&longitude={lon}"
"¤t=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m"
"&timezone=auto"
)
try:
weather_response = requests.get(weather_url, timeout=10)
weather_data = weather_response.json()
except requests.RequestException as e:
return {"status": "error", "error_message": f"Network error fetching weather: {e}"}
current = weather_data.get("current", {})
temp_c = current.get("temperature_2m")
humidity = current.get("relative_humidity_2m")
wind_speed = current.get("wind_speed_10m")
# Convert temperature based on the user's session preference
if preferred_unit.lower() == "fahrenheit":
temp_display = round((temp_c * 9 / 5) + 32, 1)
unit_symbol = "°F"
else:
temp_display = temp_c
unit_symbol = "°C"
weather_descriptions = {
0: "clear sky",
1: "mainly clear", 2: "partly cloudy", 3: "overcast",
45: "fog", 48: "rime fog",
51: "light drizzle", 53: "moderate drizzle", 55: "dense drizzle",
61: "light rain", 63: "moderate rain", 65: "heavy rain",
71: "light snow", 73: "moderate snow", 75: "heavy snow",
80: "light showers", 81: "moderate showers", 82: "heavy showers",
95: "thunderstorm",
}
condition = weather_descriptions.get(
current.get("weather_code", -1), "unknown conditions"
)
report = (
f"Current weather in {location_name}, {country}: {condition}. "
f"Temperature: {temp_display}{unit_symbol}, Humidity: {humidity}%, "
f"Wind: {wind_speed} km/h."
)
# Write the queried city to session state
tool_context.state["last_city_checked"] = city
return {"status": "success", "report": report}
def say_hello(name: str = None) -> str:
"""Greets the user by name if provided, otherwise gives a general greeting.
Args:
name (str, optional): The name of the person to greet.
Returns:
str: A friendly greeting.
"""
if name:
return f"Hello, {name}! How can I help you today?"
return "Hello! How can I help you today?"
def say_goodbye() -> str:
"""Provides a polite farewell message to close the conversation.
Returns:
str: A goodbye message.
"""
return "Goodbye! Have a great day."
greeting_agent = Agent(
model=MODEL,
name="greeting_agent",
description="Handles greetings and hellos using the 'say_hello' tool.",
instruction=(
"You are the Greeting Agent. Your only job is to greet the user. "
"Use the 'say_hello' tool. If the user shares their name, pass it to the tool. "
"Do not respond to any other type of request."
),
tools=[say_hello],
)
farewell_agent = Agent(
model=MODEL,
name="farewell_agent",
description="Handles farewells and goodbyes using the 'say_goodbye' tool.",
instruction=(
"You are the Farewell Agent. Your only job is to say goodbye. "
"Use the 'say_goodbye' tool when the user is ending the conversation. "
"Do not respond to any other type of request."
),
tools=[say_goodbye],
)
root_agent = Agent(
name="weather_agent",
model=MODEL,
description="Provides real-time weather information for any city in the world.",
instruction=(
"You are the main Weather Agent coordinating a small team. "
"For weather questions, use the 'get_weather' tool yourself. "
"For greetings like 'hi' or 'hello', delegate to 'greeting_agent'. "
"For farewells like 'bye' or 'goodbye', delegate to 'farewell_agent'. "
"Do not handle requests outside these three categories."
),
tools=[get_weather],
sub_agents=[greeting_agent, farewell_agent],
output_key="last_weather_report",
)
Where to Go Next?
Deploy to production: When you are ready to move off the local dev server, Agent Runtime on Google Cloud handles provisioning, scaling, and observability. The deployment guide is at adk.dev/deploy/agent-runtime/deploy.
Cross-platform agent communication: The A2A Protocol (Agent-to-Agent) lets your ADK agent hand off tasks to agents running on Salesforce, ServiceNow, or any other A2A-compliant platform. The protocol hit v1.0 at NEXT '26 and is in production at over 150 organizations. See adk.dev/a2a.
MCP tools: ADK natively supports the Model Context Protocol. If you already run MCP servers, your agents can consume them without any glue code. See adk.dev/tools-custom/mcp-tools.
Conclusion
ADK is production-ready for well-scoped agent systems. The package version at the time of writing is 1.31.1, which ships on top of the v1.0 stable API foundation announced at Google Cloud NEXT '26. The local development loop is fast: save a file, restart the ADK web, and test within seconds.
Building this tutorial was not without friction. The free tier quota on gemini-flash-latest ran out faster than expected mid-tutorial because the model string resolves to gemini-3-flash, which has a 20-request daily cap. Switching to gemini-2.0-flash helped, but the per-day limit still ran out during active development. If you are building anything beyond a quick proof of concept, enable billing early. The free monthly credit on a paid account is more than enough for tutorial-level work, and it removes a frustrating variable from your debugging process.
The NameError you will likely hit if you add callbacks is a Python ordering issue, not an ADK issue. Callbacks must be defined before the agent that references them.
The clearest case for the Gemini Enterprise Agent Platform is that it consolidates model access, agent orchestration, session management, and observability under one set of APIs. If your infrastructure is already on GCP, the path from a local prototype to a production deployment is shorter than any comparable alternative. If you are not on GCP, the open-source ADK still runs anywhere Python runs, and platform features like Memory Bank and Agent Runtime are additive. Start local and adopt cloud components when the need is real rather than anticipated.
Resources
ADK official documentation: https://adk.dev
ADK Python GitHub repository: https://github.com/google/adk-python
Open-Meteo free weather API: https://open-meteo.com/en/docs
Google Cloud NEXT '26 Developer Keynote: https://www.youtube.com/watch?v=A01DQ8_xy7Q
Gemini API rate limits: https://ai.google.dev/gemini-api/docs/rate-limits
https://cloud.google.com/blog/topics/google-cloud-next/welcome-to-google-cloud-next26








Top comments (0)