Get Started with ADK
Part 3 of 4The 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:
| Prompt | Expected 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 CATALOG | Reports 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 payments | Explains 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