Real Function Calling
A real model in the loop.
So far, the stand-in model has followed a script. Now we'll replace it with a real one, and give it a task that needs two tools, in the right order. Nobody will tell it what that order is. It has to work that out for itself.

- No more script
- A task that needs two tools
- It works out the order itself
Two tools, no instructions
A customer wants another helmet, like the one in an old order. To answer, Juno must look up the order to find the item, and then check the stock of that item. We give it both functions, and the library runs the loop for us. Below that, we print each tool that it called. Run it. Look at that. It called get order first, took the item name from the result, and then checked stock. It chained two tools, with no plan from us. That's what makes agents feel magical. And the reply is built on real data, with no guessing.
from google import genai
from google.genai import types
from shop import get_order, check_stock
client = genai.Client()
config = types.GenerateContentConfig(
system_instruction="You are Juno at Velo Supply. Use tools. Reply in two sentences.",
tools=[get_order, check_stock])
reply = client.models.generate_content(
model="gemini-flash-latest", config=config,
contents="I want another helmet like the one in order C09. Do you have any?")
print(reply.text)
for turn in reply.automatic_function_calling_history:
for part in turn.parts:
if part.function_call:
print("Called:", part.function_call.name, dict(part.function_call.args))I checked order C09, and unfortunately, that helmet is currently out of stock. Please let me know if you would like me to recommend an alternative or check back later!
Called: get_order {'order_id': 'C09'}
Called: check_stock {'item': 'Helmet'}Also uses shop.py
ORDERS = {
"A17": {"item": "Carbon wheelset", "price": 640, "status": "delivered", "days_ago": 12},
"B42": {"item": "Chain", "price": 28, "status": "shipped", "days_ago": 2},
"C09": {"item": "Helmet", "price": 95, "status": "delivered", "days_ago": 45},
}
STOCK = {"Carbon wheelset": 3, "Chain": 14, "Helmet": 0}
REFUNDS = []
def get_order(order_id: str) -> dict:
"""Look up one order by its ID, such as A17."""
return ORDERS.get(order_id, {"error": f"No order {order_id}"})
def check_stock(item: str) -> dict:
"""Return how many of an item are in stock."""
return {"item": item, "in_stock": STOCK.get(item, 0)}
def refund(order_id: str, amount: float) -> dict:
"""Refund money to the customer for an order."""
REFUNDS.append((order_id, amount))
return {"refunded": amount, "order_id": order_id}Also uses scripted.py
class Scripted:
"""A stand-in model that replays a fixed list of replies."""
def __init__(self, replies):
self.replies = list(replies)
self.calls = 0
def ask(self, messages):
self.calls += 1
return self.replies.pop(0)Your own loop, with a real model
Automatic calling is handy, but in production, you usually want your own loop. That's where you can add limits, logging, and approvals. This is the loop that we wrote before, with a real model inside it. If there are no function calls, we're done. Otherwise, we run each one, and add the results to the history. Run it. There are the steps, one by one, and then the answer. Every agent framework is basically this, plus some nice extras.
from google import genai
from google.genai import types
from shop import get_order, check_stock
TOOLS = {"get_order": get_order, "check_stock": check_stock}
client = genai.Client()
config = types.GenerateContentConfig(
system_instruction="You are Juno at Velo Supply. Use tools. Reply in two sentences.",
tools=list(TOOLS.values()),
automatic_function_calling=types.AutomaticFunctionCallingConfig(disable=True))
def run(goal, max_steps=6):
history = [types.Content(role="user", parts=[types.Part(text=goal)])]
for step in range(1, max_steps + 1):
r = client.models.generate_content(model="gemini-flash-latest",
config=config, contents=history)
if not r.function_calls:
return r.text
history.append(r.candidates[0].content)
parts = []
for c in r.function_calls:
out = TOOLS[c.name](**c.args)
print(f"Step {step}: {c.name}({dict(c.args)})")
parts.append(types.Part.from_function_response(name=c.name, response=out))
history.append(types.Content(role="user", parts=parts))
return "Passing this to a person."
print(run("Is the item from order A17 still in stock?"))Step 1: get_order({'order_id': 'A17'})
Step 2: check_stock({'item': 'Carbon wheelset'})
The item from order A17 is the Carbon wheelset. It is currently in stock with 3 units available.Also uses shop.py
ORDERS = {
"A17": {"item": "Carbon wheelset", "price": 640, "status": "delivered", "days_ago": 12},
"B42": {"item": "Chain", "price": 28, "status": "shipped", "days_ago": 2},
"C09": {"item": "Helmet", "price": 95, "status": "delivered", "days_ago": 45},
}
STOCK = {"Carbon wheelset": 3, "Chain": 14, "Helmet": 0}
REFUNDS = []
def get_order(order_id: str) -> dict:
"""Look up one order by its ID, such as A17."""
return ORDERS.get(order_id, {"error": f"No order {order_id}"})
def check_stock(item: str) -> dict:
"""Return how many of an item are in stock."""
return {"item": item, "in_stock": STOCK.get(item, 0)}
def refund(order_id: str, amount: float) -> dict:
"""Refund money to the customer for an order."""
REFUNDS.append((order_id, amount))
return {"refunded": amount, "order_id": order_id}Also uses scripted.py
class Scripted:
"""A stand-in model that replays a fixed list of replies."""
def __init__(self, replies):
self.replies = list(replies)
self.calls = 0
def ask(self, messages):
self.calls += 1
return self.replies.pop(0)Why you want your own loop
Owning the loop gives you four things. You see every step, so you can log it. You can stop at any time, so runaway agents can't happen. You can insert checks before a tool runs, such as an approval. And you can change which tools are available at each step. Those are the four levers of a production agent.
Summary
A real model chained two tools with no help. And you own the loop that made it happen. So now the quality of an agent depends mostly on the quality of its tools. That's next.
Key takeaways- Models can chain tools
- Own the loop in production
- Log, stop, check, change