Introduction & Overview

In previous lessons, you learned how to define function schemas and understand how Gemini can interact with external tools through function calling. Gemini's API allows you to register Python functions as tools, describe their parameters, and let the model decide when to call them as part of a conversation.

In this lesson, you'll learn how to build a complete tool execution pipeline: registering functions, detecting function call requests, executing Python code, and returning results to obtain a final natural language answer.

The Complete Tool Execution Flow

The workflow for enabling Gemini to use tools follows a specific cycle:

  1. Register functions and schemas: Provide Python functions and their JSON schemas to the Gemini client.
  2. Send initial request: Make the first API call with the user's question and the tools.
  3. Detect & Extract: Check the response for function_call parts and pull out the function name and arguments.
  4. Execute: Call the actual Python functions with the provided arguments.
  5. Return Results: Format the function output as a function_response and send it back to Gemini.
  6. Obtain Final Answer: Receive Gemini's final response that incorporates the tool results.
Setting Up the Foundation

First, we define our Python functions and create a mapping dictionary. This dictionary is the bridge between the string name Gemini returns and the actual Python code.

import os
from google import genai
from google.genai import types

client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"])

# 1. Define Python functions
def sum_numbers(a, b):
    return a + b

# 2. Create the mapping dictionary
tools_mapping = {
    "sum_numbers": sum_numbers
}

# 3. Define the schema
sum_schema = {
    "name": "sum_numbers",
    "description": "Add two numbers.",
    "parameters": {
        "type": "object",
        "properties": {
            "a": {"type": "number"},
            "b": {"type": "number"}
        },
        "required": ["a", "b"]
    }
}
Detecting and Executing Tools

When you send a prompt to Gemini with tools enabled, the model returns a list of parts. If a part contains a function_call, you must execute the corresponding function.

# Initial request
messages = [{"role": "user", "parts": [{"text": "Calculate 15 + 27"}]}]
config = types.GenerateContentConfig(tools=[types.Tool(function_declarations=[sum_schema])])

response = client.models.generate_content(
    model="gemini-2.5-flash", contents=messages, config=config
)

# Detect and process function calls
if any(getattr(part, 'function_call', None) for part in response.candidates[0].content.parts):
    # Add Gemini's tool request to history
    messages.append({"role": "model", "parts": response.candidates[0].content.parts})
    
    for part in response.candidates[0].content.parts:
        if part.function_call:
            name = part.function_call.name
            args = part.function_call.args
            
            # Execute the function
            result = tools_mapping[name](**args)
            
            # Format as function_response
            messages.append({
                "role": "user",
                "parts": [{"function_response": {"name": name, "response": {"result": result}}}]
            })
Getting the Final Response

After adding the function results to your messages list, you must call Gemini one more time to generate a natural language answer.

Important: When extracting text from Gemini's final response, use a safe check. Because responses can contain mixed parts (text and function calls), calling .text directly on a response containing a function call will raise a ValueError.

# Send results back to Gemini
final_response = client.models.generate_content(
    model="gemini-2.5-flash", contents=messages, config=config
)

# Safe text extraction
for part in final_response.candidates[0].content.parts:
    if hasattr(part, 'text') and part.text:
        print(f"Gemini: {part.text}")
Complete Working Example

Here is the consolidated agent loop. This pattern handles the initial request, tool execution, and the final response in one flow.

import os
from google import genai
from google.genai import types

client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"])

# Functions and Schemas
def sum_numbers(a, b): return a + b
tools_mapping = {"sum_numbers": sum_numbers}
sum_schema = {
    "name": "sum_numbers",
    "description": "Add numbers.",
    "parameters": {
        "type": "object",
        "properties": {"a": {"type": "number"}, "b": {"type": "number"}},
        "required": ["a", "b"]
    }
}

# 1. Setup Request
messages = [{"role": "user", "parts": [{"text": "What is 15 + 27?"}]}]
config = types.GenerateContentConfig(tools=[types.Tool(function_declarations=[sum_schema])])

# 2. Initial Call
response = client.models.generate_content(model="gemini-2.5-flash", contents=messages, config=config)

# 3. Execution Loop
if any(getattr(part, 'function_call', None) for part in response.candidates[0].content.parts):
    messages.append({"role": "model", "parts": response.candidates[0].content.parts})
    
    for part in response.candidates[0].content.parts:
        if part.function_call:
            name = part.function_call.name
            result = tools_mapping[name](**part.function_call.args)
            
            messages.append({
                "role": "user", 
                "parts": [{"function_response": {"name": name, "response": {"result": result}}}]
            })
    
    # 4. Final Call with Results
    response = client.models.generate_content(model="gemini-2.5-flash", contents=messages, config=config)

# 5. Output Final Text Safely
for part in response.candidates[0].content.parts:
    if hasattr(part, 'text') and part.text:
        print(part.text)
Summary & Practice Preparation

You have now learned the canonical workflow for Gemini tool execution:

  1. Map function names to Python functions.
  2. Detect function_call parts in the model response.
  3. Append the model's request and your function_response to the message history.
  4. Recall the model to get a final natural language answer.
  5. Extract text safely using hasattr(part, 'text').

In the upcoming practices, you will implement this loop to handle multiple tools and error scenarios.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal