Build an AI Agent with Python and Gemini

Build a trip planner AI agent that autonomously calls tools using Gemini.

Introduction

30 Second Summary

You ask an AI "What's the weather in Tokyo next week?" and get a confident, detailed answer. The problem is the AI made the whole thing up, because it has no access to real weather data.

In this project, you will build an AI agent in Python that solves this problem by calling tools on its own. Your agent will check the weather, crunch budget numbers, and deliver a complete day trip plan.

What You'll Build

You'll type a travel question into your terminal, hit Enter, and watch your agent autonomously figure out which tools to call, execute them, and deliver a complete trip plan built on real data.

By the end of this project, you'll have:

  • A fully autonomous agent loop powered by Google Gemini that calls tools, reads results, and loops until it has a complete answer.
  • Tool functions for weather lookup and budget calculation that the agent discovers and calls without your help, replacing the hallucinated guesses a plain LLM would give.
  • A live reasoning trace in your terminal showing every tool call, every result, and every decision the agent makes.
  • Secret Mission: Add an attraction search tool and watch the agent chain three tools together to build a complete itinerary.

Are there any prerequisites?

You should be comfortable with Python basics like functions, loops, and dictionaries. Experience with LLM APIs is helpful but not required.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

You're going to build a Trip Planner AI Agent in Python that takes a travel question and figures out the answer on its own by calling tools, reading the results, and deciding what to do next. The agent uses a pattern called the ReAct loop (Reason, Act, Observe) to chain together weather lookups and budget calculations until it has a complete answer.

Set Up Python and the Gemini SDK

Your agent needs three things before it can start planning trips: a Python runtime to execute your code, the Google GenAI SDK to communicate with Gemini, and an API key that authorizes your requests.

In this step, you'll install Python, add the SDK, and grab a free API key from Google AI Studio. Once everything checks out, you'll be ready to start writing your agent.

In this step, get ready to:
  • Install Python 3.9 or newer.
  • Install the Google GenAI SDK.
  • Get a free Gemini API key from Google AI Studio.
Check your Python version

Python is the language your agent runs on. You need version 3.9 or newer for the Google GenAI SDK to work.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
  • Check your Python version by running this command:

macOS

python3 --version

Windows

python --version

✔️ I see version 3.9 or higher

Your Python version is ready. Move on to the next substep.

ⓧ I see an older version

You have Python installed, but the version is too old for the Google GenAI SDK.

  • Download Python from python.org/downloads.
  • Run the installer. On Windows, check the Add Python to PATH checkbox on the first screen before clicking Install Now.
  • Close and reopen your terminal after installation.
  • Run the version check command again to confirm you see 3.9 or higher.

macOS users with an older default Python

On macOS, the system python3 command may still point to the older version after you install a newer Python from python.org. Run the versioned binary matching what you downloaded (for example, python3.13 --version) to verify the new version. Then use that versioned command in place of python3 for the rest of this project.

ⓧ Command not found

Python is not installed on your machine yet.

  • Download Python from python.org/downloads.
  • Run the installer. On Windows, check the Add Python to PATH checkbox on the first screen before clicking Install Now.
  • Close and reopen your terminal after installation.
  • Run the version check command again to confirm you see a version number.

Still seeing command not found after installing?

On Windows, restart your computer to refresh the PATH. On macOS, try python3 instead of python.

Help me fix my Python PATH

Install the Google GenAI SDK

The Google GenAI SDK is the Python library that lets your code send requests to Gemini and receive responses.

  • Install the SDK by running this command:

macOS

pip3 install google-genai

Windows

pip install google-genai

You should see output ending with a line that includes Successfully installed followed by the package name and version.

Seeing a pip not found error?

On macOS, make sure you use pip3 instead of pip. On Windows, if pip is not found, try python -m pip install google-genai instead.

Help me fix my pip install

Get a free Gemini API key

Your API key is what authorizes your Python script to use the Gemini model. Google provides a free tier with no billing required.

  • Open Google AI Studio API Keys in your browser.
  • Sign in with your Google account if prompted.
  • Click Create API key.
  • Copy the key that appears and paste it somewhere safe, like a temporary text file on your Desktop. You will need it in the next step.

Keep your API key private

Your API key is a secret credential. Do not share it publicly, commit it to version control, or post it online. If you accidentally expose it, you can delete it from Google AI Studio and create a new one.

Having trouble creating a key?

If you see a permission error, try signing out of Google AI Studio and signing back in. Make sure you are using a personal Google account, not a managed workspace account with restricted permissions.

Help me fix my API key permissions

Verify the SDK is ready

Before you move on, confirm that Python can find and import the Google GenAI SDK. This one command tests that everything is wired up correctly.

Before you run this, what do you think the output will be if the SDK installed correctly?

  • Verify the SDK is importable by running this command:

macOS

python3 -c "from google import genai; print('google-genai ready')"

Windows

python -c "from google import genai; print('google-genai ready')"

You should see google-genai ready printed with no errors.

Seeing a ModuleNotFoundError?

This means the SDK was installed for a different Python version than the one you are running. On macOS, make sure you installed with pip3 and are running with python3. On Windows, make sure you used the same python and pip commands.

Help me fix my ModuleNotFoundError

Your environment is locked and loaded. Next up, you'll write your first script and see what happens when you ask Gemini a question it cannot possibly answer.

Ask Gemini Without Tools

Your Python environment is set up, the Gemini SDK is installed, and you have an API key ready to go. Now it is time to actually talk to the model.

Before building any agent machinery, there is a question worth testing. What happens when you ask an LLM a factual question it cannot possibly know the answer to? In this step, you will send Gemini a real-time question and find out.

In this step, get ready to:
  • Create agent.py and send a trip planning question to Gemini.
  • Run the script and observe how Gemini handles a question it cannot answer with its training data alone.
Set up your project folder

You need a dedicated folder to keep your project files organized. You will create one on your Desktop so it is easy to find.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir trip-planner-agent
cd trip-planner-agent
  • Confirm the folder was created by running:
pwd

You should see a path ending in trip-planner-agent.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type VS Code and press Enter to open it.
  • Click File in the top menu bar.
  • Click Open Folder.
  • Select the trip-planner-agent folder on your Desktop, then click Open.

VS Code opens with the trip-planner-agent folder loaded in the Explorer sidebar on the left.

Write the script and ask Gemini about weather

Now you will write a short Python script that sends Gemini a trip planning question. The question deliberately asks about real-time weather data, something no language model can actually look up.

  • In the Explorer sidebar, click the New File... button at the top of the file list.
  • Type agent.py and press Enter.
  • Add the following code to agent.py:
import os
from google import genai
from google.genai import types

# Set your API key as an environment variable
os.environ["GOOGLE_API_KEY"] = "your-api-key-here"

# Choose the Gemini model
MODEL = "gemini-2.0-flash"

# Create a Gemini client
client = genai.Client()

# Send a trip planning question that needs real-time data
response = client.models.generate_content(
    model=MODEL,
    contents="I'm planning a trip to Tokyo next week. What will the weather be like? Should I pack an umbrella or sunscreen?",
)

# Print Gemini's response
print(response.text)

What does this code do?

  • os.environ["GOOGLE_API_KEY"] sets your API key as an environment variable so the SDK can authenticate your requests.
  • genai.Client() creates a connection to the Gemini API. It automatically reads the API key from the environment variable you just set.
  • client.models.generate_content() sends your question to the gemini-2.0-flash model and returns a response.
  • The types import is not used yet. You will need it in the next step when you declare tools for the model.
  • Find the line that says your-api-key-here and replace it with the Gemini API key you copied from Google AI Studio.
  • Save agent.py.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete agent.py so far. Make sure yours matches this exactly (aside from your own API key).

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

os.environ["GOOGLE_API_KEY"] = "your-api-key-here"

MODEL = "gemini-2.0-flash"

client = genai.Client()

response = client.models.generate_content(
    model=MODEL,
    contents="I'm planning a trip to Tokyo next week. What will the weather be like? Should I pack an umbrella or sunscreen?",
)

print(response.text)
Run the script and read the response

Your script is ready. Before you run it, take a moment to think about what you expect Gemini to say.

You asked about next week's weather in Tokyo. Do you think Gemini actually knows what the weather will be like next week?

  • Open the terminal in VS Code by pressing Ctrl+` (backtick key, top-left of your keyboard).
  • Run your script by entering this command:
python agent.py

Read Gemini's response carefully. It gives you specific weather predictions for Tokyo, complete with temperatures, humidity levels, and packing advice. It sounds confident and helpful.

Here is the problem: every piece of weather data in that response is made up. Gemini has no access to real-time weather information. It generated a plausible-sounding forecast based on patterns in its training data, not actual weather data.

What is hallucination?

When an LLM produces confident, specific answers to questions it cannot actually answer, that is called hallucination. The model fills in gaps in its knowledge with plausible-sounding text instead of admitting it does not have the information.

This is the core limitation that AI agents exist to solve. By giving an LLM access to tools, you let it fetch real data instead of guessing.

Seeing an error instead of a response?

  • If you see a message about an invalid API key, double check that you replaced your-api-key-here with the actual key you copied from Google AI Studio. Make sure there are no extra spaces.
  • If you see ModuleNotFoundError, the google-genai SDK is not installed in the Python environment your terminal is using. Run pip install google-genai and try again.
  • On macOS, if python is not found, try python3 agent.py instead.

Still stuck? Help me debug why my agent.py script is failing to run.

You have just seen the core limitation that every AI agent is built to solve. Gemini sounds helpful, but it is making things up because it has no way to look up real data. In the next step, you will give Gemini a tool and watch how that changes everything.

Give Gemini a Tool

In the last step, you asked Gemini about Tokyo's weather and it responded with confident, specific predictions. The problem is that those predictions were completely fabricated. The model has no access to real-time weather data, so it made numbers up.

The fix is to give the model a tool it can call when it needs real data. In this step, you will walk through the complete function calling cycle by hand. The model requests a tool call, you execute it, and you send the result back. This three-step handshake is the single most important pattern in all of AI agents.

In this step, get ready to:
  • Define a weather tool function and declare it for Gemini.
  • Send a weather question with the tool attached and extract the function call.
  • Send the tool result back and receive a grounded answer.
Define a weather function and declare it as a tool

Before Gemini can call a tool, two things need to exist. First, a Python function that does the actual work. Second, a FunctionDeclaration that describes the function to the model so it knows when and how to use it.

  • In agent.py, find the import os line at the top of the file.
  • Add import json on a new line right after it. You will use this later to print tool call arguments.
  • Then find the MODEL = "gemini-2.0-flash" line. Below it, add the get_weather function by copying the following code:
def get_weather(city: str) -> str:
    """Get the current weather forecast for a city."""
    weather_data = {
        "tokyo": "Sunny, 28°C (82°F), humidity 65%, light breeze from the south",
        "paris": "Partly cloudy, 22°C (72°F), humidity 55%, calm winds",
        "new york": "Rainy, 18°C (64°F), humidity 80%, moderate northeast wind",
        "london": "Overcast, 16°C (61°F), humidity 75%, light rain expected",
        "sydney": "Clear skies, 20°C (68°F), humidity 45%, mild westerly breeze",
    }
    city_lower = city.lower().strip()
    for key, value in weather_data.items():
        if key in city_lower:
            return value
    return f"Weather data not available for {city}. General forecast: Mild, around 20°C (68°F)."

What does this code do?

This function simulates a weather API. The weather_data dictionary maps city names to weather strings.

When called, the function lowercases the input and checks if any key matches. If no city is found, it returns a generic fallback. Using simulated data keeps the project free and avoids needing a separate weather API key.

  • Save agent.py.

Now Gemini needs to know this function exists. You tell it by creating a tool declaration that describes the function's name, purpose, and expected parameters.

  • Below the get_weather function, add the tool declaration by copying the following code:
tool_declarations = types.Tool(
    function_declarations=[
        types.FunctionDeclaration(
            name="get_weather",
            description="Get the current weather forecast for a city. Use this when you need weather information for travel planning or activity recommendations.",
            parameters_json_schema={
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "The name of the city, e.g. Tokyo, Paris, New York",
                    }
                },
                "required": ["city"],
            },
        ),
    ]
)

What is a FunctionDeclaration?

A FunctionDeclaration is a JSON schema that teaches the model what a tool does. The model never runs your Python function directly. Instead, it reads this declaration and decides whether to request a call.

The description field is critical. It tells the model when to use the tool. The parameters_json_schema field defines what arguments the model should pass. A clear description means the model picks the right tool at the right time.

  • Save agent.py and run it to confirm there are no syntax errors:
python agent.py

The script still prints the old hallucinated response. That is expected because you have not connected the tool to the API call yet.

Seeing a syntax error?

  • Check that the weather_data dictionary has matching braces and each entry ends with a comma.
  • Make sure the tool_declarations block has matching parentheses. Every opening ( and [ needs a closing counterpart.
  • Confirm import json is on its own line right after import os.

Still stuck? Help me fix a syntax error in my tool declaration code.

Send a weather question with the tool attached

The tool is declared but not attached to any API call yet. You need to replace the old code that sends a plain question with new code that includes the tool. This time, instead of answering directly, the model will return a function call request.

  • In agent.py, find this code at the bottom of the file:
client = genai.Client()

response = client.models.generate_content(
    model=MODEL,
    contents="I'm planning a trip to Tokyo next week. What will the weather be like? Should I pack an umbrella or sunscreen?",
)

print(response.text)
  • Select all of that code and delete it.
  • In its place, paste the following code that sends the question with the tool attached and extracts the function call:
# Create the client and attach our tool
client = genai.Client()
config = types.GenerateContentConfig(tools=[tool_declarations])

# Build the conversation as a list of Content objects
contents = [
    types.Content(
        role="user",
        parts=[types.Part.from_text(text="I'm planning a trip to Tokyo next week. What will the weather be like?")],
    )
]

# Send the request with the tool available
response = client.models.generate_content(
    model=MODEL,
    contents=contents,
    config=config,
)

# Check if Gemini requested a function call instead of answering directly
if response.function_calls:
    fc = response.function_calls[0]
    func_name = fc.name
    func_args = fc.args or {}
    print(f"Tool call: {func_name}({json.dumps(func_args)})")

    result = get_weather(**func_args)
    print(f"Result: {result}")
else:
    print(response.text)

What changed from before?

  • The config parameter passes tool_declarations to GenerateContentConfig. This tells Gemini the weather tool exists and is available to call.
  • The contents parameter is now a list of Content objects instead of a plain string. This structured format lets you build a multi-turn conversation later.
  • The response.function_calls check determines whether Gemini answered directly or requested a tool call. If it requested a call, you extract the function name and arguments, then run get_weather yourself.

Before you run this, think about what Gemini will do. Will it answer the weather question directly like last time, or will it do something different now that it has a tool?

  • Save agent.py and run it:
python agent.py

Instead of answering directly, Gemini requests a tool call. You should see two lines printed in your terminal:

Tool call: get_weather({"city": "Tokyo"})
Result: Sunny, 28°C (82°F), humidity 65%, light breeze from the south

Gemini recognized the question needs weather data and asked you to call get_weather with city: Tokyo. Your code executed the function and printed the result. But Gemini itself has not seen this result yet. Right now the conversation is stuck halfway through.

Seeing the old hallucinated response instead?

  • Make sure you deleted all the old code (the original client = genai.Client(), response = ..., and print(response.text) block). If both the old and new code exist, the old code runs first.
  • Confirm that config=config is included in the generate_content call. Without it, Gemini has no tools and answers directly.
  • If you see an API error, double-check that your GOOGLE_API_KEY is still set correctly at the top of the file.

Still stuck? Help me figure out why Gemini is not requesting a tool call.

Send the tool result back to Gemini

You have executed the tool call and have the weather data. But Gemini does not know the result yet. To complete the cycle, you need to add the model's tool call request and your tool response to the conversation history, then call Gemini again.

  • In agent.py, find the line print(f"Result: {result}") inside the if response.function_calls: block.
  • Below that line, still inside the if block, add the following code to send the result back:
    # Add the model's tool call to conversation history
    contents.append(response.candidates[0].content)
    # Add our tool response to conversation history
    contents.append(
        types.Content(
            role="tool",
            parts=[
                types.Part.from_function_response(
                    name=func_name,
                    response={"result": result},
                )
            ],
        )
    )

    # Call Gemini again with the full conversation
    final_response = client.models.generate_content(
        model=MODEL,
        contents=contents,
        config=config,
    )
    print(f"Final answer: {final_response.text}")

Why do you need to send the result back?

Gemini does not execute functions itself. It only requests that you run them. After you run get_weather, you need to send the result back so Gemini can use it to write a final answer.

The conversation history now has three parts: the user's question, the model's tool call request (response.candidates[0].content), and your tool response (a Content with role="tool"). Gemini reads all three and produces a grounded answer.

What do you think will happen now that Gemini has both the question and the real weather data? Will the answer be different from the hallucinated one in Step 2?

  • Save agent.py and run it:
python agent.py

Your terminal now prints three things in sequence: the tool call Gemini requested, the result your function returned, and Gemini's final answer using the real weather data.

Tool call: get_weather({"city": "Tokyo"})
Result: Sunny, 28°C (82°F), humidity 65%, light breeze from the south
Final answer: Based on the weather forecast, Tokyo next week will be sunny...

Compare this to Step 2

In Step 2, Gemini made up weather data because it had no tools. Now, the temperature, humidity, and conditions all come from your get_weather function. The answer is grounded in real data instead of hallucinated. This three-step cycle (model requests a call, you execute it, you send the result back) is exactly how every AI agent framework works under the hood.

Seeing an error about contents or role?

  • Make sure the two contents.append lines are indented inside the if response.function_calls: block, at the same level as fc = response.function_calls[0].
  • Check that role="tool" is spelled exactly. Any other role name will cause an API error.
  • Confirm you are passing name=func_name to from_function_response. The function name must match what Gemini requested.

Still stuck? Help me debug the tool response I'm sending back to Gemini.

✔️ Awesome, I've got everything!

Great. Double check you have saved agent.py.

ⓧ I'd like to double check the full code

Your agent.py should contain these sections in order. Scroll up and confirm each one is present:

  • Imports: os, json, genai, types.
  • API key and MODEL constant.
  • The get_weather function with a weather_data dictionary for five cities.
  • The tool_declarations variable wrapping a FunctionDeclaration for get_weather.
  • Client setup with config = types.GenerateContentConfig(tools=[tool_declarations]).
  • A contents list with a user message, followed by generate_content.
  • An if response.function_calls: block that extracts the call, runs get_weather, appends to contents, calls Gemini again, and prints the final answer.
  • An else branch that prints response.text as a fallback.

You have just hand-wired the complete function calling cycle that powers every AI agent. The model requests a tool call, your code executes it, and the result goes back for a grounded answer. Next up, you will wrap this cycle in a loop so the agent can call multiple tools on its own.

Build the Agent Loop

In the last step, you manually handled a single tool call. You extracted the function call from Gemini's response, executed it, and sent the result back. That works for one tool, but real questions need more.

A trip budget needs weather data AND math calculations. The agent loop automates the whole cycle: call Gemini, execute whatever tools it asks for, feed results back, and repeat until it produces a final text answer with no more tool calls.

In this step, get ready to:
  • Add a second tool and a tool registry that maps names to functions.
  • Build the agent loop that calls Gemini, executes tools, and repeats.
  • Run the agent on a complex query and watch it chain multiple tools.
Add the calculate tool and build a tool registry

Your agent needs a second tool. A math calculator lets the agent compute budgets and convert currencies. You also need a tool registry that maps function names to Python functions, so the agent loop can call any tool by name.

  • In agent.py, find the closing line of the get_weather function. Add the following code below it, above the tool_declarations block:
def calculate(expression: str) -> str:
    """Evaluate a mathematical expression and return the result."""
    try:
        allowed_chars = set("0123456789+-*/.() ")
        if not all(c in allowed_chars for c in expression):
            return "Error: Invalid characters. Only numbers and +-*/.() are allowed."
        result = eval(expression)
        return str(round(result, 2))
    except Exception as e:
        return f"Error calculating '{expression}': {e}"


# ---- Tool Registry ----

TOOLS = {
    "get_weather": get_weather,
    "calculate": calculate,
}

What does this code do?

  • calculate takes a math expression as a string, checks it for safe characters, evaluates it, and returns the result.
  • The character allowlist blocks code injection by only permitting digits and basic math operators. In production you would use a sandboxed evaluator, but for a local learning project this is a practical safeguard.
  • TOOLS is a dictionary that maps function names (as strings) to their actual Python functions. The agent loop uses this dictionary to look up and call the right function by name.
  • Save agent.py.
  • Test the calculate function by running this command:
python -c "from agent import calculate; print(calculate('1500 * 3'))"

You should see 4500 in the output.

Seeing an import error?

  • Make sure the calculate function is at the top level of agent.py (not indented inside another function).
  • Check that TOOLS is placed below both get_weather and calculate. If it comes before either function, Python can't find them yet.

Help me debug my calculate function import error

Now Gemini needs to know the calculate tool exists. You do this by adding a second FunctionDeclaration to the tool_declarations block.

  • In agent.py, find the tool_declarations block. Inside the function_declarations list, find the closing ), of the get_weather FunctionDeclaration.
  • Add this new declaration right after it, still inside the same list:
        types.FunctionDeclaration(
            name="calculate",
            description="Evaluate a mathematical expression. Use this for budget calculations, currency conversions, or any arithmetic.",
            parameters_json_schema={
                "type": "object",
                "properties": {
                    "expression": {
                        "type": "string",
                        "description": "A math expression to evaluate, e.g. '1500 * 3' or '4500 / 150'",
                    }
                },
                "required": ["expression"],
            },
        ),

What does this declaration do?

This tells Gemini that a calculate tool exists and describes when to use it. The description field is how the model decides whether this tool is the right one for a given question.

parameters_json_schema tells Gemini what arguments the tool expects. The model reads this schema to construct valid function call requests.

  • Save agent.py.
  • Verify the updated declarations work by running the script one more time:
python agent.py

You should see the same weather result as before. The old inline code still handles the get_weather call, and the new calculate declaration does not break anything.

Getting a syntax error?

  • Check that the new FunctionDeclaration ends with ), (closing parenthesis plus comma). The comma separates it from the previous declaration.
  • Make sure the new declaration is inside the function_declarations=[...] list, not outside it.

Help me fix my tool_declarations syntax error

Replace the inline code with the agent loop

The old inline script at the bottom of your file handles exactly one tool call, then stops. The agent loop replaces it with a while loop that keeps calling Gemini, executing any tool calls it requests, and feeding results back until the model produces a text answer with no more tool calls.

  • In agent.py, delete everything from client = genai.Client() all the way to the end of the file.
  • Paste the following code after the tool_declarations block. This is the first part of the run_agent function:
# ---- Agent Loop ----

def run_agent(user_query: str, max_turns: int = 10) -> str:
    """Run the AI agent loop until Gemini produces a final answer or hits max_turns."""

    client = genai.Client()

    config = types.GenerateContentConfig(tools=[tool_declarations])

    print(f"\n{'='*60}")
    print(f"Agent task: {user_query}")
    print(f"{'='*60}")

    contents = [
        types.Content(
            role="user",
            parts=[types.Part.from_text(text=user_query)],
        )
    ]

    turn = 0
    while turn < max_turns:
        turn += 1
        print(f"\n--- Turn {turn} ---")

        response = client.models.generate_content(
            model=MODEL,
            contents=contents,
            config=config,
        )

What does this code do?

  • run_agent takes a user query and a max_turns safety limit (default 10). The safety limit prevents infinite loops if the agent keeps calling tools forever.
  • The contents list starts with the user's question, just like the old inline code. Each turn, the loop calls Gemini and prints the turn number so you can follow the agent's reasoning.
  • Save agent.py and verify the function loads by running:
python -c "from agent import run_agent; print('run_agent loaded')"

You should see run_agent loaded printed. This confirms the function definition has no syntax errors.

The function is incomplete. It calls Gemini but does not handle the response yet. The next two code blocks complete it.

  • Directly below the response = client.models.generate_content(...) line, still inside the while loop, add the tool call handling code:
        if response.function_calls:
            model_content = response.candidates[0].content
            contents.append(model_content)

            function_response_parts = []
            for fc in response.function_calls:
                func_name = fc.name
                func_args = fc.args or {}

                print(f"  Tool call: {func_name}({json.dumps(func_args)})")

                if func_name in TOOLS:
                    result = TOOLS[func_name](**func_args)
                else:
                    result = f"Error: Unknown tool '{func_name}'"

                print(f"  Result:    {result}")

                function_response_parts.append(
                    types.Part.from_function_response(
                        name=func_name,
                        response={"result": result},
                    )
                )

            contents.append(
                types.Content(role="tool", parts=function_response_parts)
            )

What does this code do?

This is the core of the ReAct loop. When Gemini returns function calls instead of text, the agent acts on them.

  • It saves the model's response to the conversation history so Gemini remembers what it asked for.
  • For each function call, it looks up the function in TOOLS, calls it with the arguments Gemini provided, and prints the trace.
  • All function results are collected and sent back to Gemini as a single tool response. The loop then continues, and Gemini can call more tools or give a final answer.
Run the agent with a complex query

The last piece is the exit condition. When Gemini responds with plain text and no function calls, the agent has its final answer.

  • Still inside the while loop, add the else branch right after the tool-handling if block. Then close the function with the safety limit, and add the __main__ block at the bottom of the file:
        else:
            final_answer = response.text
            print(f"\n{'='*60}")
            print(f"FINAL ANSWER:\n")
            print(final_answer)
            print(f"\n{'='*60}")
            print(f"Completed in {turn} turn(s)")
            return final_answer

    print(f"\nAgent stopped after {max_turns} turns (safety limit reached)")
    return "I could not complete the task within the allowed number of steps."


# ---- Main ----

if __name__ == "__main__":
    query = (
        "I'm planning a day trip to Tokyo next week with 2 friends (3 people total). "
        "What's the weather going to be like? "
        "Can you estimate our total budget if lunch costs 1500 yen per person, "
        "entrance fees are 800 yen per person for 2 attractions, "
        "and the exchange rate is 150 yen per dollar?"
    )
    run_agent(query)

What does this code do?

  • The else branch fires when Gemini's response has no function calls. That means the model has enough information and is ready with a final answer.
  • If the loop exceeds max_turns, the safety limit stops the agent. This prevents runaway loops where the model keeps calling tools without converging on an answer.
  • The query in __main__ is deliberately complex. It requires weather data for Tokyo AND multiple budget calculations, forcing the agent to chain both tools.
  • Save agent.py.

Before you run this, think about what you expect to see. The query asks for weather AND a budget breakdown. Will the agent call just one tool, or will it need multiple tools across multiple turns?

  • Run the agent by executing:
python agent.py

Watch your terminal. The agent calls get_weather for Tokyo, then calculate for the budget math (lunch costs, entrance fees, currency conversion). It chains these tool results together across multiple turns and produces a complete trip plan with accurate numbers.

What is the ReAct pattern?

What you just watched is the ReAct loop: Reason, Act, Observe. The model reasons about what it needs, acts by requesting a tool call, observes the result, then reasons again about what to do next.

Every agent framework (LangChain, CrewAI, OpenAI Agents SDK) runs this same loop under the hood. You just built it from scratch.

Agent stopped after 10 turns?

  • Check the indentation of your if response.function_calls: block. It must be indented inside the while loop (two levels in from the function def).
  • Make sure the else branch is at the same indentation level as the if. If the else is misaligned, the function never returns the final answer and hits the safety limit.
  • Verify that TOOLS contains both "get_weather" and "calculate" as keys. A missing key causes the "Unknown tool" error branch to fire, and Gemini keeps retrying.

Help me debug my agent loop that keeps hitting the max_turns limit

✔️ Awesome, I've got everything!

Great. Double check you have saved agent.py.

ⓧ I'd like to double check the full code

Your agent.py should contain these sections in order. Scroll through your file and confirm each one is present:

  • Imports: import os, import json, from google import genai, from google.genai import types.
  • Configuration: os.environ["GOOGLE_API_KEY"] and MODEL = "gemini-2.0-flash".
  • Tool Functions: get_weather(city) and calculate(expression).
  • Tool Registry: TOOLS dictionary mapping both function names to their Python functions.
  • Tool Declarations: tool_declarations with two FunctionDeclaration entries (get_weather and calculate).
  • Agent Loop: run_agent(user_query, max_turns=10) function with the while loop, tool call handling, final answer branch, and safety limit.
  • Main: if __name__ == "__main__": block with the complex Tokyo day trip query calling run_agent(query).

Your agent is fully operational. It calls tools, chains results, and reasons its way to a final answer. Next up, you will push it further with a Secret Mission that adds a brand new tool and proves the architecture works with any number of tools.

Secret mission

Add an Attraction Search Tool

Your agent handles two tools. Can it handle three? Add an attraction search tool and watch the agent discover and chain it with weather and budget calculations to build a complete itinerary. The best part: the agent loop code stays exactly the same.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, and the Gemini API free tier has no charges. There are no ongoing costs to worry about. Decide whether to keep your resources, pause them to come back later, or delete them entirely.

Resources you used:

  • agent.py file on your Desktop.
  • google-genai Python package.
  • Gemini API key in Google AI Studio.

Shared your API key by accident?

If you pasted your API key into a chat, a public repo, or anywhere visible, rotate it immediately. Visit https://aistudio.google.com/apikey, delete the compromised key, and create a new one.

Keep everything running

No action needed. Choose this if you want to keep experimenting with your agent or add more tools.

  • Your agent.py file stays on your Desktop, ready to run anytime.
  • Your Gemini API key remains active at no cost. The free tier has rate limits but no charges.
  • The google-genai package stays installed and available for future Python projects.

Pause - I'll come back to this later

There are no running processes or paid services to shut down. Your files and API key will be waiting when you come back.

  • Close your code editor if you have it open.
  • Your Gemini API key stays active at no cost. You can pick up right where you left off.

Delete - I don't want to use this again

Remove your project file, uninstall the SDK, and delete your API key.

  • Delete your agent.py file from your Desktop.

macOS

cd ~/Desktop
rm agent.py

Windows

cd ~\Desktop
del agent.py
  • Uninstall the Google GenAI SDK by running:
pip uninstall google-genai

Enter y when prompted to confirm the uninstall.

Nice Work!

Nice Work!

You just built a fully autonomous AI agent from scratch in Python. Your trip planner agent decides which tools to call, executes them, and loops until it has a complete answer.

You've learned how to:

  • Build an AI agent loop that autonomously reasons, calls tools, observes results, and repeats until it produces a final answer.
  • Use function calling to teach an LLM what tools exist through tool declarations, and handle the full request-execute-respond cycle that grounds the model's answers in real data.
  • Diagnose LLM hallucination firsthand and solve it by giving the model access to tools instead of letting it guess.
  • Secret Mission: Added a new attraction search tool and watched the agent discover and chain all three tools together without any changes to the loop.

Ready to quiz yourself?