Giving Agents Tools: Python Functions as First-Class Capabilities
Equip your ADK 2 agents with typed Python tools. Learn schema auto-generation, tool payload optimization, and cost-conscious tool design.

Giving Agents Tools: Python Functions as First-Class Capabilities
📦 Companion Code: View the complete, runnable code on GitHub:
part02in the cartpilot-adk2-series repository.
In Part 1, CartPilot's search agent could talk but not act. Today it gets real tools — and we treat tool design as a cost decision from day one, not just a capability one.
How ADK turns functions into tools
ADK inspects a plain Python function — its name, docstring, type hints, and parameter names — and builds the function-calling schema the model sees automatically. No manual JSON schema authoring.
# cartpilot_search/catalog.py
CATALOG = {
"sku-001": {"sku": "sku-001", "name": "Trail Runner Jacket", "category": "outdoor",
"price": 89.99, "stock": 14, "color": "forest green", "sizes": ["S", "M", "L", "XL"]},
"sku-002": {"sku": "sku-002", "name": "Insulated Water Bottle 1L", "category": "outdoor",
"price": 24.50, "stock": 42, "color": "steel", "sizes": ["one-size"]},
"sku-003": {"sku": "sku-003", "name": "Wireless Trail Earbuds", "category": "electronics",
"price": 59.00, "stock": 0, "color": "black", "sizes": ["one-size"]},
"sku-004": {"sku": "sku-004", "name": "Merino Wool Hiking Socks (3-pack)", "category": "apparel",
"price": 18.75, "stock": 60, "color": "grey", "sizes": ["S", "M", "L"]},
"sku-005": {"sku": "sku-005", "name": "Compact Camp Stove", "category": "outdoor",
"price": 45.00, "stock": 7, "color": "silver", "sizes": ["one-size"]},
}
def find_products(query: str = "", category: str = "") -> list[dict]:
query = query.lower().strip()
return [
item for item in CATALOG.values()
if (query in item["name"].lower() if query else True)
and (item["category"] == category if category else True)
]
def get_product(sku: str) -> dict | None:
return CATALOG.get(sku)In production, swap this module for a real product-service client — the tool functions below don't change.
# cartpilot_search/agent.py
from google.adk.agents import Agent
from google.adk.tools import ToolContext
from .catalog import find_products, get_product
def search_products(query: str, category: str, tool_context: ToolContext) -> dict:
"""Searches the CartPilot catalog for products.
Args:
query: Free-text search term, e.g. "water bottle". Pass an empty
string to browse by category only.
category: Optional category filter, e.g. "outdoor", "electronics",
"apparel". Pass an empty string to search all categories.
Returns:
A dict with a "results" list of matching products, each including
sku, name, price, and stock count.
"""
results = find_products(query=query, category=category)
tool_context.state["last_search_results"] = [r["sku"] for r in results]
return {"status": "success", "results": results}
def get_product_details(sku: str) -> dict:
"""Fetches full details for a single product by SKU.
Args:
sku: The product SKU, e.g. "sku-002".
"""
product = get_product(sku)
if product is None:
return {"status": "not_found", "sku": sku}
return {"status": "success", "product": product}
root_agent = Agent(
model="gemini-3.5-flash",
name="product_search_agent",
description=(
"Helps shoppers find products in the CartPilot store by name, "
"category, or general description."
),
instruction="""
You are the CartPilot product search assistant.
- Use `search_products` to find items matching what the shopper describes.
- Use `get_product_details` when the shopper wants more info on one item.
- If `search_products` returns no results, say so plainly and suggest
broadening the search (drop the category filter, try a different term).
- Don't call a tool more than once for the same query — read the results
you already have before deciding you need another search.
- Keep responses short (2-4 sentences).
""",
tools=[search_products, get_product_details],
)A few things worth calling out:
ToolContextis injected, not requested. It appears in the function signature but never in the docstring'sArgs:— the model never fills it in; ADK passes it automatically. This is how a tool reads and writes session state (more in Part 3).- Return dicts with a
statusfield, always. We're setting up the convention Part 9 builds resilience on — the model needs a machine-readable way to tell "found it" from "not found" apart from parsing prose. - The docstring is the API contract the model reasons from. A vague docstring produces vague tool calls — wrong arguments, redundant calls, or the model guessing instead of calling the tool at all.
Tool design as a cost lever
This is the part that's easy to miss: every tool call is a full model round-trip — output tokens for the call, input tokens for the result, and (usually) another generation to turn the result into a response. Three habits keep that under control:
- Design tools to return everything the model is likely to need next, so it doesn't have to chain a second call to get a detail it could've had the first time.
search_productsreturns price and stock, not just names — because "is it in stock?" is an obvious follow-up. - Nudge against redundant calls in the instruction. We explicitly tell the agent not to re-call
search_productsfor a query it's already answered. Left unsaid, some models will re-search "just to be sure" on every turn — quietly doubling your tool-call cost per conversation. - Prefer one tool with parameters over many narrow tools.
search_products(query, category)beats separatesearch_by_nameandsearch_by_categorytools — fewer schema entries competing for the model's attention, and one call covers more shopper intents.
Try it
adk webAsk: "Do you have anything for hiking under $30?" — watch the event inspector show the search_products call, its arguments, and the raw tool result before the model turns it into a sentence.
What's next
Right now, every new chat starts from zero — CartPilot doesn't remember what it just searched for once the conversation moves on. Part 3 adds session state and a shopping cart, and we look at what multi-turn memory actually costs in tokens as a conversation grows.