Giving LLMs Tools: Function Calling with Gemini
Transform your LLM from a passive text generator into an active agent. Learn how to declare external tools with JSON schemas and parse function calls in the Google GenAI SDK.

TL;DR: LLMs cannot query your database, send emails, or check order statuses on their own. Function Calling is the mechanism that bridges this gap: you define functions and schemas that the model is allowed to use, the model signals when it needs to call a function with specific arguments, and your code executes the action and feeds the result back.
π¦ Interactive Notebook: Test and run this code live on GitHub:
06_function_calling.ipynbin the ai-architect-foundation-gemini repository.
What You'll Learn
How function calling works, how to define tool declarations using types.FunctionDeclaration and types.Schema, and how to detect, execute, and handle function call responses in Gemini.
The Core Concept: Brain vs. Hands
An LLM on its own is an isolated reasoning engine. It has no network connectivity to your internal APIs or databases.
When you provide tools to the model:
- The Model is the Brain: It reasons about user intent, decides if external data or actions are required, and selects which tool to invoke with exact JSON arguments.
- Your Application is the Hands: The model never executes code directly. It returns a structured
function_callpayload. Your application executes the Python function securely, inspects the result, and passes the output back to the model.
Defining Tools with FunctionDeclaration
In the google-genai SDK, tools are defined using types.FunctionDeclaration backed by types.Schema:
from google import genai
from google.genai import types
client = genai.Client()
model = "gemini-2.5-flash"
# Define the Tool Schema
lookup_order_declaration = types.FunctionDeclaration(
name="lookup_order",
description="Look up a customer order by its unique order ID. Returns status, items, delivery tracking, and return eligibility.",
parameters=types.Schema(
type="OBJECT",
properties={
"order_id": types.Schema(
type="STRING",
description="The customer order identifier (e.g. ORD-1001)."
)
},
required=["order_id"]
)
)
# Package into a Tool object
tools = [types.Tool(function_declarations=[lookup_order_declaration])]Dispatching a Request with Tools
Pass your tools array inside GenerateContentConfig:
# User query requiring external order lookup
prompt = "Hi, can you check the delivery status of my order ORD-77819?"
response = client.models.generate_content(
model=model,
contents=prompt,
config=types.GenerateContentConfig(
tools=tools,
temperature=0.0
)
)
# Inspect the candidate response parts
candidate = response.candidates[0]
for part in candidate.content.parts:
if part.function_call:
fc = part.function_call
print(f"Tool Requested: {fc.name}")
print(f"Arguments: {dict(fc.args)}")
elif part.text:
print(f"Text Response: {part.text}")Output
Tool Requested: lookup_order
Arguments: {'order_id': 'ORD-77819'}Executing the Function and Returning the Result
Once the model requests a function call, your application executes the local function and returns a types.Part.from_function_response:
# 1. Local Python Implementation
def lookup_order(order_id: str) -> dict:
# Simulated database lookup
return {
"order_id": order_id,
"status": "In Transit",
"carrier": "DHL Express",
"eta": "Wednesday, July 15",
"item": "Premium Wool Coat",
"eligible_for_return": True
}
# 2. Map tool names to functions
tool_dispatcher = {
"lookup_order": lookup_order
}
# 3. Execute tool call requested by the model
fc = response.candidates[0].content.parts[0].function_call
tool_result = tool_dispatcher[fc.name](**dict(fc.args))
# 4. Construct multi-turn context with the tool result
contents = [
types.Content(role="user", parts=[types.Part(text=prompt)]),
response.candidates[0].content, # The model's function_call turn
types.Content(
role="user",
parts=[
types.Part.from_function_response(
name=fc.name,
response=tool_result
)
]
)
]
# 5. Send back to the model for the final customer-facing answer
final_response = client.models.generate_content(
model=model,
contents=contents,
config=types.GenerateContentConfig(tools=tools, temperature=0.0)
)
print("\n--- Final Model Response ---")
print(final_response.text)
# --- Execution Output ---
# Tool Requested: lookup_order
# Arguments: {'order_id': 'ORD-77819'}
#
# --- Final Model Response ---
# Your order ORD-77819 (Premium Wool Coat) is currently In Transit via DHL Express.
# It is scheduled for delivery on Wednesday, July 15. Since it is within our 30-day
# return window, you will be eligible for a full return or replacement upon delivery.
# Would you like me to set a reminder or help with anything else?Final Output
Your order ORD-77819 (Premium Wool Coat) is currently In Transit with DHL Express. It is scheduled for delivery on Wednesday, July 15.Writing High-Performance Tool Descriptions
The description field in FunctionDeclaration is prompt surface that the model uses to decide whether to call a tool:
| β Poor Tool Description | β Production-Grade Tool Description |
|---|---|
"Gets order details." | "Look up an order by order_id. Returns delivery tracking, line items, and return eligibility. Use whenever a customer inquires about their order." |
"Process refund." | "Initiates a payment refund for an order. Requires verified customer_id and order_id. Do NOT call without checking return policy first." |
Key Takeaways
- Security & Control: The model only generates invocation intents; your application controls tool execution.
- Schema Clarity: Explicit JSON schemas with parameter descriptions guide the model to supply exact types.
- Multi-Turn Roundtrip: A tool execution involves: User Request $\to$ Model
function_call$\to$ Local Execution $\to$ Userfunction_response$\to$ Final Model Answer.
What's Next
In Part 7: Guaranteed Structured Outputs & Schema Validation, we force the model to strictly adhere to JSON schemas for reliable parsing.