Function Tools
An agent that can only talk is a chatbot. An agent that can act is a tool. You'll build the Lakeside Outfitters Order Operations Agent — it resolves real requests by calling typed Python functions the model invokes on demand.
How tool calling works
You don't call the tools — the model does. You describe each function with type hints and a docstring; the framework advertises them to the model, which decides which to call, with what arguments, and when to chain several.
sequenceDiagram
participant U as User
participant A as Agent
participant M as Model
participant T as Your Python tools
U->>A: "Status of LO-10231 and when will it arrive?"
A->>M: message + tool schemas
M-->>A: call lookup_order_status("LO-10231")
A->>T: execute
T-->>A: "packed, 1x TENT-4P"
M-->>A: call estimate_delivery("LO-10231")
A->>T: execute
T-->>A: "arrives ~3 days"
M-->>A: final natural-language answer
A-->>U: response.text
Define tools as plain functions
A tool is just a Python function. Three things make it model-ready:
- Type hints on every parameter and the return value.
Annotated[type, Field(description=...)]to describe each argument — the model reads these to fill in arguments correctly.- A docstring — it becomes the tool's description, telling the model when to use it.
from typing import Annotated
from datetime import date, timedelta
from pydantic import Field
_ORDERS = {
"LO-10231": {"status": "packed", "sku": "TENT-4P", "qty": 1, "region": "WA"},
"LO-10477": {"status": "shipped", "sku": "BAG-65L", "qty": 2, "region": "NY"},
}
_TRANSIT_DAYS = {"WA": 2, "NY": 4, "TX": 3}
def lookup_order_status(
order_id: Annotated[str, Field(description="Order ID, e.g. 'LO-10231'")],
) -> str:
"""Return the current fulfillment status of an order."""
order = _ORDERS.get(order_id.upper())
if not order:
return f"No order found with ID {order_id}."
return f"Order {order_id.upper()} is '{order['status']}' ({order['qty']}x {order['sku']})."
def estimate_delivery(
order_id: Annotated[str, Field(description="Order ID to estimate delivery for")],
) -> str:
"""Estimate the delivery date for an order based on its destination region."""
order = _ORDERS.get(order_id.upper())
if not order:
return f"No order found with ID {order_id}."
days = _TRANSIT_DAYS.get(order["region"], 5)
eta = date.today() + timedelta(days=days)
return f"Order {order_id.upper()} should arrive around {eta.isoformat()} ({days} days)."
Register tools with the agent
Pass the functions in a list to create_agent. No decorators, no manual schema.
agent = client.create_agent(
name="OrderOpsAgent",
instructions=(
"You are Lakeside Outfitters' order operations assistant. "
"Use the available tools to answer questions about orders, stock, "
"delivery dates, and refunds. Be concise and confirm actions you take."
),
tools=[lookup_order_status, check_inventory, estimate_delivery, initiate_refund],
)
Run it
cd modules/04_function_tools
python main.py
Watch the agent chain two tools in the first turn — status and delivery — from a single question:
User: What's the status of order LO-10231 and when will it arrive? Agent: Order LO-10231 is packed (1x TENT-4P) and should arrive around 2025-01-18, about 2 days out. User: Is BAG-65L in stock? Order LO-10477 has 2 of them. Agent: BAG-65L is currently out of stock. Order LO-10477 already shipped its 2 units, so it's unaffected — but new orders for that SKU can't be fulfilled until it's restocked. User: Refund order LO-10588 — the customer changed their mind. Agent: Done — refund RF-10588 has been initiated for order LO-10588 (reason: customer changed their mind).
mode flag. Small tools are easier for the model to choose correctly, easier to test, and easier to authorize independently.Design checklist
- One responsibility per tool —
initiate_refund, notmanage_order. - Describe every argument with
Field(description=...). - Return strings the model can reason over — include IDs and outcomes.
- Validate inside the function — never assume the model passed safe input.
- Make side-effects auditable — log who/what/when for refunds and writes.