1. Declaring Tools
Function calling enables Gemini models to interact with external systems — APIs, databases, file systems, or any executable code. You declare what tools are available, and the model decides when and how to invoke them based on the user’s request.
1.1 FunctionDeclaration Schema
The explicit approach uses types.FunctionDeclaration with a full JSON Schema definition:
from google import genai
from google.genai import types
client = genai.Client()
# Define a tool with explicit schema
weather_tool = types.Tool(function_declarations=[
types.FunctionDeclaration(
name="get_weather",
description="Get the current weather conditions for a specified city. "
"Returns temperature, conditions, humidity, and wind speed.",
parameters=types.Schema(
type="OBJECT",
properties={
"city": types.Schema(
type="STRING",
description="The city name, e.g. 'London' or 'Tokyo'"
),
"units": types.Schema(
type="STRING",
description="Temperature units: 'celsius' or 'fahrenheit'",
enum=["celsius", "fahrenheit"]
)
},
required=["city"]
)
)
])
# Use the tool in a request
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="What's the weather in Paris right now?",
config=types.GenerateContentConfig(tools=[weather_tool])
)
# Check if the model wants to call a function
part = response.candidates[0].content.parts[0]
if part.function_call:
print(f"Function: {part.function_call.name}")
print(f"Args: {part.function_call.args}")
1.2 From Python Functions
The SDK can also generate schemas automatically from Python function signatures with type hints:
from google import genai
from google.genai import types
client = genai.Client()
# Define tools as regular Python functions with type hints
def get_stock_price(ticker: str, exchange: str = "NYSE") -> dict:
"""Get the current stock price for a ticker symbol.
Args:
ticker: The stock ticker symbol (e.g., AAPL, GOOGL)
exchange: The stock exchange (NYSE, NASDAQ, LSE)
Returns:
Dictionary with price, change, and volume
"""
# This would call a real API in production
return {"price": 185.42, "change": "+2.3%", "volume": "45M"}
def convert_currency(amount: float, from_currency: str, to_currency: str) -> dict:
"""Convert an amount between two currencies.
Args:
amount: The amount to convert
from_currency: Source currency code (e.g., USD, EUR, GBP)
to_currency: Target currency code
Returns:
Dictionary with converted amount and exchange rate
"""
return {"converted": amount * 0.92, "rate": 0.92}
# Pass Python functions directly — SDK infers the schema
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="How much is 500 USD in euros, and what's Apple's stock price?",
config=types.GenerateContentConfig(
tools=[get_stock_price, convert_currency]
)
)
# The model may request one or both functions
for part in response.candidates[0].content.parts:
if part.function_call:
print(f"Call: {part.function_call.name}({part.function_call.args})")
description field in the schema, which helps the model decide when to use each tool. Vague descriptions lead to incorrect tool selection.
2. The Function Calling Loop
Function calling is not a single request–response. It follows an agentic loop: the model requests a function call, you execute it, return the result, and the model either generates a final answer or requests another call.
2.1 Loop Pattern
from google import genai
from google.genai import types
client = genai.Client()
# Tool definition
def get_weather(city: str, units: str = "celsius") -> dict:
"""Get current weather for a city."""
# Simulated API response
weather_data = {
"London": {"temp": 14, "condition": "Overcast", "humidity": 78},
"Tokyo": {"temp": 26, "condition": "Sunny", "humidity": 55},
"New York": {"temp": 22, "condition": "Partly cloudy", "humidity": 62},
}
data = weather_data.get(city, {"temp": 20, "condition": "Unknown", "humidity": 50})
if units == "fahrenheit":
data["temp"] = round(data["temp"] * 9/5 + 32)
data["units"] = units
return data
# Dispatcher — maps function names to implementations
tool_functions = {"get_weather": get_weather}
# Start the agentic loop
contents = [
types.Content(role="user", parts=[
types.Part(text="What's the weather in London and Tokyo? Compare them.")
])
]
while True:
response = client.models.generate_content(
model="gemini-3.5-flash",
contents=contents,
config=types.GenerateContentConfig(tools=[get_weather])
)
# Append model response to history
contents.append(response.candidates[0].content)
# Check if model wants to call functions
function_calls = [
part for part in response.candidates[0].content.parts
if part.function_call
]
if not function_calls:
# No more function calls — model produced final text
print("Final Answer:")
print(response.text)
break
# Execute each function call and collect responses
function_responses = []
for part in function_calls:
fc = part.function_call
print(f"Executing: {fc.name}({fc.args})")
# Call the actual function
result = tool_functions[fc.name](**fc.args)
function_responses.append(
types.Part(function_response=types.FunctionResponse(
name=fc.name,
response=result
))
)
# Return all function results to the model
contents.append(types.Content(role="user", parts=function_responses))
2.2 Sequential Tool Calls
Sometimes the model needs to call tools in sequence — using the result of one call to inform the next:
from google import genai
from google.genai import types
client = genai.Client()
def search_flights(origin: str, destination: str, date: str) -> dict:
"""Search for available flights between two airports."""
return {
"flights": [
{"id": "FL123", "depart": "09:00", "arrive": "11:30", "price": 245},
{"id": "FL456", "depart": "14:00", "arrive": "16:30", "price": 189},
]
}
def book_flight(flight_id: str, passenger_name: str) -> dict:
"""Book a specific flight for a passenger."""
return {"confirmation": "BK-78901", "status": "confirmed", "flight_id": flight_id}
tool_functions = {
"search_flights": search_flights,
"book_flight": book_flight
}
contents = [
types.Content(role="user", parts=[
types.Part(text="Find the cheapest flight from London to Paris tomorrow "
"and book it for John Smith.")
])
]
# The model will: 1) search flights, 2) pick cheapest, 3) book it
max_turns = 5
for turn in range(max_turns):
response = client.models.generate_content(
model="gemini-3.5-flash",
contents=contents,
config=types.GenerateContentConfig(
tools=[search_flights, book_flight]
)
)
contents.append(response.candidates[0].content)
function_calls = [p for p in response.candidates[0].content.parts if p.function_call]
if not function_calls:
print(f"Final (turn {turn + 1}): {response.text}")
break
responses = []
for part in function_calls:
fc = part.function_call
print(f" Turn {turn + 1}: {fc.name}({fc.args})")
result = tool_functions[fc.name](**fc.args)
responses.append(types.Part(function_response=types.FunctionResponse(
name=fc.name, response=result
)))
contents.append(types.Content(role="user", parts=responses))
Due Diligence Automation
A venture capital firm uses Deep Research for pre-investment due diligence: market size analysis, competitor mapping, regulatory landscape, and technology risk assessment. What previously took an analyst 2 weeks now takes 4 hours, with comparable quality and better source coverage.
3. Strict Response Matching
Gemini enforces strict matching between function calls and function responses. Every function call must receive exactly one corresponding function response, in the correct order.
3.1 Matching Rules
- Every
function_callmust have a matchingfunction_responsewith the samename - Responses must appear in the same order as the calls
- You cannot skip, reorder, or add extra function responses
- When thinking is active, thought signatures from the model’s response MUST be preserved
- Violating any rule returns a
400 Bad Requesterror
from google import genai
from google.genai import types
client = genai.Client()
def get_temperature(city: str) -> dict:
"""Get temperature for a city."""
temps = {"Paris": 18, "Berlin": 15, "Rome": 24}
return {"city": city, "temp_celsius": temps.get(city, 20)}
def get_population(city: str) -> dict:
"""Get population of a city."""
pops = {"Paris": 2_161_000, "Berlin": 3_645_000, "Rome": 2_873_000}
return {"city": city, "population": pops.get(city, 1_000_000)}
tool_functions = {"get_temperature": get_temperature, "get_population": get_population}
contents = [
types.Content(role="user", parts=[
types.Part(text="Compare Paris and Berlin: temperature and population.")
])
]
response = client.models.generate_content(
model="gemini-3.5-flash",
contents=contents,
config=types.GenerateContentConfig(tools=[get_temperature, get_population])
)
# CORRECT: Match each function_call with its response IN ORDER
model_content = response.candidates[0].content
contents.append(model_content)
function_responses = []
for part in model_content.parts:
if part.function_call:
fc = part.function_call
result = tool_functions[fc.name](**fc.args)
# Name MUST match the function_call name exactly
function_responses.append(
types.Part(function_response=types.FunctionResponse(
name=fc.name, # Must match fc.name exactly
response=result
))
)
# All responses in one Content block, same order as calls
contents.append(types.Content(role="user", parts=function_responses))
final = client.models.generate_content(
model="gemini-3.5-flash",
contents=contents,
config=types.GenerateContentConfig(tools=[get_temperature, get_population])
)
print(final.text)
3.2 Error Handling
When a function execution fails, return an error inside the function response rather than omitting it:
from google import genai
from google.genai import types
client = genai.Client()
def unreliable_api(query: str) -> dict:
"""An API that might fail."""
raise ConnectionError("Service unavailable")
# When execution fails, still return a function_response with error info
def safe_execute(name: str, args: dict, func) -> types.Part:
"""Execute a function safely, returning errors as responses."""
try:
result = func(**args)
return types.Part(function_response=types.FunctionResponse(
name=name, response=result
))
except Exception as e:
# Return error as a valid function response — don't skip it!
return types.Part(function_response=types.FunctionResponse(
name=name,
response={"error": str(e), "error_type": type(e).__name__}
))
# The model receives the error and can inform the user gracefully
# rather than causing a 400 validation error from a missing response
4. Multimodal Function Responses
Function responses can include not just text/JSON but also images, audio, and other media. The key requirement: multimodal data must be returned inside the function_response part, not as separate message parts.
function_response part. Do NOT append them as separate Part objects alongside the function response — this violates the matching protocol.
from google import genai
from google.genai import types
import base64
client = genai.Client()
def generate_chart(data_type: str, values: list) -> dict:
"""Generate a chart image from data values."""
# In production, this would use matplotlib/plotly to create a chart
# and return the image bytes
import io
try:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(8, 5))
ax.bar(range(len(values)), values)
ax.set_title(f"{data_type} Chart")
buf = io.BytesIO()
fig.savefig(buf, format='png')
buf.seek(0)
plt.close(fig)
image_bytes = buf.getvalue()
return {
"chart_type": data_type,
"data_points": len(values),
"image_base64": base64.b64encode(image_bytes).decode()
}
except ImportError:
return {"chart_type": data_type, "data_points": len(values), "status": "generated"}
# The function response contains the image data INSIDE the response dict
# The model can then describe or reference the chart in its final answer
tool_functions = {"generate_chart": generate_chart}
contents = [
types.Content(role="user", parts=[
types.Part(text="Create a bar chart of monthly sales: [45, 52, 38, 61, 55, 70]")
])
]
response = client.models.generate_content(
model="gemini-3.5-flash",
contents=contents,
config=types.GenerateContentConfig(tools=[generate_chart])
)
contents.append(response.candidates[0].content)
# Execute and return multimodal result INSIDE the function_response
for part in response.candidates[0].content.parts:
if part.function_call:
fc = part.function_call
result = tool_functions[fc.name](**fc.args)
contents.append(types.Content(role="user", parts=[
types.Part(function_response=types.FunctionResponse(
name=fc.name,
response=result # Image data is INSIDE this response
))
]))
final = client.models.generate_content(
model="gemini-3.5-flash",
contents=contents,
config=types.GenerateContentConfig(tools=[generate_chart])
)
print(final.text)
5. Parallel Tool Calls
When the model determines that multiple independent operations are needed, it can request parallel tool calls in a single response — multiple function_call parts in one turn.
from google import genai
from google.genai import types
client = genai.Client()
def get_weather(city: str) -> dict:
"""Get current weather for a city."""
data = {
"London": {"temp": 14, "condition": "Rainy"},
"Tokyo": {"temp": 28, "condition": "Sunny"},
"Sydney": {"temp": 19, "condition": "Windy"},
}
return data.get(city, {"temp": 20, "condition": "Unknown"})
def get_time(timezone: str) -> dict:
"""Get current time in a timezone."""
times = {
"Europe/London": "14:30",
"Asia/Tokyo": "23:30",
"Australia/Sydney": "01:30",
}
return {"timezone": timezone, "current_time": times.get(timezone, "12:00")}
tool_functions = {"get_weather": get_weather, "get_time": get_time}
contents = [
types.Content(role="user", parts=[
types.Part(text="What's the weather and current time in London, Tokyo, and Sydney?")
])
]
response = client.models.generate_content(
model="gemini-3.5-flash",
contents=contents,
config=types.GenerateContentConfig(tools=[get_weather, get_time])
)
# Detect parallel calls — multiple function_call parts in one response
model_content = response.candidates[0].content
function_calls = [p for p in model_content.parts if p.function_call]
print(f"Model requested {len(function_calls)} parallel tool calls:")
for fc_part in function_calls:
print(f" → {fc_part.function_call.name}({fc_part.function_call.args})")
# Execute ALL calls and return ALL responses in matching order
contents.append(model_content)
function_responses = []
for fc_part in function_calls:
fc = fc_part.function_call
result = tool_functions[fc.name](**fc.args)
function_responses.append(
types.Part(function_response=types.FunctionResponse(
name=fc.name,
response=result
))
)
# Return all responses in a single Content block
contents.append(types.Content(role="user", parts=function_responses))
# Model synthesizes all results into a coherent answer
final = client.models.generate_content(
model="gemini-3.5-flash",
contents=contents,
config=types.GenerateContentConfig(tools=[get_weather, get_time])
)
print(f"\nFinal Answer:\n{final.text}")
asyncio.gather() or concurrent.futures to minimize latency. The results must still be returned in the same order as the requests.
from google import genai
from google.genai import types
import asyncio
client = genai.Client()
async def execute_parallel_calls(function_calls: list, tool_functions: dict) -> list:
"""Execute multiple function calls concurrently."""
async def run_one(fc_part):
fc = fc_part.function_call
# In production, these would be async API calls
result = tool_functions[fc.name](**fc.args)
return types.Part(function_response=types.FunctionResponse(
name=fc.name,
response=result
))
# Execute all calls concurrently
tasks = [run_one(fc_part) for fc_part in function_calls]
results = await asyncio.gather(*tasks)
return list(results)
# Usage: responses = await execute_parallel_calls(function_calls, tool_functions)
print("Parallel execution pattern ready for async tool calls")
6. Combining Tools & Function Calling (Preview)
You can combine built-in tools (Google Search, Code Execution) alongside your custom function declarations. The model intelligently decides which tool to use based on the query.
from google import genai
from google.genai import types
client = genai.Client()
# Custom function
def calculate_mortgage(principal: float, annual_rate: float, years: int) -> dict:
"""Calculate monthly mortgage payment and total interest."""
monthly_rate = annual_rate / 100 / 12
num_payments = years * 12
if monthly_rate == 0:
monthly_payment = principal / num_payments
else:
monthly_payment = principal * (monthly_rate * (1 + monthly_rate)**num_payments) / \
((1 + monthly_rate)**num_payments - 1)
total_paid = monthly_payment * num_payments
return {
"monthly_payment": round(monthly_payment, 2),
"total_interest": round(total_paid - principal, 2),
"total_paid": round(total_paid, 2)
}
# Combine built-in Google Search with custom function
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="What are current UK mortgage rates, and if I borrow £300,000 "
"at that rate for 25 years, what's my monthly payment?",
config=types.GenerateContentConfig(
tools=[
# Built-in: Google Search for current rates
types.Tool(google_search=types.GoogleSearch()),
# Custom: Mortgage calculator
calculate_mortgage
]
)
)
# The model may:
# 1. Use Google Search to find current rates
# 2. Use calculate_mortgage with the discovered rate
# 3. Synthesize both results into a final answer
for part in response.candidates[0].content.parts:
if part.function_call:
print(f"Tool used: {part.function_call.name}")
elif part.text:
print(f"Text: {part.text[:200]}...")
from google import genai
from google.genai import types
client = genai.Client()
def get_internal_inventory(product_id: str) -> dict:
"""Check internal warehouse inventory for a product by ID."""
inventory = {"SKU-001": 45, "SKU-002": 0, "SKU-003": 128}
qty = inventory.get(product_id, -1)
return {"product_id": product_id, "in_stock": qty > 0, "quantity": qty}
# Combine: search for product info + check internal stock
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="Look up reviews for the Sony WH-1000XM5 headphones, "
"and check if we have SKU-001 in stock.",
config=types.GenerateContentConfig(
tools=[
types.Tool(google_search=types.GoogleSearch()),
get_internal_inventory
]
)
)
# Model uses Google Search for reviews AND custom function for inventory
for part in response.candidates[0].content.parts:
if part.function_call:
print(f"Calling: {part.function_call.name}({part.function_call.args})")
Next in the Gemini SDK Track
In Part 7: Built-in Tools: Search, Maps, URL & Code Execution, we’ll ground Gemini responses with Google Search, Google Maps, and URL context tools, use the code execution sandbox for calculations and data processing, and combine multiple built-in tools in a single request.