Back to Blog
This post is Part 6 of 13 in the series: AI Architect Foundations

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.

Share Editorial
Giving LLMs Tools: Function Calling with Gemini

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.ipynb in 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.

diagram
Rendering diagram…

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:

  1. 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.
  2. Your Application is the Hands: The model never executes code directly. It returns a structured function_call payload. 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:

python
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:

python
# 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

text
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:

python
# 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

text
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

  1. Security & Control: The model only generates invocation intents; your application controls tool execution.
  2. Schema Clarity: Explicit JSON schemas with parameter descriptions guide the model to supply exact types.
  3. Multi-Turn Roundtrip: A tool execution involves: User Request $\to$ Model function_call $\to$ Local Execution $\to$ User function_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.