Home / Articles

Google ADK Agents: From a Single Gemini Agent to Cloud Run Deploys

Google ADK from one Gemini agent to production: tools, agents as tools, sequential flows, local testing, and deploys to Cloud Run and Vertex AI Agent Engine.

Publisher: REACTAPP.TOP7 min read1614 words

This article is published in English.

Sources: Introduction to Agentic AI with Google ADK

Large language models changed how people use software, but the next step has less to do with smarter chat and more to do with systems that reason, call tools, work together and carry out whole workflows on their own. That's the territory of agentic AI. Where a classic app sends one prompt and waits for one answer, an agent decides what to do next: it may call an API, fetch information, hand a subtask to a specialist or return a structured result.

Google's Agent Development Kit (ADK) is a practical framework for learning this. It lets you build agents on Gemini models, expose Python functions as tools, orchestrate several agents and deploy to Google Cloud. The sections below move from a single agent up to cloud deployment.

From prompts to agents

Traditional LLM apps are prompt-and-response. You ask, the model answers, and that's the end of it. In an agentic setup the model sits inside a larger execution system. It can hold goals, choose tools, talk to APIs and cooperate with other agents. The idea to remember is that an agent is more than a prompt: it's a software component driven by a language model.

An ADK app centers on a root_agent. The smallest version looks like this:

from google.adk.agents import Agent

root_agent = Agent(
    name="assistant",
    model="gemini-2.5-flash",
    instruction="You are a helpful assistant"
)

Even this is a working agent with a name, behavior, a model and the ability to run. It can chat, but it can't touch anything outside itself until it gets tools.

Tools

What defines agentic AI is tool use. Without tools a model only produces text, and with them an agent can run real logic. In ADK, a plain Python function turns into a tool as soon as it's passed to the agent's configuration. A calculator agent shows how:

from google.adk.agents import Agent


def calculator(a: float, b: float, operation: str) -> float:
    if operation == "add":
        return a + b

    if operation == "subtract":
        return a - b

    if operation == "multiply":
        return a * b

    if operation == "divide":
        if b == 0:
            raise Exception("Cannot divide by zero")

        return a / b

    raise Exception("Unsupported operation")


root_agent = Agent(
    name="assistant",
    model="gemini-2.5-flash",
    instruction=(
        "You are a helpful assistant with calculator capabilities. "
        "Use the calculator tool for arithmetic. "
        "Supported operations are add, subtract, multiply, divide."
    ),
    tools=[calculator]
)

Here the model understands the request, but rather than doing arithmetic itself, it hands the exact computation to the function. The LLM does the reasoning and the tool does the executing. That split underpins serious enterprise designs.

Real systems seldom rely on one tool. An assistant might call APIs, convert data, query a database, fetch documents, check policies and summarize outcomes. A multi-tool agent below combines arithmetic, unit conversion and weather lookup:

from google.adk.agents import Agent


def calculator(a: float, b: float, operation: str) -> float:
    if operation == "add":
        return a + b

    if operation == "subtract":
        return a - b

    if operation == "multiply":
        return a * b

    if operation == "divide":
        if b == 0:
            raise ValueError("Cannot divide by zero.")

        return a / b

    raise ValueError("Unsupported operation.")


def convert_units(value: float, from_unit: str, to_unit: str) -> float:
    from_unit = from_unit.lower()
    to_unit = to_unit.lower()

    if from_unit == "km" and to_unit == "miles":
        return value * 0.621371

    if from_unit == "miles" and to_unit == "km":
        return value / 0.621371

    if from_unit == "celsius" and to_unit == "fahrenheit":
        return value * 9 / 5 + 32

    if from_unit == "fahrenheit" and to_unit == "celsius":
        return (value - 32) * 5 / 9

    raise ValueError("Unsupported unit conversion.")


def get_weather_mock(city: str) -> dict:
    weather_data = {
        "bucharest": {
            "temperature_celsius": 23,
            "condition": "sunny",
            "wind_speed_kmh": 10,
        },
        "london": {
            "temperature_celsius": 16,
            "condition": "rain",
            "wind_speed_kmh": 18,
        },
    }

    key = city.lower()

    if key not in weather_data:
        return {
            "city": city,
            "error": "Weather data not available."
        }

    return {
        "city": city,
        **weather_data[key],
    }


root_agent = Agent(
    name="multi_tool_agent",
    model="gemini-2.5-flash",
    instruction=(
        "You are a practical assistant. "
        "Use the available tools when the user asks for calculations, "
        "unit conversions, or weather information."
    ),
    tools=[
        calculator,
        convert_units,
        get_weather_mock,
    ],
)

The agent now acts as an orchestrator. It picks tools on the fly based on the request, and the user never has to say which one to use.

Agents as tools of other agents

ADK lets one agent serve as a tool for another, which encourages specialization. Instead of one giant prompt that tries to cover everything, you build smaller agents with narrow jobs. A travel planner assembled from several agents illustrates it:

from google.adk.agents import LlmAgent
from google.adk.tools import google_search
from google.adk.tools import google_maps_grounding
from google.adk.tools.agent_tool import AgentTool


routing_agent = LlmAgent(
    name="routing_agent",
    model="gemini-2.5-pro",
    instruction="""
    You are a routing agent.
    Use google_maps_grounding to estimate routes and travel times.
    """,
    tools=[google_maps_grounding],
)


discovery_agent = LlmAgent(
    name="discovery_agent",
    model="gemini-2.5-pro",
    instruction="""
    You are a travel discovery agent.
    Use Google Search to find interesting places.
    """,
    tools=[google_search]
)


composer_agent = LlmAgent(
    name="composer_agent",
    model="gemini-2.5-pro",
    instruction="""
    Write a friendly travel itinerary based on the collected information.
    """,
    tools=[]
)


root_agent = LlmAgent(
    name="travel_agent",
    model="gemini-2.5-pro",
    instruction="""
    You are a travel assistant.
    Coordinate discovery, routing, and itinerary composition.
    """,
    tools=[
        AgentTool(discovery_agent),
        AgentTool(routing_agent),
        AgentTool(composer_agent)
    ]
)

The root agent works as an orchestrator without a fixed workflow behind it, and every sub-agent is a specialist that can be called in any order. Such a system is simpler to maintain, scale and reason about, which matches the direction many enterprise AI designs are taking.

Structured flows

Not every process should be fully autonomous, since business systems often need structure. A common shape is planning, then execution, then reporting. ADK covers it with sequential agents, as in this planner, executor and reporter chain:

from google.adk.agents import Agent, SequentialAgent
from google.adk.tools import AgentTool


planner_agent = Agent(
    name="planner_agent",
    model="gemini-2.5-flash",
    instruction="""
    Read the user request and create a short execution plan.
    """
)


executor_agent = Agent(
    name="executor_agent",
    model="gemini-2.5-flash",
    instruction="""
    Execute the plan and delegate specialist work.
    """,
    tools=[]
)


report_agent = Agent(
    name="report_agent",
    model="gemini-2.5-flash",
    instruction="""
    Produce the final report based on execution results.
    """
)


root_agent = SequentialAgent(
    name="planner_executor_report_workflow",
    sub_agents=[
        planner_agent,
        executor_agent,
        report_agent,
    ],
)

Local development

ADK's local loop is quick. First, a .env file sets the Google Cloud project, location and Vertex AI usage:

GOOGLE_CLOUD_PROJECT=PROJECT_ID
GOOGLE_CLOUD_LOCATION=us-central1
GOOGLE_GENAI_USE_VERTEXAI=True

Then one command starts a local web UI for trying the agents interactively:

adk web

That fast cycle suits experimentation.

Deploying to Cloud Run

Local testing only goes so far, and Cloud Run is one of the easiest production routes. For the first agent above, a short Dockerfile is enough:

FROM python:3.11-slim

WORKDIR /app

COPY . .

RUN pip install --no-cache-dir -r requirements.txt

CMD ["adk", "web", "--host", "0.0.0.0", "--port", "8080"]

Deploy from source with gcloud:

gcloud run deploy simple-agent \
  --source . \
  --region us-central1 \
  --allow-unauthenticated \
  --set-env-vars GOOGLE_GENAI_USE_VERTEXAI=TRUE \
  --set-env-vars GOOGLE_CLOUD_PROJECT=PROJECT_ID \
  --set-env-vars GOOGLE_CLOUD_LOCATION=us-central1

Afterward, look up the service URL:

gcloud run services describe simple-agent \
  --region us-central1 \
  --format='value(status.url)'

Opening that URL serves the ADK interface straight from Cloud Run. The result is a deployable AI service, not a notebook toy.

Exposing an API with FastAPI

Sometimes an HTTP API fits better than a browser UI, and ADK works well with FastAPI. A minimal main.py wrapping the weather-tool agent looks like this:

import uuid

from fastapi import FastAPI
from pydantic import BaseModel

from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types

from agent import root_agent


app = FastAPI()

session_service = InMemorySessionService()

runner = Runner(
    agent=root_agent,
    app_name="weather_agent_service",
    session_service=session_service,
)


class QueryRequest(BaseModel):
    message: str


@app.post("/weather")
async def weather(request: QueryRequest):

    user_id = "api_user"
    session_id = str(uuid.uuid4())

    await session_service.create_session(
        app_name="weather_agent_service",
        user_id=user_id,
        session_id=session_id,
    )

    content = types.Content(
        role="user",
        parts=[
            types.Part(text=request.message)
        ],
    )

    final_answer = ""

    async for event in runner.run_async(
        user_id=user_id,
        session_id=session_id,
        new_message=content,
    ):
        if event.is_final_response():
            final_answer = event.content.parts[0].text

    return {
        "response": final_answer
    }

Deploying it needs a slightly different Dockerfile:

FROM python:3.11-slim

WORKDIR /app

COPY . .

RUN pip install --no-cache-dir -r requirements.txt

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]

Agent Engine

ADK can also deploy to Google's Agent Engine, a managed runtime built for enterprise agent execution. Start by enabling the needed APIs:

gcloud services enable \
  aiplatform.googleapis.com \
  storage.googleapis.com

Next, create a staging bucket:

export STAGING_BUCKET="gs://${PROJECT_ID}-agent-staging"

gsutil mb -l us-central1 $STAGING_BUCKET

Then deploy the agent:

adk deploy agent_engine \
--project=$PROJECT_ID \
--region=us-central1 \
--staging_bucket=$STAGING_BUCKET \
basic_agent

This produces a managed reasoning-engine resource in Google Cloud, and the process is clean given how much happens underneath.

Why this matters

Agentic AI is a different way of building software around language models, not merely better prompts. Agents reason, use tools, coordinate workflows and work with outside systems instead of producing isolated replies.

ADK is a good learning vehicle since it brings these ideas in gradually. You begin with one Gemini agent, add tools, connect several agents and later reach enterprise workflows with RAG, MCP integrations, evaluation pipelines and deployment on GCP. The same principles hold from the calculator example to a multi-agent enterprise setup: reasoning, delegation, orchestration and execution. Software is shifting from answering questions to operating inside real applications.