MIKAEELS

Explore the blog

Get Started with ADK: Add Custom Python Tools to Your Agent

The agent from Part 2 can explain service health, but it cannot look up a service's status. We will now add two tools: one lists supported services and the other reads a fixed demo record.

This tutorial uses simulated data throughout. The goal is to understand the tool boundary before connecting a monitoring API.

Prerequisites and file changes

Complete Part 2 first. Activate its .venv, work from adk-starter, and stop any running ADK process before editing.

We will add ops_assistant/tools.py and replace ops_assistant/agent.py. Keep __init__.py and .env from Part 2.

Step 1: Define the tool contract

Our lookup accepts one string, service_name. For a known service, it returns status: success, the normalized name, its health, and a description. For an unknown name, it returns status: error and the supported names.

A tool's operation status is different from service health. A successful lookup can report a degraded service. Keeping those fields separate prevents an API success from being mistaken for healthy infrastructure.

ADK can wrap a Python function when it is included in an agent's tools list. Type annotations and docstrings describe the callable to the model. See Google's function tools documentation.

Step 2: Create the complete tools module

Create adk-starter/ops_assistant/tools.py:

DEMO_SERVICES = { "payments": { "health": "degraded", "detail": "Elevated checkout latency in the demo dataset.", }, "catalog": { "health": "operational", "detail": "No active incident in the demo dataset.", }, } def list_services() -> dict: """List the service names supported by this simulated status demo.""" return { "status": "success", "source": "simulated_demo_data", "services": sorted(DEMO_SERVICES), } def get_service_status(service_name: str) -> dict: """Look up simulated health for a service; this never queries live systems. Args: service_name: Service name, such as payments or catalog. """ name = service_name.strip().lower() record = DEMO_SERVICES.get(name) if record is None: return { "status": "error", "source": "simulated_demo_data", "error_message": f"Unknown service: {name!r}.", "supported_services": sorted(DEMO_SERVICES), } return { "status": "success", "source": "simulated_demo_data", "service": name, "health": record["health"], "detail": record["detail"], }

Normalization lets PAYMENTS and payments refer to the same record. Unsupported names remain errors; there is no default healthy result.

Step 3: Check the lookup without a model

Create adk-starter/check_tools.py with these complete contents:

import runpy from pathlib import Path tools_path = Path(__file__).resolve().parent / "ops_assistant" / "tools.py" tools = runpy.run_path(str(tools_path)) lookup = tools["get_service_status"] assert tools["list_services"]()["services"] == ["catalog", "payments"] assert lookup(" PAYMENTS ")["health"] == "degraded" assert lookup("catalog")["health"] == "operational" assert lookup("unknown")["status"] == "error" assert lookup("")["status"] == "error" print("Tool checks passed")

Run it from adk-starter:

python check_tools.py

Expected output:

Tool checks passed

This checks the Python lookup independently of model behavior and does not make an API request. runpy loads the file directly, avoiding the agent import in the package's __init__.py.

Step 4: Replace the complete agent file

Replace adk-starter/ops_assistant/agent.py with:

from google.adk.agents import Agent from .tools import get_service_status, list_services root_agent = Agent( name="ops_assistant", model="gemini-flash-latest", description="Explains simulated service health using a fixed demo dataset.", instruction=( "You are an operations learning assistant. " "For status questions naming a service, call get_service_status. " "If the user asks which services are supported, call list_services. " "If a status question is missing a service name, ask for one. " "Answer general operations questions without a status lookup. " "Use only tool results for service-health claims. " "Always explain that status results are simulated demo data, " "not a live monitoring result. " "If a tool returns an error, explain it and do not invent a status. " "You cannot restart, deploy, or modify services." ), tools=[list_services, get_service_status], )

The model receives the available tool definitions; ADK executes the Python function when the model requests it. The resulting data can then inform the answer. Google's function tools guide explains this execution flow.

Step 5: Run and inspect the interaction

Start the development interface:

adk web --port 8000

Open http://localhost:8000, select ops_assistant, and start a new conversation. Try these prompts individually:

PromptExpected behavior
Which services can you check?Lists catalog and payments from the tool
What is the status of payments?Reports simulated degradation and elevated checkout latency
Check CATALOGReports simulated operational health
What is the status of billing?Explains that the service is unsupported
What is the status?Asks for a service name
What does degraded mean?Explains the concept without looking up a service
Restart paymentsExplains that it cannot perform a restart

Inspect the interaction's event details in the development interface. For the payments question, look for get_service_status and its service_name argument. The successful tool result should contain:

{ "status": "success", "source": "simulated_demo_data", "service": "payments", "health": "degraded", "detail": "Elevated checkout latency in the demo dataset." }

The final sentence is model-generated and can vary. The fields above are produced by our function and should match exactly.

Step 6: Diagnose failures at the right boundary

If the direct checks fail, fix tools.py first. If the checks pass but the agent does not call a tool, inspect the prompt, tool registration, and instruction. If a tool call returns the right data but the answer contradicts it, that is a model-response failure.

If the browser appears to use old code, stop and restart adk web, then start a new session. If Python reports a relative-import error, use the ADK CLI from the parent directory instead of running agent.py directly.

When adapting this example to a real monitoring service, add authentication, timeouts, and explicit unavailable-data results inside the tool implementation. The model should not decide whether a caller is authorized to read a service.

Next, Part 4: Sessions and state will let the user select a service once and ask follow-up questions without repeating its name.

Quick Recap

How do I register a Python function as an ADK tool?

Define a function with type annotations and a clear docstring, then include it in the agent's tools list. ADK wraps supported functions as function tools.

Does the tutorial query real monitoring data?

No. It uses a fixed, explicitly labeled demo dataset so readers can verify the behavior without connecting infrastructure.

Can I check the lookup function without calling Gemini?

Yes. The tutorial includes a standalone check_tools.py script that loads tools.py with Python's runpy module, without importing the agent package or calling Gemini.

Comments

Share your thoughts and questions below