
Google ADK Tutorial: Build AI Agents From Zero to Production
Google's Agent Development Kit (ADK) is the framework that finally makes multi-agent systems feel approachable. If you've been building AI agents with LangChain or CrewAI and felt like you were fighting the framework more than building with it, this Google ADK tutorial walks you through everything, from your first agent to deploying it on Cloud Run.
What Is Google ADK (And Why Should You Care)?
Google's Agent Development Kit (ADK) is an open-source Python framework for building, evaluating, and deploying AI agents. Released in 2025, it's optimized for Gemini but supports 100+ models via LiteLLM. ADK's killer feature is native multi-agent orchestration, agents that delegate tasks to other agents without glue code.
After building agents with LangChain, CrewAI, and now ADK, here's what stands out: ADK is opinionated in the right places. It gives you a project structure, a built-in dev UI, and a deployment command. You're not stitching together five libraries to get a basic agent running.
If LangChain is a general-purpose Swiss army knife, ADK is Google's purpose-built toolkit for multi-agent workflows. CrewAI is closer in philosophy, role-based agents that collaborate, but ADK goes further with built-in evaluation, native Gemini optimization, and one-command Cloud Run deployment. For a deeper breakdown, check out our deep-dive comparison of agent frameworks.
Who is ADK for? Python developers who want structured multi-agent systems. Teams already on Google Cloud or Gemini. Anyone tired of writing boilerplate orchestration logic.
Here's how the frameworks stack up at a glance:
| Feature | Google ADK | LangGraph | CrewAI |
|---|---|---|---|
| Multi-agent native | Yes | Via graph | Yes |
| Model support | Gemini + 100+ via LiteLLM | Any | Any |
| Built-in UI | Yes (adk web) | LangSmith | No |
| Deployment | Cloud Run, Vertex AI | Custom | Custom |
| Learning curve | Low-Medium | High | Low |
| Open source | Yes (Apache 2.0) | Yes | Yes |
The short version: if you want the fastest path from "idea" to "deployed multi-agent system," ADK is hard to beat right now.
Prerequisites and Google ADK Installation
To start with Google ADK, you need Python 3.9+, a Gemini API key (free tier available at Google AI Studio), and the google-adk package. Install with pip install google-adk, set your API key as an environment variable, and you're ready to build your first agent in under 5 minutes.
Here's your setup checklist:
- Python 3.9+ (3.10+ recommended for full type hint support)
- A Gemini API key, grab one free at aistudio.google.com. The free tier gives you 15 requests per minute, which is plenty for development.
- pip (or
uvif you prefer speed,uv pip install google-adkworks too)
Install the package and set your key:
pip install google-adk
# Set your API key (add to .bashrc/.zshrc for persistence)
export GOOGLE_API_KEY="your-api-key-here"ADK expects a specific folder structure. Each agent lives in its own package directory:
my_agent/
__init__.py # Exports root_agent
agent.py # Agent definition
.env # Optional: GOOGLE_API_KEY=your-keyThe folder name becomes your agent's package name, so pick something descriptive. Don't call it test or agent, you'll confuse Python's import system.
Pro tip: If you're using uv, create a virtual environment first with uv venv && source .venv/bin/activate. It's noticeably faster than regular pip for dependency resolution.
Building Your First Google ADK Agent
Your first ADK agent needs just three things: a name, a model (like gemini-2.0-flash), and an instruction string. Define it in agent.py, place it inside a folder with an __init__.py, and run adk web to chat with it in a browser UI. The entire setup takes about 10 lines of Python.
Create a folder called my_agent and add two files. First, the agent definition:
# my_agent/agent.py
from google.adk.agents import LlmAgent
root_agent = LlmAgent(
name="my_assistant",
model="gemini-2.0-flash",
instruction="""You are a helpful coding assistant.
You explain concepts clearly and provide working code examples.
Keep responses concise but thorough.""",
description="A coding assistant that explains concepts and writes code"
)Then the init file that exports your agent:
# my_agent/__init__.py
from .agent import root_agentThat variable name matters, ADK looks for root_agent specifically. Miss this and you'll get an "agent not found" error that doesn't explain why.
Now run it. You have two options:
# CLI mode -- chat in your terminal
adk run my_agent
# Web UI mode -- opens a browser interface
adk web my_agentThe adk web interface is genuinely useful. It shows you the full conversation trace, which tools the agent called, what the model received, and what it returned. Think of it as Chrome DevTools for your agent. When you start building multi-agent systems later, this becomes essential for understanding delegation flow.
Try modifying the instruction to see how behavior changes. Make it a pirate. Make it only respond in haiku. Getting a feel for how instructions shape behavior is the foundation for everything else in this tutorial.
Adding Custom Tools to Your Google ADK Agent
ADK agents become useful when you give them tools. Define a Python function with a clear docstring, and ADK automatically converts it into a tool the agent can call. The docstring is critical, it tells the model what the tool does and when to use it. ADK also ships with built-in tools like Google Search and code execution.
Tools are an agent's hands. Without them, your agent can only talk. With them, it can check databases, call APIs, run calculations, and interact with external systems. If you want to understand how function calling works under the hood, we've got a separate deep-dive on that.
Custom Function Tools
Here's a practical example, a tool that looks up stock prices:
# my_agent/agent.py
from google.adk.agents import LlmAgent
def get_stock_price(ticker: str) -> dict:
"""Get the current stock price for a given ticker symbol.
Args:
ticker: The stock ticker symbol (e.g., 'AAPL', 'GOOGL', 'MSFT')
Returns:
A dictionary with the ticker and its current price.
"""
# In production, you'd call a real API here
mock_prices = {"AAPL": 198.50, "GOOGL": 175.20, "MSFT": 425.80}
price = mock_prices.get(ticker.upper(), None)
if price:
return {"ticker": ticker.upper(), "price": price, "currency": "USD"}
return {"error": f"Ticker {ticker} not found"}
root_agent = LlmAgent(
name="finance_assistant",
model="gemini-2.0-flash",
instruction="You help users check stock prices. Use the get_stock_price tool when asked about any stock.",
tools=[get_stock_price],
description="A financial assistant that looks up stock prices"
)Notice the type hints and docstring. These aren't optional niceties, ADK uses them to generate the tool schema that the model sees. Skip the docstring and the model won't know when to call your function. Skip the type hints and you'll get a signature error.
Built-In Tools (Google Search, Code Execution)
ADK ships with tools you can drop in without writing any code:
from google.adk.agents import LlmAgent
from google.adk.tools import google_search, code_execution
root_agent = LlmAgent(
name="research_agent",
model="gemini-2.0-flash",
instruction="You research topics using Google Search and can run Python code to analyze data.",
tools=[google_search, code_execution],
description="A research agent with search and code execution capabilities"
)google_search lets the agent query the web in real time. code_execution gives it a sandboxed Python environment to run calculations. These two alone cover a surprising number of use cases.
Multi-Agent Systems: How Google ADK Agents Delegate Work
ADK's multi-agent system uses a root agent that delegates tasks to specialized sub-agents. Each sub-agent handles one domain, research, writing, coding. The root agent decides which sub-agent to call based on the user's request. You can also use the agent-as-tool pattern, where one agent calls another as if it were a function. Google's official blog on multi-agent systems goes deeper into the architectural patterns.
Think of it like a project manager delegating to specialists. The root agent reads the user's request, figures out which specialist should handle it, and routes accordingly. The specialists don't know about each other, they just do their job and report back.
Root Agent + Sub-Agents Pattern
Here's a working example with a root agent that delegates to a research agent and a writing agent:
from google.adk.agents import LlmAgent
from google.adk.tools import google_search
# Sub-agent 1: handles research
research_agent = LlmAgent(
name="researcher",
model="gemini-2.0-flash",
instruction="You research topics thoroughly using Google Search. Return factual, well-sourced information.",
tools=[google_search],
description="Researches topics and returns factual information"
)
# Sub-agent 2: handles writing
writing_agent = LlmAgent(
name="writer",
model="gemini-2.0-flash",
instruction="You write clear, engaging content based on provided information. Focus on readability and accuracy.",
description="Writes polished content from research notes"
)
# Root agent: delegates to the right sub-agent
root_agent = LlmAgent(
name="content_manager",
model="gemini-2.0-flash",
instruction="""You manage content creation.
- When the user wants information gathered, delegate to the researcher.
- When the user wants content written or edited, delegate to the writer.
- You can chain both: research first, then write.""",
sub_agents=[research_agent, writing_agent],
description="Manages content creation by delegating to research and writing specialists"
)The description field on each sub-agent is how the root agent understands what they can do. Write clear descriptions, vague ones lead to bad routing decisions.
Agent-as-Tool Pattern
Sometimes you want more control over how one agent calls another. The agent-as-tool pattern wraps a sub-agent as a callable tool:
from google.adk.tools import agent_tool
research_tool = agent_tool.AgentTool(agent=research_agent)
root_agent = LlmAgent(
name="writer_with_research",
model="gemini-2.0-flash",
instruction="You write articles. Use the research tool to gather facts before writing.",
tools=[research_tool],
description="A writer that can research topics on demand"
)Use sub-agents when you want the root agent to fully delegate control. Use agent-as-tool when you want the calling agent to stay in the driver's seat and just use the sub-agent's output as input. If you're building systems where agents need shared context, see our comprehensive guide to agent memory architectures.
Workflow Agents: Sequential, Parallel, and Loop
Beyond LLM-driven delegation, ADK offers three workflow agent types for deterministic orchestration: SequentialAgent runs sub-agents one after another, ParallelAgent runs them simultaneously, and LoopAgent repeats a sequence until a condition is met. These are useful when you need predictable execution order rather than letting the LLM decide.
The distinction matters. LLM-driven delegation (the sub_agents pattern above) lets the model choose who to call. Workflow agents give you programmatic control. Use workflow agents when the execution order is known upfront.
from google.adk.agents import SequentialAgent, ParallelAgent, LlmAgent
# Three agents that must run in order
research_agent = LlmAgent(name="researcher", model="gemini-2.0-flash",
instruction="Research the given topic.", description="Researches topics")
draft_agent = LlmAgent(name="drafter", model="gemini-2.0-flash",
instruction="Write a draft based on the research.", description="Writes drafts")
review_agent = LlmAgent(name="reviewer", model="gemini-2.0-flash",
instruction="Review the draft for accuracy and clarity.", description="Reviews content")
# Pipeline: research -> draft -> review
content_pipeline = SequentialAgent(
name="content_pipeline",
sub_agents=[research_agent, draft_agent, review_agent],
description="Runs a complete content creation pipeline"
)For independent tasks that can run at the same time, ParallelAgent saves real time:
# Three data fetchers that run concurrently
fetch_news = LlmAgent(name="news_fetcher", model="gemini-2.0-flash",
instruction="Fetch latest tech news.", description="Fetches news")
fetch_stocks = LlmAgent(name="stock_fetcher", model="gemini-2.0-flash",
instruction="Fetch stock market summary.", description="Fetches stocks")
fetch_weather = LlmAgent(name="weather_fetcher", model="gemini-2.0-flash",
instruction="Fetch weather forecast.", description="Fetches weather")
morning_briefing = ParallelAgent(
name="morning_briefing",
sub_agents=[fetch_news, fetch_stocks, fetch_weather],
description="Gathers morning briefing data in parallel"
)| Pattern | Agent Type | Use Case | Example |
|---|---|---|---|
| Pipeline | SequentialAgent | Steps must happen in order | Research -> Write -> Review |
| Fan-out | ParallelAgent | Independent tasks | Fetch data from 3 APIs simultaneously |
| Iteration | LoopAgent | Repeat until quality met | Draft -> Review -> Revise (loop) |
Managing State and Memory
ADK manages agent state at two levels: session state (data within a conversation, like user preferences collected mid-chat) and memory services (data that persists across conversations). Session state is a simple key-value store accessed via context.state. Memory uses services like InMemoryMemoryService or VertexAIMemoryBankService for production.
Session state is the simpler one. It's a dictionary attached to each conversation:
from google.adk.agents import LlmAgent
def save_preference(key: str, value: str, context) -> str:
"""Save a user preference to session state.
Args:
key: The preference name (e.g., 'language', 'theme')
value: The preference value
context: The ADK context object
Returns:
Confirmation message
"""
context.state[key] = value
return f"Saved preference: {key} = {value}"
def get_preference(key: str, context) -> str:
"""Retrieve a user preference from session state.
Args:
key: The preference name to look up
context: The ADK context object
Returns:
The preference value or a not-found message
"""
value = context.state.get(key, "Not set")
return f"{key} = {value}"
root_agent = LlmAgent(
name="personalized_assistant",
model="gemini-2.0-flash",
instruction="You remember user preferences. Save them when told, recall them when asked.",
tools=[save_preference, get_preference],
description="An assistant that remembers user preferences"
)For cross-conversation memory, the kind where your agent remembers a user from last Tuesday, you need a memory service:
from google.adk.memory import InMemoryMemoryService
# For development (data lost on restart)
memory_service = InMemoryMemoryService()
# For production, use VertexAIMemoryBankService
# memory_service = VertexAIMemoryBankService(project="your-project")When do you need memory vs session state? If it's within a single conversation (shopping cart, current task context), use session state. If it needs to survive between conversations (user preferences, past interactions), use a memory service. Check out our comprehensive guide to agent memory architectures for production patterns.
Callbacks: Controlling Agent Behavior
ADK callbacks let you intercept and modify agent behavior at four points: before_model_callback (before LLM call), after_model_callback (after LLM response), before_tool_callback (before tool execution), and after_tool_callback (after tool result). Use them for input validation, safety filtering, logging, or modifying responses before they reach the user.
Callbacks are where you add guardrails. Think of them as middleware for your agent, every request and response passes through them, and you can inspect, modify, or block anything.
from google.adk.agents import LlmAgent
def safety_filter(callback_context, llm_request):
"""Block requests containing harmful content patterns."""
user_message = str(llm_request)
blocked_patterns = ["ignore your instructions", "pretend you are"]
for pattern in blocked_patterns:
if pattern.lower() in user_message.lower():
# Return a response directly, skipping the model call
return {"blocked": True, "reason": "Request matched safety filter"}
# Return None to proceed normally
return None
def log_tool_usage(callback_context, tool_name, tool_result):
"""Log every tool call for monitoring."""
print(f"[TOOL LOG] {tool_name}: {tool_result}")
return None # Don't modify the result
root_agent = LlmAgent(
name="safe_assistant",
model="gemini-2.0-flash",
instruction="You are a helpful assistant.",
before_model_callback=safety_filter,
after_tool_callback=log_tool_usage,
description="A safety-filtered assistant with tool logging"
)The before_model_callback is the most important for production. It runs before every LLM call, giving you a chance to block prompt injections, validate inputs, or add system context. If you return a response object, ADK skips the model entirely. Return None to let the request through. For more patterns, see deeper patterns for LLM safety guardrails.
Testing and Evaluating Your ADK Agents
ADK includes a built-in evaluation framework with two evaluator types: ResponseEvaluator checks if the agent's final answer is correct, and TrajectoryEvaluator verifies the agent took the right steps, called the right tools in the right order. Write test cases as JSON files and run them with pytest to catch regressions before deployment.
Why bother testing agents? Because they're non-deterministic. The same input can produce different outputs, and a small change to your instruction can break tool calling in subtle ways. In our experience, agents that pass trajectory evaluation are far more reliable in production than those only tested on final output quality. For broader evaluation strategies, see our guide to LLM evaluation strategies.
Your test cases go in a JSON file:
[
{
"input": "What's the stock price of AAPL?",
"expected_output": "198.50",
"expected_trajectory": [
{"tool_name": "get_stock_price", "args": {"ticker": "AAPL"}}
]
},
{
"input": "Compare AAPL and GOOGL prices",
"expected_output": "AAPL.*198.*GOOGL.*175",
"expected_trajectory": [
{"tool_name": "get_stock_price", "args": {"ticker": "AAPL"}},
{"tool_name": "get_stock_price", "args": {"ticker": "GOOGL"}}
]
}
]Then run evaluations with pytest. The ADK Python repository has the full evaluation API reference:
# test_agent.py
import pytest
from google.adk.evaluation import ResponseEvaluator, TrajectoryEvaluator
def test_stock_agent_response():
evaluator = ResponseEvaluator(agent=root_agent)
results = evaluator.evaluate("test_cases.json")
assert results.pass_rate >= 0.8, f"Response pass rate too low: {results.pass_rate}"
def test_stock_agent_trajectory():
evaluator = TrajectoryEvaluator(agent=root_agent)
results = evaluator.evaluate("test_cases.json")
assert results.pass_rate >= 0.9, f"Trajectory pass rate too low: {results.pass_rate}"Run with pytest test_agent.py -v. Set your thresholds based on criticality -- 80% response accuracy might be fine for a creative writing agent, but you'd want 95%+ for anything handling financial data.
Deploying Your Google ADK Agent to Production
Deploy your ADK agent to Google Cloud Run with one command: adk deploy cloud_run --project YOUR_PROJECT --region us-central1. ADK packages your code, builds a container, and launches a serverless endpoint. For managed hosting, use Vertex AI Agent Engine. For custom infrastructure, ADK also supports Docker containerization.
We've deployed ADK agents on Cloud Run for internal tools, and the cold start times are surprisingly fast, under 3 seconds for a basic agent. For production systems, consider pairing your deployment with monitoring tools for production agents.
Deploy to Cloud Run (Recommended for Most)
Cloud Run is the simplest path. One command, and your agent is live with an HTTPS endpoint:
adk deploy cloud_run \
--project your-gcp-project-id \
--region us-central1 \
--service-name my-agent-service \
--with_uiThe --with_ui flag deploys the ADK Web interface alongside your agent, so you get a browser-based chat for testing in production. Behind the scenes, ADK builds a container image, pushes it to Google Artifact Registry, and creates a Cloud Run service. The full deployment flow is documented in Google's Cloud Run quickstart for ADK.
For custom infrastructure, here's a minimal Dockerfile:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["adk", "api_server", "--port", "8080", "my_agent"]Alternative: Vertex AI Agent Engine
For enterprise teams that need managed scaling, monitoring, and versioning, Vertex AI Agent Engine handles infrastructure entirely. You trade flexibility for convenience, no containers to manage, automatic scaling, built-in analytics.
Cost Considerations
Real numbers you should know:
- Gemini API free tier: 15 requests per minute, 1 million tokens/day. Enough for development and light demos.
- Gemini 2.0 Flash (paid): $0.10 per million input tokens, $0.40 per million output tokens. Cheap enough for production.
- Cloud Run free tier: 2 million requests/month, 360,000 GB-seconds of compute. A basic agent handling 1,000 requests/day stays well within free tier.
- Optimization tip: Use
gemini-2.0-flash(notgemini-2.0-pro) for sub-agents that do simple routing or formatting. Reserve the more capable models for agents doing complex reasoning.
How Techsy Approaches AI Agent Development
At Techsy, we've built multi-agent systems for clients using ADK, LangGraph, and CrewAI. The framework choice depends on your stack: if you're already on Google Cloud, ADK eliminates a lot of integration friction. If you need multi-provider LLM support from day one, LangGraph gives you more flexibility.
Our typical engagement starts with architecture consulting, mapping your use case to the right agent patterns, followed by prototype development and Cloud Run deployment. We've found that teams save 2-3 weeks by getting the architecture right upfront instead of refactoring later.
Building AI agents for your team? Get a free consultation, we'll help you choose the right framework and deployment strategy.
Common Errors and Troubleshooting
These are the errors we hit most often when getting started with ADK. Save yourself the debugging time:
| Error | Cause | Fix |
|---|---|---|
GOOGLE_API_KEY not set | Missing environment variable | export GOOGLE_API_KEY="your-key" or add to .env |
Model not found | Wrong model name string | Use exact IDs: gemini-2.0-flash, not gemini-flash |
Tool function signature error | Missing type hints or docstring | Add type hints to all params, add a descriptive docstring |
Agent not found | Wrong folder structure or missing export | Ensure __init__.py exports root_agent by that exact name |
Rate limit exceeded (429) | Too many API calls on free tier | Upgrade to paid Gemini tier or add exponential backoff |
ImportError: google-adk | Package not installed | Run pip install google-adk in your active virtual environment |
Debugging tip: adk web is your best friend here. It shows the full conversation trace, every model call, tool invocation, and agent delegation, in real time. When something goes wrong in a multi-agent system, the web UI shows you exactly where the chain broke.
FAQ
What is Google ADK?
Google's Agent Development Kit (ADK) is an open-source Python framework for building, evaluating, and deploying AI agents. It's optimized for Google Gemini models but supports 100+ LLMs through LiteLLM integration. ADK's core strength is native multi-agent orchestration with built-in tools, a dev UI, and one-command Cloud Run deployment.
Is Google ADK free to use?
Yes. ADK itself is open-source under the Apache 2.0 license. You need a Gemini API key, which has a free tier offering 15 requests per minute and 1 million tokens per day. Cloud deployment costs depend on your hosting choice, Cloud Run's free tier covers 2 million requests per month.
What is the difference between Google ADK and LangChain?
ADK is Google's opinionated framework optimized for Gemini with native multi-agent orchestration and built-in deployment tools. LangChain is model-agnostic with broader third-party integrations but significantly more complexity. ADK is better for Gemini-first teams wanting fast deployment; LangChain suits multi-provider setups needing maximum flexibility.
Does Google ADK support multi-agent systems?
Yes, and it's ADK's flagship feature. You create a root agent that delegates to specialized sub-agents based on user requests. ADK also offers SequentialAgent, ParallelAgent, and LoopAgent for deterministic workflow orchestration. The agent-as-tool pattern lets agents call other agents as callable functions.
How do I deploy a Google ADK agent?
Run adk deploy cloud_run --project YOUR_PROJECT --region us-central1 for serverless deployment to Google Cloud Run. Add --with_ui to include the browser-based chat interface. You can also deploy to Vertex AI Agent Engine for managed hosting, or build a Docker container for custom infrastructure.
Can Google ADK use models other than Gemini?
Yes. ADK supports 100+ models through LiteLLM integration, including Anthropic Claude, OpenAI GPT-4, Meta Llama, and Mistral. Set the model parameter to the LiteLLM model string, for example, litellm/anthropic/claude-3-sonnet or litellm/openai/gpt-4o. Gemini models work natively without the LiteLLM prefix.
What is the ADK Web UI?
A browser-based debugging interface launched with adk web your_agent_folder. It displays real-time conversation traces, tool calls, agent delegation chains, and state changes as they happen. The Web UI is essential for debugging multi-agent systems because it shows exactly which sub-agent handled each request.
Does Google ADK support MCP (Model Context Protocol)?
Yes. ADK has native Model Context Protocol support, allowing agents to connect to any MCP-compatible tool server for external tools and data sources. This makes ADK agents interoperable with the growing MCP ecosystem. For background on the protocol, see our MCP guide.
How do I test ADK agents?
ADK ships with built-in evaluators: ResponseEvaluator for checking output quality against expected answers, and TrajectoryEvaluator for verifying the agent called the right tools in the right order. Write test cases as JSON files defining inputs, expected outputs, and expected tool call sequences, then run them with pytest.
What Python version does Google ADK require?
ADK requires Python 3.9 or higher. Python 3.10+ is recommended for full type hinting support, which matters because ADK uses type hints to generate tool schemas. Python 3.11 or 3.12 also offer meaningful performance improvements for agent workloads. Install with pip install google-adk.