Build Your First Intelligent Agent Team: A Progressive Weather Bot with ADK¶
This tutorial extends from the Multi-tool agent project. Now, you're ready to dive deeper and construct a more sophisticated, multi-agent system.
We'll embark on building a Weather Bot agent team, progressively layering advanced features onto a simple foundation. Starting with a single agent that can look up weather, we will incrementally add capabilities like:
- Leveraging different AI models (Gemini, GPT, Claude).
- Designing specialized sub-agents for distinct tasks (like greetings and farewells).
- Enabling intelligent delegation between agents.
- Giving agents memory using persistent session state.
- Implementing crucial safety guardrails using callbacks.
Why a Weather Bot Team?
This use case, while seemingly simple, provides a practical and relatable canvas to explore core ADK concepts essential for building complex, real-world agentic applications. You'll learn how to structure interactions, manage state, ensure safety, and orchestrate multiple AI "brains" working together.
What is ADK Again?
As a reminder, ADK is a Python framework designed to streamline the development of applications powered by Large Language Models (LLMs). It offers robust building blocks for creating agents that can reason, plan, utilize tools, interact dynamically with users, and collaborate effectively within a team.
In this advanced tutorial, you will master:
- ✅ Tool Definition & Usage: Crafting Python functions (
tools) that grant agents specific abilities (like fetching data) and instructing agents on how to use them effectively. - ✅ Multi-LLM Flexibility: Configuring agents to utilize various leading LLMs (Gemini, GPT-4o, Claude Sonnet) via LiteLLM integration, allowing you to choose the best model for each task.
- ✅ Agent Delegation & Collaboration: Designing specialized sub-agents and enabling automatic routing (
auto flow) of user requests to the most appropriate agent within a team. - ✅ Session State for Memory: Utilizing
Session StateandToolContextto enable agents to remember information across conversational turns, leading to more contextual interactions. - ✅ Safety Guardrails with Callbacks: Implementing
before_model_callbackandbefore_tool_callbackto inspect, modify, or block requests/tool usage based on predefined rules, enhancing application safety and control.
End State Expectation:
By completing this tutorial, you will have built a functional multi-agent Weather Bot system. This system will not only provide weather information but also handle conversational niceties, remember the last city checked, and operate within defined safety boundaries, all orchestrated using ADK.
Prerequisites:
- ✅ Solid understanding of Python programming.
- ✅ Familiarity with Large Language Models (LLMs), APIs, and the concept of agents.
- ❗ Crucially: Completion of the ADK Quickstart tutorial(s) or equivalent foundational knowledge of ADK basics (Agent, Runner, SessionService, basic Tool usage). This tutorial builds directly upon those concepts.
- ✅ API Keys for the LLMs you intend to use (e.g., Google AI Studio for Gemini, OpenAI Platform, Anthropic Console).
Note on Execution Environment:
This tutorial is structured for interactive notebook environments like Google Colab, Colab Enterprise, or Jupyter notebooks. Please keep the following in mind:
- Running Async Code: Notebook environments handle asynchronous code differently. You'll see examples using
await(suitable when an event loop is already running, common in notebooks) orasyncio.run()(often needed when running as a standalone.pyscript or in specific notebook setups). The code blocks provide guidance for both scenarios. - Manual Runner/Session Setup: The steps involve explicitly creating
RunnerandSessionServiceinstances. This approach is shown because it gives you fine-grained control over the agent's execution lifecycle, session management, and state persistence.
Alternative: Using ADK's Built-in Tools (Web UI / CLI / API Server)
If you prefer a setup that handles the runner and session management automatically using ADK's standard tools, you can find the equivalent code structured for that purpose here. That version is designed to be run directly with commands like adk web (for a web UI), adk run (for CLI interaction), or adk api_server (to expose an API). Please follow the README.md instructions provided in that alternative resource.
Ready to build your agent team? Let's dive in!
Note: This tutorial works with adk version 1.0.0 and above
# @title Step 0: Setup and Installation
# Install ADK and LiteLLM for multi-model support
!pip install google-adk -q
!pip install "litellm>=1.84" -q
print("Installation complete.")
# @title Import necessary libraries
import os
import asyncio
from google.adk.agents import Agent
from google.adk.models.lite_llm import LiteLlm # For multi-model support
from google.adk.sessions import InMemorySessionService
from google.adk.runners import Runner
from google.genai import types # For creating message Content/Parts
import warnings
# Ignore all warnings
warnings.filterwarnings("ignore")
import logging
logging.basicConfig(level=logging.ERROR)
print("Libraries imported.")
# @title Configure API Keys (Replace with your actual keys!)
# --- IMPORTANT: Replace placeholders with your real API keys ---
# Gemini API Key (Get from Google AI Studio: https://aistudio.google.com/app/apikey)
os.environ["GOOGLE_API_KEY"] = "YOUR_GOOGLE_API_KEY" # <--- REPLACE
# [Optional]
# OpenAI API Key (Get from OpenAI Platform: https://platform.openai.com/api-keys)
os.environ['OPENAI_API_KEY'] = 'YOUR_OPENAI_API_KEY' # <--- REPLACE
# [Optional]
# Anthropic API Key (Get from Anthropic Console: https://console.anthropic.com/settings/keys)
os.environ['ANTHROPIC_API_KEY'] = 'YOUR_ANTHROPIC_API_KEY' # <--- REPLACE
# --- Verify Keys (Optional Check) ---
print("API Keys Set:")
print(f"Google API Key set: {'Yes' if os.environ.get('GOOGLE_API_KEY') and os.environ['GOOGLE_API_KEY'] != 'YOUR_GOOGLE_API_KEY' else 'No (REPLACE PLACEHOLDER!)'}")
print(f"OpenAI API Key set: {'Yes' if os.environ.get('OPENAI_API_KEY') and os.environ['OPENAI_API_KEY'] != 'YOUR_OPENAI_API_KEY' else 'No (REPLACE PLACEHOLDER!)'}")
print(f"Anthropic API Key set: {'Yes' if os.environ.get('ANTHROPIC_API_KEY') and os.environ['ANTHROPIC_API_KEY'] != 'YOUR_ANTHROPIC_API_KEY' else 'No (REPLACE PLACEHOLDER!)'}")
# Configure ADK to use API keys directly (not Agent Platform for this multi-model setup)
os.environ["GOOGLE_GENAI_USE_ENTERPRISE"] = "False"
# @markdown **Security Note:** It's best practice to manage API keys securely (e.g., using Colab Secrets or environment variables) rather than hardcoding them directly in the notebook. Replace the placeholder strings above.
# --- Define Model Constants for easier use ---
# More supported models can be referenced here: https://ai.google.dev/gemini-api/docs/models#model-variations
MODEL_GEMINI_FLASH = "gemini-flash-latest"
# More supported models can be referenced here: https://docs.litellm.ai/docs/providers/openai#openai-chat-completion-models
MODEL_GPT_4O = "openai/gpt-4.1" # You can also try: gpt-4.1-mini, gpt-4o etc.
# More supported models can be referenced here: https://docs.litellm.ai/docs/providers/anthropic
MODEL_CLAUDE_SONNET = "claude-sonnet-4-6" # You can also try: claude-opus-4-6, etc
print("\nEnvironment configured.")
Step 1: Your First Agent - Basic Weather Lookup¶
Let's begin by building the fundamental component of our Weather Bot: a single agent capable of performing a specific task – looking up weather information. This involves creating two core pieces:
- A Tool: A Python function that equips the agent with the ability to fetch weather data.
- An Agent: The AI "brain" that understands the user's request, knows it has a weather tool, and decides when and how to use it.
1. Define the Tool (get_weather)
In ADK, Tools are the building blocks that give agents concrete capabilities beyond just text generation. They are typically regular Python functions that perform specific actions, like calling an API, querying a database, or performing calculations.
Our first tool will provide a mock weather report. This allows us to focus on the agent structure without needing external API keys yet. Later, you could easily swap this mock function with one that calls a real weather service.
Key Concept: Docstrings are Crucial! The agent's LLM relies heavily on the function's docstring to understand:
- What the tool does.
- When to use it.
- What arguments it requires (
city: str). - What information it returns.
Best Practice: Write clear, descriptive, and accurate docstrings for your tools. This is essential for the LLM to use the tool correctly.
# @title Define the get_weather Tool
def get_weather(city: str) -> dict:
"""Retrieves the current weather report for a specified city.
Args:
city (str): The name of the city (e.g., "New York", "London", "Tokyo").
Returns:
dict: A dictionary containing the weather information.
Includes a 'status' key ('success' or 'error').
If 'success', includes a 'report' key with weather details.
If 'error', includes an 'error_message' key.
"""
print(f"--- Tool: get_weather called for city: {city} ---") # Log tool execution
city_normalized = city.lower().replace(" ", "") # Basic normalization
# Mock weather data
mock_weather_db = {
"newyork": {"status": "success", "report": "The weather in New York is sunny with a temperature of 25°C."},
"london": {"status": "success", "report": "It's cloudy in London with a temperature of 15°C."},
"tokyo": {"status": "success", "report": "Tokyo is experiencing light rain and a temperature of 18°C."},
}
if city_normalized in mock_weather_db:
return mock_weather_db[city_normalized]
else:
return {"status": "error", "error_message": f"Sorry, I don't have weather information for '{city}'."}
# Example tool usage (optional test)
print(get_weather("New York"))
print(get_weather("Paris"))
2. Define the Agent (weather_agent)
Now, let's create the Agent itself. An Agent in ADK orchestrates the interaction between the user, the LLM, and the available tools.
We configure it with several key parameters:
name: A unique identifier for this agent (e.g., "weather_agent_v1").model: Specifies which LLM to use (e.g.,MODEL_GEMINI_FLASH). We'll start with a specific Gemini model.description: A concise summary of the agent's overall purpose. This becomes crucial later when other agents need to decide whether to delegate tasks to this agent.instruction: Detailed guidance for the LLM on how to behave, its persona, its goals, and specifically how and when to utilize its assignedtools.tools: A list containing the actual Python tool functions the agent is allowed to use (e.g.,[get_weather]).
Best Practice: Provide clear and specific instruction prompts. The more detailed the instructions, the better the LLM can understand its role and how to use its tools effectively. Be explicit about error handling if needed.
Best Practice: Choose descriptive name and description values. These are used internally by ADK and are vital for features like automatic delegation (covered later).
# @title Define the Weather Agent
# Use one of the model constants defined earlier
AGENT_MODEL = MODEL_GEMINI_FLASH # Starting with Gemini
weather_agent = Agent(
name="weather_agent_v1",
model=AGENT_MODEL, # Can be a string for Gemini or a LiteLlm object
description="Provides weather information for specific cities.",
instruction="You are a helpful weather assistant. "
"When the user asks for the weather in a specific city, "
"use the 'get_weather' tool to find the information. "
"If the tool returns an error, inform the user politely. "
"If the tool is successful, present the weather report clearly.",
tools=[get_weather], # Pass the function directly
)
print(f"Agent '{weather_agent.name}' created using model '{AGENT_MODEL}'.")
3. Setup Runner and Session Service
To manage conversations and execute the agent, we need two more components:
SessionService: Responsible for managing conversation history and state for different users and sessions. TheInMemorySessionServiceis a simple implementation that stores everything in memory, suitable for testing and simple applications. It keeps track of the messages exchanged. We'll explore state persistence more in Step 4.Runner: The engine that orchestrates the interaction flow. It takes user input, routes it to the appropriate agent, manages calls to the LLM and tools based on the agent's logic, handles session updates via theSessionService, and yields events representing the progress of the interaction.
# @title Setup Session Service and Runner
# --- Session Management ---
# Key Concept: SessionService stores conversation history & state.
# InMemorySessionService is simple, non-persistent storage for this tutorial.
session_service = InMemorySessionService()
# Define constants for identifying the interaction context
APP_NAME = "weather_tutorial_app"
USER_ID = "user_1"
SESSION_ID = "session_001" # Using a fixed ID for simplicity
# Create the specific session where the conversation will happen
session = await session_service.create_session(
app_name=APP_NAME,
user_id=USER_ID,
session_id=SESSION_ID
)
print(f"Session created: App='{APP_NAME}', User='{USER_ID}', Session='{SESSION_ID}'")
# --- OR ---
# Uncomment the following lines if running as a standard Python script (.py file):
# from google.adk.sessions import Session
#
# async def init_session(app_name:str,user_id:str,session_id:str) -> Session:
# session = await session_service.create_session(
# app_name=app_name,
# user_id=user_id,
# session_id=session_id
# )
# print(f"Session created: App='{app_name}', User='{user_id}', Session='{session_id}'")
# return session
#
# session = asyncio.run(init_session(APP_NAME,USER_ID,SESSION_ID))
# --- Runner ---
# Key Concept: Runner orchestrates the agent execution loop.
runner = Runner(
agent=weather_agent, # The agent we want to run
app_name=APP_NAME, # Associates runs with our app
session_service=session_service # Uses our session manager
)
print(f"Runner created for agent '{runner.agent.name}'.")
4. Interact with the Agent
We need a way to send messages to our agent and receive its responses. Since LLM calls and tool executions can take time, ADK's Runner operates asynchronously.
We'll define an async helper function (call_agent_async) that:
- Takes a user query string.
- Packages it into the ADK
Contentformat. - Calls
runner.run_async, providing the user/session context and the new message. - Iterates through the Events yielded by the runner. Events represent steps in the agent's execution (e.g., tool call requested, tool result received, intermediate LLM thought, final response).
- Identifies and prints the final response event using
event.is_final_response().
Why async? Interactions with LLMs and potentially tools (like external APIs) are I/O-bound operations. Using asyncio allows the program to handle these operations efficiently without blocking execution.