The world of AI is rapidly evolving, moving beyond simple prompts to sophisticated ‘agents’ that can reason, plan, and execute multi-step tasks. Imagine an AI that doesn’t just answer questions but actively helps you write code, debug, refactor, and even translate it. With Google’s Agent Development Kit (ADK) for Python, you can turn this vision into reality.
This guide will walk you through creating your own powerful AI agent using ADK, leveraging the modularity and extensibility it offers. We’ll explore how to build specialized ‘sub-agents’ for different tasks and orchestrate them with a central ‘coordinator agent’ to tackle complex challenges.
Introduction: Unlock Agentic AI with Google’s ADK
AI agents are more than just chatbots; they are autonomous programs designed to achieve specific goals. They can perceive their environment, reason about their actions, plan a sequence of steps, and execute those steps, often interacting with various tools. This agentic behavior is a game-changer for automating complex processes and building intelligent applications.
Google’s ADK provides the foundational building blocks to develop these agents in Python. It abstracts away much of the complexity, allowing you to focus on defining your agent’s capabilities and instructions. By the end of this tutorial, you’ll have a clear understanding of ADK’s core components and how to assemble them into a functional, multi-talented AI assistant.
Prerequisites and Setup
Before we dive into agent creation, let’s get your development environment ready.
Python 3.9+: Ensure you have a recent version of Python installed.
Google Cloud Project: You’ll need a Google Cloud Project with the Gemini API enabled. If you don’t have one, follow Google Cloud’s documentation to set it up.
Obtain Your GOOGLE_API_KEY: Generate an API key from your Google Cloud console (APIs & Services > Credentials).
Install Dependencies: Open your terminal or command prompt and run:
pip install google-generativeai python-dotenv
Configure .env: Create a file named .env in your project’s root directory and add your API key:GOOGLE_API_KEY="your_api_key_here"
This keeps your API key secure and separate from your code. Your Python script will load this key using dotenv.
Understanding ADK Core Concepts
ADK is built around a few fundamental abstractions:
LlmAgent: This is the core of any AI agent. It encapsulates an LLM (like Gemini), specific instructions, and a list of tools it can use. The instructions define the agent’s persona and how it should respond.
from google.adk.agents import LlmAgent
from google.adk.models.google_llm import Gemini
from google.genai import types
from google.adk.tools import google_search
# -------------------------------------------------------------------
# Retry options for robust API calls
# -------------------------------------------------------------------
retry_config = types.HttpRetryOptions(
attempts=5,
exp_base=7,
initial_delay=1,
http_status_codes=[429, 500, 503, 504]
)
# -------------------------------------------------------------------
# Translator Agent
# -------------------------------------------------------------------
translator_agent = LlmAgent(
name="translator_agent",
model=Gemini(
model="gemini-2.5-flash-lite",
retry_options=retry_config
),
instruction="You are a universal code translator...",
tools=[google_search] # Agents can use tools like Google Search
)
FunctionTool: This allows your agent to interact with the outside world or execute custom Python logic. You can wrap any async Python function as a FunctionTool.
from typing import Dict, Any
from google.adk.tools.function_tool import FunctionTool
# ------------------------------------------------------------
# Custom Function Tool
# ------------------------------------------------------------
async def my_custom_function(payload: Dict[str, Any]):
# ... custom logic ...
return {"result": "success"}
my_tool = FunctionTool(
func=my_custom_function
)
Runner & SessionService: The Runner executes your App (which contains your agents), and the SessionService manages the state of ongoing conversations or tasks, allowing agents to remember context across turns.
Step-by-Step: Building Specialized Sub-Agents
Modularity is key in ADK. Instead of one monolithic agent, you create smaller, specialized LlmAgents, each with a distinct purpose. Here are examples from the provided code:
Translator Agent: Translates code between programming languages.
# ------------------------------------------------------------
# LlmAgent Instruction (Formatted)
# ------------------------------------------------------------
instruction = """
You are a universal code translator and polyglot programmer.
You can understand and translate between ANY two programming or
markup languages.
Your responsibilities:
- Detect the source and target languages automatically.
- Preserve logic, structure, comments, and naming conventions.
- Produce clean, optimized, idiomatic code in the target language.
- Avoid unnecessary explanations unless explicitly asked.
- Return ONLY the translated code unless the instructions require more.
Be precise, consistent, and reliable across all languages.
"""
Reviewer & Refactor Agent: Identifies issues and suggests refactorings.
reviewer_agent = LlmAgent(
name="reviewer_agent",
model=Gemini(
model="gemini-2.5-flash-lite",
retry_options=retry_config
),
instruction="""
You are a code reviewer and refactorer.
Your responsibilities:
- Analyze code for bugs, design flaws, performance issues, and bad practices.
- Suggest improvements while preserving original functionality.
- Provide clean, optimized, production-ready refactored code.
- Explain issues clearly and concisely unless asked for code-only output.
""",
tools=[google_search] # Optional: allow agent to use Google Search
)
Code Writer Agent: Generates code and unit tests based on specifications.
Explainer Agent: Provides line-by-line code explanations.
Doc Writer Agent: Creates README.md and API documentation.
Visualizer Agent: Generates code diagrams (e.g., Mermaid call graphs).
Each sub-agent is designed with specific instructions to guide its behavior and often expects a structured output, like JSON, for easy parsing by other parts of your system.
Integrating Custom Logic with Function Tools
To make these sub-agents callable and their outputs manageable, we create wrapper functions that act as FunctionTools for a higher-level agent (the coordinator). These wrappers often call run_subagent (a helper function to execute a sub-agent) and then process its raw output.
For example, the cmd_translate function takes a payload, calls the translator_agent, and then post-processes the raw text to ensure only the translated code is returned, free of markdown or explanations.
from typing import Dict, Any
from google.adk.runners import InMemoryRunner
from google.adk.tools.function_tool import FunctionTool
# ------------------------------------------------------------
# Helper: Run a sub-agent and collect output events
# ------------------------------------------------------------
async def run_subagent(agent: LlmAgent, prompt: str, data: Dict[str, Any] = None):
runner = InMemoryRunner(agent=agent)
response = await runner.run_debug(prompt, verbose=False)
return response
# ------------------------------------------------------------
# Translate Command
# ------------------------------------------------------------
async def cmd_translate(payload: Dict[str, Any]):
# Extract details
source = payload.get("source", "")
target = payload.get("target", "")
code = payload.get("code", "")
prompt = f"""
You are a strict code translator.
Follow all rules exactly:
- Translate ONLY the code.
- DO NOT add explanations unless asked.
- DO NOT add markdown.
- Preserve logic, structure, naming conventions.
- Produce idiomatic code in the target language.
SOURCE LANGUAGE: {source}
TARGET LANGUAGE: {target}
CODE TO TRANSLATE:
{code}
"""
# Run translator sub-agent
events = await run_subagent(translator_agent, prompt)
# Collect raw text
translated_text = ""
for ev in events:
if ev.content and ev.content.parts:
for p in ev.content.parts:
if p.text:
translated_text += p.text + "\n"
# --------------------------------------------------------
# Cleanup: Remove markdown, explanations, wrappers
# --------------------------------------------------------
translated_code_only = (
translated_text.replace("```", "")
.replace("translated code:", "")
.strip()
)
return {
"status": "ok",
"translated_code": translated_code_only
}
# Register tool
translate_tool = FunctionTool(func=cmd_translate)
# ------------------------------------------------------------
# Similar tools can be defined:
# review_tool
# write_tool
# explain_tool
# docs_tool
# visualize_tool
# ------------------------------------------------------------
Notice the strict rules in the prompt for cmd_translate. This is a crucial technique to ensure the LLM returns exactly what you expect, especially when dealing with structured data.
The Coordinator Agent: Orchestrating the Workflow
The coordinator_agent is the maestro. It receives high-level user requests, determines which specialized tools (our cmd_ functions) are needed, and calls them in the correct sequence.
# ------------------------------------------------------------
# Coordinator Agent Instruction
# ------------------------------------------------------------
coordinator_instruction = """
You are the DevSuite Coordinator.
Users will send high-level requests such as:
- “Translate this code”
- “Review and refactor this”
- “Generate documentation”
- “Explain this code”
- “Write code from this spec”
- “Visualize this logic”
Your responsibilities:
1. Understand the user's intent clearly.
2. Choose the correct tool(s) from the available list.
3. Pass the right arguments to the tools.
4. Combine, summarize, or structure the tool outputs if needed.
5. Return clean, direct, helpful responses—never unnecessary text.
Your goal is to orchestrate tools intelligently and efficiently.
"""
# ------------------------------------------------------------
# Coordinator Agent
# ------------------------------------------------------------
coordinator_agent = LlmAgent(
name="devsuite_coordinator",
model=Gemini(
model="gemini-2.5-flash-lite",
retry_options=retry_config
),
instruction=coordinator_instruction,
tools=[
translate_tool,
review_refactor_tool,
write_tool,
explain_tool,
docs_tool,
visualize_tool,
# ... add more tools if needed ...
],
)
By registering all FunctionTools with the coordinator, it gains the ability to use each sub-agent’s functionality on demand.
Setting Up and Running Your ADK Application
With your agents and tools defined, you need an App to house them and a Runner to execute them.
import uuid
from google.genai import types
from google.adk.apps.app import App, ResumabilityConfig
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
# ------------------------------------------------------------
# DevSuite App Setup
# ------------------------------------------------------------
app = App(
name="devsuite_app",
root_agent=coordinator_agent,
resumability_config=ResumabilityConfig(is_resumable=True)
)
session_service = InMemorySessionService()
runner = Runner(app=app, session_service=session_service)
# ------------------------------------------------------------
# Main Entry: Handle a DevSuite Request
# ------------------------------------------------------------
async def run_dev_suite_request(user_input: str):
# Create a unique session
session_id = f"session_{uuid.uuid4().hex[:6]}"
await session_service.create_session(
app_name="devsuite_app",
user_id="dev_user",
session_id=session_id
)
# Build message content
content = types.Content(
role="user",
parts=[types.Part(text=user_input)]
)
# Run agent and collect events
events = []
async for ev in runner.run_async(
user_id="dev_user",
session_id=session_id,
new_message=content
):
events.append(ev)
# Print output for debugging
for ev in events:
if ev.content and ev.content.parts:
for p in ev.content.parts:
if p.text:
print("AGENT:", p.text)
return events
# ------------------------------------------------------------
# Example Usage (Uncomment when running async)
# ------------------------------------------------------------
# await run_dev_suite_request(
# "Translate this Python code to TypeScript: def hello(): return 'world'"
# )
The run_dev_suite_request function demonstrates how you would interact with your agent. It creates a session and sends a user message, then prints the agent’s responses.
Advanced Example: Building a Pair Programming Agent
ADK’s modularity shines when building complex, interactive agents like a pair programming assistant. This agent combines several specialized sub-agents to offer real-time code completions, reviews, and alternative suggestions.
Instead of a single cmd_ function, a cmd_pair_analyze tool orchestrates multiple analyses in parallel using asyncio.gather:
import asyncio
from typing import Dict, Any
from google.adk.tools.function_tool import FunctionTool
# ------------------------------------------------------------
# Define: completion_agent, real_time_review_agent, alternative_agent
# (same pattern as translator_agent / reviewer_agent)
# ------------------------------------------------------------
# Example placeholders (you can replace with real definitions):
# completion_agent = LlmAgent(...)
# real_time_review_agent = LlmAgent(...)
# alternative_agent = LlmAgent(...)
# ------------------------------------------------------------
# Pair Analysis Command
# ------------------------------------------------------------
async def cmd_pair_analyze(payload: Dict[str, Any]):
code = payload.get("code", "")
cursor_position = payload.get("cursor_position", [0, 0])
language = payload.get("language", "python")
# Run three parallel analysis paths
analysis_tasks = [
_analyze_completions(code, cursor_position, language),
_analyze_review(code, cursor_position, language),
_analyze_alternatives(code, cursor_position, language)
]
# Run all tasks concurrently
results = await asyncio.gather(*analysis_tasks, return_exceptions=True)
# --------------------------------------------------------
# Aggregate & Rank Suggestions
# --------------------------------------------------------
ranked_suggestions = []
for res in results:
if isinstance(res, Exception):
continue
if res:
ranked_suggestions.extend(res)
# Sort (sample: by score if your analyzers return {text, score})
ranked_suggestions = sorted(
ranked_suggestions,
key=lambda x: x.get("score", 0),
reverse=True
)
return {
"status": "ok",
"suggestions": ranked_suggestions
}
# ------------------------------------------------------------
# Register the Pair Analysis Tool
# ------------------------------------------------------------
pair_analyze_tool = FunctionTool(func=cmd_pair_analyze)
# ------------------------------------------------------------
# Add this tool to the Coordinator Agent
# ------------------------------------------------------------
# coordinator_agent.tools.append(pair_analyze_tool)
Each _analyze_ function (_analyze_completions, _analyze_review, _analyze_alternatives) calls its respective sub-agent, processes its structured JSON output, and formats it into a unified suggestion list. This demonstrates how ADK allows for sophisticated, multi-faceted agent behaviors.
Conclusion: Your Journey into Agentic AI
You’ve now seen how to leverage Google’s ADK in Python to build powerful, modular AI agents. From specialized sub-agents for specific tasks to a central coordinator that orchestrates complex workflows, ADK provides a robust framework for agent development.
With this foundation, you can explore endless possibilities: integrate more external tools, build agents that interact with databases, create personalized assistants, or automate intricate business processes. The future of agentic AI is here, and you’re now equipped to be a part of it. Start experimenting, build your own unique agents, and unlock the next generation of intelligent applications!

