Build an AI Code Analyzer Agent
Build a Python AI agent that reads and analyzes code files using Gemini.
Introduction
30 Second Summary
Every developer has looked at an unfamiliar codebase and thought "what does this code actually do?" The usual answer involves hours of reading through files, tracing function calls, and piecing together how everything connects.
In this project, you will build a command-line AI agent that can read any code folder on your computer and answer natural language questions about it. You will use Python and the Gemini API to create an agent that autonomously decides which files to read, what to search for, and how to answer your questions.
What You'll Build
You point the agent at a code folder, type a question like "What does the main function do?", and watch it list the files, read the relevant ones, and come back with an intelligent answer based on real code.
By the end of this project, you'll have:
- An interactive AI agent that reads your local code files and answers natural language questions about them.
- Three tools the agent can call on its own: listing files in a directory, reading a file's contents, and searching for a specific term across an entire project.
- A transparent decision-making view that shows you exactly which tools the AI picks and what results it gets, in real time.
- Secret Mission: Add a "count lines of code" tool that generates project statistics broken down by file type.
Are there any prerequisites?
None. This guide includes full setup for every tool you need, including Python, VS Code, and your free Gemini API key.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
Set Up Python and Your Gemini API Key
Before your agent can analyze a single line of code, it needs two things: a programming language to run in, and an AI model to think with.
In this step, you will install Python, get a free API key from Google AI Studio, and set up your project folder with the Gemini API SDK.
In this step, get ready to:
- Install and verify Python 3.10 or higher.
- Get a free Gemini API key from Google AI Studio.
- Create the project folder, virtual environment, and install the SDK.
Check if Python is installed
The Gemini SDK requires Python 3.10 or higher. Let's check if you already have it.
- Press Cmd+Space to open Spotlight.
- Type Terminal and press Enter to open it.
- Check your Python version by running this command:
python3 --version
Look at the output in your terminal.
✔️ I see version 3.10 or higher
Your Python version is 3.10 or above. You are all set to continue.
ⓧ I see an older version
Your Mac may have an older version of Python installed by default. You need 3.10 or higher for this project.
- Go to python.org/downloads in your browser.
- Click Download Python to get the latest macOS installer.
- Open the downloaded .pkg file and follow the installer prompts.
- After installation completes, close and reopen Terminal.
- Run python3 --version again to confirm it now shows 3.10 or higher.
Still seeing the old version?
The python.org installer sometimes requires you to close all Terminal windows and reopen a fresh one before the new version appears. If it still shows an older version, try restarting your Mac.
Help me get the right Python version on my Mac.
ⓧ Command not found
Python is not installed on your Mac yet. Let's install it.
- Go to python.org/downloads in your browser.
- Click Download Python to get the latest macOS installer.
- Open the downloaded .pkg file and follow the installer prompts.
- After installation completes, close and reopen Terminal.
- Run python3 --version again to confirm it shows 3.10 or higher.
Still seeing command not found?
Make sure you closed and reopened Terminal after the installer finished. If the issue persists, try restarting your Mac so the new PATH takes effect.
Help me install Python on macOS.
Get your free Gemini API key
Your agent will send questions to the Gemini AI model through an API. To authenticate those requests, you need an API key.
- Open aistudio.google.com in your browser.
- Sign in with your Google account.
- Click Get API key in the left sidebar.
- Click Create API key.
- Copy the generated key and paste it somewhere safe (you will need it in a moment).
What is an API key?
An API key is a unique string that identifies your requests to a service. Think of it like a library card. The library (Gemini) checks your card each time you ask a question to confirm you have access.
The Gemini API free tier does not require a credit card or billing account. Your key is ready to use immediately.
Having trouble creating a key?
If you see a permission error, Google AI Studio may need to create a default project for your account. Accept the Terms of Service when prompted and try again.
Help me get a Gemini API key.
Create your project and install dependencies
Now you will create a project folder, set up an isolated virtual environment, and install the Gemini SDK.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir code-analyzer-agent
cd code-analyzer-agent
- Confirm you are in the right folder by running:
pwd
You should see a path ending in Desktop/code-analyzer-agent.
- Create and activate a virtual environment by running these commands:
python3 -m venv venv
source venv/bin/activate
Your terminal prompt now starts with (venv). This confirms the virtual environment is active.
What is a virtual environment?
A virtual environment is an isolated space where Python packages are installed separately from your system Python. This prevents version conflicts between different projects on your computer.
- Install the Gemini SDK by running:
pip install google-genai==2.19.0
You should see output ending with a line that confirms the install succeeded.
Seeing a pip error?
Make sure your terminal prompt shows (venv) at the beginning. If it does not, run source venv/bin/activate and try the install again.
Help me install the Gemini SDK.
- Create your .env file by running this command, replacing your-api-key-here with the actual API key you copied from Google AI Studio:
echo "GOOGLE_API_KEY=your-api-key-here" > .env
- Confirm the file was created by running:
cat .env
You should see GOOGLE_API_KEY= followed by your API key.
Why a .env file?
Storing your API key in a .env file keeps it out of your code. This is a common practice that prevents you from accidentally sharing secrets when you push code to a repository.
Before you run the verification command below, what do you think will happen? Will Python be able to import the Gemini SDK?
- Verify everything works by running:
python3 -c "from google import genai; print('SDK ready')"
You should see SDK ready printed in your terminal.
Seeing ModuleNotFoundError?
This means the SDK is not installed in the active environment. Check that your prompt starts with (venv). If it does not, activate the virtual environment with source venv/bin/activate and run pip install google-genai==2.19.0 again.
Help me fix this import error.
✔️ Awesome, I've got everything!
Great. Double check you have SDK ready in your terminal output, then move on.
ⓧ I'd like to double check the full code
Your .env file should contain exactly one line:
GOOGLE_API_KEY=your-api-key-here
Replace your-api-key-here with your actual Gemini API key.
Your environment is ready. Next up, you will make your first call to the Gemini AI and discover its biggest limitation.
Make Your First AI Call and Hit the Wall
Your Python environment is set up, the SDK is installed, and your API key is stored safely. Time to make your first call to the AI.
Before building a full agent, you need to see what the Gemini API can do on its own. Asking it about your local files will reveal a core limitation: the AI has no way to access your filesystem.
In this step, get ready to:
- Send your first prompt to the Gemini API.
- Discover what happens when the AI has no access to your local files.
Create agent.py and connect to Gemini
Your agent needs a connection to the Gemini API before it can do anything. You'll create a Python file that loads your API key from the .env file and sets up a client.
- In VS Code, create a new file by pressing Cmd+N (macOS) or Ctrl+N (Windows).
- Save it as agent.py by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Make sure it saves inside your code-analyzer-agent folder.
- Set up the Gemini connection by pasting this code into agent.py:
import os
import json
from google import genai
from google.genai import types
# Load the API key from the .env file
def load_api_key():
with open(".env") as f:
for line in f:
if line.startswith("GOOGLE_API_KEY="):
return line.strip().split("=", 1)[1]
raise ValueError("GOOGLE_API_KEY not found in .env file")
# Set up the Gemini client
API_KEY = load_api_key()
client = genai.Client(api_key=API_KEY)
MODEL = "gemini-2.5-flash"
What does this code do?
- load_api_key() reads your .env file line by line, finds the line starting with GOOGLE_API_KEY=, and extracts the key value after the equals sign.
- genai.Client(api_key=API_KEY) creates a connection to Google's Gemini API using your key.
- MODEL stores which AI model to use. gemini-2.5-flash is a fast, free-tier model perfect for this project.
- The os, json, and types imports are not used yet. You will need them in later steps when you add tools to the agent.
- Save agent.py and confirm it appears in the VS Code Explorer sidebar on the left.
Ask the AI about your local files
Now let's put the connection to work. You'll send the AI a simple question about the files in your current directory and see what it comes back with.
- Add the following code to the bottom of agent.py, below the MODEL line:
# Ask the AI about files in the current directory
response = client.models.generate_content(
model=MODEL,
contents="What files are in the current directory?"
)
print(response.text)
What does this code do?
- client.models.generate_content() sends your prompt to the Gemini API and waits for a response.
- contents is the question you are asking the AI. Here, you are asking it to list the files in your current directory.
- response.text extracts just the text portion of the AI's reply so you can print it.
- Save agent.py.
Before you run this, think about it: you are asking the AI what files are in your directory. The AI is running on Google's servers. Does it have any way to actually check what is on your computer?
- Run the script in your terminal by running this command:
python3 agent.py
Seeing an error instead of an AI response?
- If you see a message about No module named 'google', your virtual environment is not activated. Run source venv/bin/activate from your code-analyzer-agent folder and try again.
- If you see a message about GOOGLE_API_KEY not found, check that your .env file is in the same folder as agent.py and contains GOOGLE_API_KEY= followed by your actual key.
- If you see a permission or authentication error, your API key may be incorrect or expired. Double-check it in Google AI Studio.
- Help me debug my agent.py error
The AI responds, but look closely at its answer. It does not list agent.py or .env by name. Instead, it gives a generic explanation of how to list files, or it guesses common filenames that do not exist in your project.
Why can't the AI see my files?
Large language models like Gemini run on remote servers. They process the text you send them, but they cannot reach out and interact with your computer. The AI has no way to open a directory, read a file, or check what's on your filesystem.
To bridge this gap, you need to give the AI tools: Python functions it can ask you to run on its behalf. That is exactly what you will build in the next step.
✔️ Awesome, I've got everything!
Great. Make sure you have saved agent.py before moving on.
ⓧ I'd like to double check the full code
Here is the complete agent.py file. Compare it against yours and fix any differences.
import os
import json
from google import genai
from google.genai import types
# Load the API key from the .env file
def load_api_key():
with open(".env") as f:
for line in f:
if line.startswith("GOOGLE_API_KEY="):
return line.strip().split("=", 1)[1]
raise ValueError("GOOGLE_API_KEY not found in .env file")
# Set up the Gemini client
API_KEY = load_api_key()
client = genai.Client(api_key=API_KEY)
MODEL = "gemini-2.5-flash"
# Ask the AI about files in the current directory
response = client.models.generate_content(
model=MODEL,
contents="What files are in the current directory?"
)
print(response.text)
The AI is smart but blind to your local environment. In the next step, you will give it eyes by defining Python functions it can call to list, read, and search your actual files.
Give the AI Tools It Can Call
In the last step, you saw the AI try to answer a question about your local files. It could not do it. The AI has no way to open folders or read code on your computer.
The fix is to give the AI tools. Tools are Python functions that the AI can ask your code to run on its behalf. You will write three tools that let the AI list files, read file contents, and search for text across a project. Then you will register them with the Gemini API so the AI knows they exist.
In this step, get ready to:
- Write three tool functions that interact with your file system.
- Register the tools with the Gemini API using tool declarations.
- Test that the AI calls your tools to answer a real question.
Write the list_files tool function
Your first tool lets the AI see what files exist in a directory. Before writing it, you will also add some color constants that the project uses later for console output.
- In agent.py, find the line that says MODEL = "gemini-2.5-flash". Delete everything below that line (the old response and print lines).
- Add the following code below MODEL = "gemini-2.5-flash":
# --- ANSI Colors for Console Output ---
YELLOW = "\033[93m"
CYAN = "\033[96m"
GREEN = "\033[92m"
RED = "\033[91m"
RESET = "\033[0m"
BOLD = "\033[1m"
# --- Tool Functions ---
def list_files(directory: str) -> dict:
"""Lists all files in a directory and its subdirectories.
Args:
directory: The path to the directory to list files from.
Returns:
A dictionary with the list of file paths found.
"""
try:
file_list = []
for root, dirs, files in os.walk(directory):
# Skip hidden directories and virtual environments
dirs[:] = [d for d in dirs if not d.startswith('.') and d != 'venv' and d != '__pycache__']
for file in files:
if not file.startswith('.'):
file_path = os.path.relpath(os.path.join(root, file), directory)
file_list.append(file_path)
return {"files": file_list, "count": len(file_list)}
except FileNotFoundError:
return {"error": f"Directory not found: {directory}"}
What does this code do?
The ANSI color constants (YELLOW, CYAN, etc.) are special escape codes that make terminal text appear in color. You will use them in a later step to make the agent's output easier to read.
The list_files function uses os.walk to traverse a directory tree. It skips hidden folders, venv, and __pycache__ so the AI only sees your actual project files. It returns a dictionary with the file list and a count.
- Save agent.py.
- Test the function by temporarily adding this line at the bottom of the file:
print(list_files("."))
- Save and run the script by running this command:
python3 agent.py
You should see a dictionary with agent.py listed in the output. This proves your function can actually read the file system.
- Delete the print(list_files(".")) test line you just added.
Not seeing any files?
- Make sure you saved agent.py before running. Unsaved changes will not execute.
- Check that the os import is still at the top of your file. The list_files function needs it.
- Make sure your virtual environment is still active. You should see (venv) at the start of your terminal prompt.
Still stuck? Help me debug why list_files is not returning any files when I run agent.py.
Write the read_file and search_in_files functions
The AI also needs to read what is inside a file and search for specific terms. These two functions round out the agent's toolkit.
- In agent.py, add the read_file function directly below your list_files function:
def read_file(file_path: str) -> dict:
"""Reads the contents of a file and returns it as text.
Args:
file_path: The path to the file to read.
Returns:
A dictionary with the file contents or an error message.
"""
try:
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
content = f.read()
return {"file_path": file_path, "content": content[:5000]}
except FileNotFoundError:
return {"error": f"File not found: {file_path}"}
except PermissionError:
return {"error": f"Permission denied: {file_path}"}
What does this code do?
The read_file function opens a file and returns its text content. The content[:5000] slice caps the output at 5000 characters so that large files do not burn through your free API token budget.
- Add the search_in_files function directly below read_file:
def search_in_files(directory: str, search_term: str) -> dict:
"""Searches for a term across all files in a directory.
Args:
directory: The path to the directory to search in.
search_term: The text to search for in files.
Returns:
A dictionary with matching lines and their file locations.
"""
matches = []
try:
for root, dirs, files in os.walk(directory):
dirs[:] = [d for d in dirs if not d.startswith('.') and d != 'venv' and d != '__pycache__']
for file in files:
if file.startswith('.'):
continue
file_path = os.path.join(root, file)
try:
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
for line_num, line in enumerate(f, 1):
if search_term.lower() in line.lower():
matches.append({
"file": os.path.relpath(file_path, directory),
"line_number": line_num,
"line": line.strip()[:200]
})
- Immediately after the last line above, add the remaining lines to close the function:
except (PermissionError, UnicodeDecodeError):
continue
except FileNotFoundError:
return {"error": f"Directory not found: {directory}"}
return {"search_term": search_term, "matches": matches[:50], "total_matches": len(matches)}
What does this code do?
The search_in_files function walks through every file in a directory and checks each line for a match. It uses case-insensitive comparison (.lower()) and caps results at 50 matches to keep things manageable.
Each match includes the file path, line number, and a preview of the matching line. This gives the AI enough context to reason about what it finds.
- Save agent.py.
- Test the search function by temporarily adding this line at the bottom of the file:
print(search_in_files(".", "import"))
- Save and run the script:
python3 agent.py
You should see a dictionary with matches showing every line in agent.py that contains the word "import". Your three tool functions are working.
- Delete the print(search_in_files(".", "import")) test line.
Seeing a SyntaxError?
The search_in_files function has deeply nested indentation. Double-check that each level uses exactly four spaces. The matches.append block should be indented six levels deep (24 spaces).
Need help? Help me fix a SyntaxError in my search_in_files function in agent.py.
Register the tools and test the agent
Your functions work, but the AI does not know about them yet. You need to create tool declarations that describe each function's name, purpose, and parameters in a format the Gemini API understands.
What is a tool declaration?
A tool declaration is a JSON schema that tells the AI what a function does, what arguments it accepts, and which arguments are required. The AI reads these declarations to decide when and how to call your functions.
Think of it like a menu at a restaurant. The AI reads the menu (declarations) to decide what to order (which function to call), and your code is the kitchen that fills the order.
- In agent.py, add the following code below your search_in_files function. This creates a mapping from tool names to functions and starts the declarations list:
TOOL_FUNCTIONS = {
"list_files": list_files,
"read_file": read_file,
"search_in_files": search_in_files,
}
tool_declarations = [
{
"type": "function",
"name": "list_files",
"description": "Lists all files in a directory and its subdirectories.",
"parameters": {
"type": "object",
"properties": {
"directory": {
"type": "string",
"description": "The path to the directory to list files from."
}
},
"required": ["directory"]
}
},
What does this code do?
The TOOL_FUNCTIONS dictionary maps each tool's name to its actual Python function. Later, when the AI says "call list_files", your code looks up TOOL_FUNCTIONS["list_files"] to find the right function to execute.
The tool_declarations list is the "menu" the AI reads. Each entry describes one function with its name, description, and expected parameters. The list is not complete yet. The next code block adds the remaining two entries.
- Continue the tool_declarations list by pasting the remaining two tool entries and closing the list. Also replace the old response and print lines at the bottom of the file with this updated version:
{
"type": "function",
"name": "read_file",
"description": "Reads the contents of a file and returns it as text.",
"parameters": {
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "The path to the file to read."
}
},
"required": ["file_path"]
}
},
{
"type": "function",
"name": "search_in_files",
"description": "Searches for a term across all files in a directory.",
"parameters": {
"type": "object",
"properties": {
"directory": {
"type": "string",
"description": "The path to the directory to search in."
},
"search_term": {
"type": "string",
"description": "The text to search for in files."
}
},
"required": ["directory", "search_term"]
}
}
]
- Now add the config and the updated API call at the bottom of the file, below the closing ] of tool_declarations:
config = types.GenerateContentConfig(
tools=tool_declarations,
)
response = client.models.generate_content(
model=MODEL,
contents="What files are in this project?",
config=config
)
print(response.text)
What changed?
The GenerateContentConfig now includes tools=tool_declarations. This tells the Gemini API about your three functions. When the AI receives your question, it can now choose to call one of those functions instead of guessing.
The prompt also changed from "What files are in the current directory?" to "What files are in this project?" to match the kind of question you would actually ask a code analyzer.
Before you run this, what do you think will happen? Will the AI give the same vague answer as before, or something different?
- Save agent.py and run it:
python3 agent.py
You should see the AI respond with information about the actual files in your project directory. The AI recognized that it needed to list files, called your list_files tool, and used the real results to form its answer.
Why is this different from Step 2?
In Step 2, the AI had no tools and could only guess. Now it has access to list_files, read_file, and search_in_files. When you ask about files, the AI reads the tool declarations, decides list_files is the right tool, and the SDK handles calling your function and feeding the result back to the AI. This is function calling in action.
Seeing None or an empty response?
- Make sure you deleted the old response = client.models.generate_content(...) block from Step 2 and replaced it with the new version that includes config=config.
- Check that tool_declarations is a properly closed list. The opening [ and closing ] must both be present.
- If you see a rate limit error, wait a minute and try again. The free tier has a limit of about 10 requests per minute.
Still stuck? Help me debug why my agent is not calling the list_files tool when I ask about project files.
✔️ Awesome, I've got everything!
Great. Double check that you have saved agent.py.
ⓧ I'd like to double check the full code
Your agent.py is now quite long. Scroll through your file and confirm you have each of these sections in order:
- Imports (os, json, genai, types) at the top.
- The load_api_key() function and API_KEY, client, MODEL setup.
- Six ANSI color constants (YELLOW through BOLD).
- Three tool functions: list_files, read_file, search_in_files.
- The TOOL_FUNCTIONS dictionary.
- The tool_declarations list with three entries.
- The config, response, and print(response.text) lines at the bottom.
Make sure there are no leftover test lines like print(list_files(".")) or print(search_in_files(...)) remaining in the file.
Your agent can now see your files. The problem is that it only answers one question and exits. Next up, you will build the interactive agent loop so you can have a conversation and ask follow-up questions.
Build the Interactive Agent Loop
Your agent can answer questions about local files now. But it only handles one question before the program exits.
A real agent is interactive. You ask a question, it reasons and calls tools, you ask a follow-up that builds on the previous context, and the conversation keeps flowing. In this step, you will build the full agent loop that powers every AI assistant.
In this step, get ready to:
- Add a system prompt that tells the AI what it is and what tools it has.
- Build an interactive chat loop with conversation memory.
- Handle tool calls, clean exits, and errors gracefully.
Tell the AI who it is
Right now, the AI receives your question with no context about its role. A system prompt is a set of instructions that tells the AI who it is, what tools it has, and how to use them.
- In agent.py, scroll to the bottom of the file and find these lines:
config = types.GenerateContentConfig(
tools=tool_declarations,
)
response = client.models.generate_content(
model=MODEL,
contents="What files are in this project?",
config=config
)
print(response.text)
- Delete all of these lines. You are replacing this one-shot call with an interactive loop.
- Now find the search_in_files function. Right below it (above TOOL_FUNCTIONS), add the system prompt by pasting this code:
SYSTEM_PROMPT = """You are a code analyzer agent. You help users understand code projects by reading and analyzing their files.
You have access to these tools:
- list_files: Lists all files in a directory. Use this first to understand the project structure.
- read_file: Reads the contents of a specific file. Use this to examine code.
- search_in_files: Searches for a term across all files. Use this to find specific functions, imports, or patterns.
When a user asks about a project:
1. Start by listing the files to understand the project structure.
2. Read relevant files to understand the code.
3. Provide clear, helpful explanations based on what you find.
Always base your answers on the actual file contents you read. Never guess or make up code that you have not seen."""
What does this prompt do?
The system prompt gives the AI its identity and a playbook. It lists the three tools by name, describes when to use each one, and sets a strict rule: only answer based on files you actually read.
Without this prompt, the AI might guess at file contents or ignore its tools entirely. The clearer your instructions, the better the agent behaves.
Create the interactive agent loop
The single generate_content call you deleted was a one-and-done request. To hold a conversation, you need a chat session that remembers every message the AI and user have exchanged.
- At the very bottom of agent.py (after the tool_declarations list), add the first half of the run_agent function by pasting this code:
def run_agent():
# Welcome message
print(f"\n{BOLD}Code Analyzer Agent{RESET}")
print(f"Ask me about any code project. Type {BOLD}quit{RESET} to exit.\n")
# Configure the chat with system prompt and tools
config = types.GenerateContentConfig(
system_instruction=SYSTEM_PROMPT,
tools=tool_declarations,
)
# Create a stateful chat that remembers previous messages
chat = client.chats.create(model=MODEL, config=config)
while True:
try:
user_input = input(f"{BOLD}You:{RESET} ").strip()
except (KeyboardInterrupt, EOFError):
print("\nGoodbye!")
break
if not user_input:
continue
if user_input.lower() in ("quit", "exit"):
print("Goodbye!")
break
What does this code do?
The GenerateContentConfig now includes system_instruction=SYSTEM_PROMPT so every message in this chat is guided by the system prompt you wrote above.
The client.chats.create() call creates a stateful chat session. Unlike the old generate_content call, this remembers every message so follow-up questions have full context.
The while True loop keeps asking for input. Typing quit or exit breaks the loop cleanly. Pressing Ctrl+C is caught by the KeyboardInterrupt handler so the program exits gracefully.
- Directly below the code you just pasted (still inside the run_agent function, inside the while True loop), add the tool-call handler and the entry point by pasting this code:
try:
response = chat.send_message(user_input)
# Process tool calls until the AI produces a text response
while response.candidates[0].content.parts:
has_function_call = False
for part in response.candidates[0].content.parts:
if part.function_call:
has_function_call = True
fn_name = part.function_call.name
fn_args = dict(part.function_call.args)
# Execute the matching tool function
if fn_name in TOOL_FUNCTIONS:
result = TOOL_FUNCTIONS[fn_name](**fn_args)
else:
result = {"error": f"Unknown function: {fn_name}"}
# Send the tool result back to the AI
response = chat.send_message(
types.Part.from_function_response(
name=fn_name,
response=result
)
)
break
if not has_function_call:
break
if response.text:
print(f"\n{BOLD}Agent:{RESET} {response.text}\n")
except Exception as e:
print(f"\n{RED}Error:{RESET} {e}\n")
print("Try again or type 'quit' to exit.\n")
if __name__ == "__main__":
run_agent()
What is the agent loop doing here?
This is the core agent loop pattern. The AI receives your question via chat.send_message(). If the AI decides it needs data, it responds with a function_call instead of text.
Your code catches that function call, runs the matching Python function from TOOL_FUNCTIONS, and sends the result back using types.Part.from_function_response(). The AI then decides whether it needs another tool call or has enough information to give a text answer.
The inner while loop keeps this going. One question might trigger three or four tool calls before the AI is satisfied. This loop-until-done pattern is exactly how every AI assistant works under the hood.
- Save agent.py.
Before you run the agent, think about what will happen when you ask it "What does this project do?" Do you think it will call one tool, multiple tools, or none?
- Run the agent by entering this command in your terminal:
python3 agent.py
- You will see a You: prompt. Type:
What does this project do?
The agent calls your tool functions behind the scenes, reads the actual files, and gives you an accurate answer based on real code. It may take a few seconds as the AI chains multiple tool calls together.
- Now ask a follow-up question without restarting the program:
Show me the main function
The agent remembers your previous conversation and uses tool calls again to find and display the relevant code. This is conversation memory in action.
- Type quit to exit the agent.
Agent not responding or crashing?
- If you see an indentation error, make sure the try: block from the second chunk is indented inside the while True: loop (8 spaces or 2 tabs from the left edge).
- If you see a rate limit error, wait about 60 seconds and try again. The free tier has a limit of requests per minute.
- If the agent gives a generic answer without reading files, check that SYSTEM_PROMPT is placed above TOOL_FUNCTIONS and that the config inside run_agent includes system_instruction=SYSTEM_PROMPT.
Help me debug my agent loop
✔️ Awesome, I've got everything!
Great. Double check you have saved agent.py.
ⓧ I'd like to double check the full code
Scroll up through your file and confirm each section is present in this order:
- Imports (os, json, genai, types) at the top.
- The load_api_key() function, API_KEY, client, and MODEL setup.
- ANSI color constants (YELLOW, CYAN, etc.).
- Three tool functions: list_files, read_file, search_in_files.
- The SYSTEM_PROMPT string (added in this step).
- The TOOL_FUNCTIONS dictionary and tool_declarations list.
- The run_agent() function with the while True loop and tool-call handler (added in this step).
- The if __name__ == "__main__": block at the very bottom calling run_agent().
Make sure the old client.models.generate_content lines at the bottom are completely removed.
Your agent is fully interactive now. You can ask any question, follow up on the answer, and the AI remembers the whole conversation. Next up, you will make the agent's decision-making visible so you can watch it think in real time.
Make the Agent Transparent
Your agent answers questions correctly now. But when it does, you only see the final answer. All the tool calls happen silently behind the scenes.
The most valuable part of building an AI agent is understanding HOW it thinks. In this step, you'll add colored console output so you can watch every tool call happen in real time.
In this step, get ready to:
- Add helper functions that print tool calls and results in color.
- Wire the helpers into your agent loop so every tool call is visible.
- Test the transparent agent with a multi-tool question.
Add the print helper functions
You already defined ANSI color constants back in Step 3. Now you'll put them to work in two small functions that format tool activity for the console.
- In agent.py, scroll down to the run_agent() function.
- Add the following two functions directly above def run_agent()::
# --- Agent Loop ---
def print_tool_call(name, args):
print(f"\n {YELLOW}[Tool Call]{RESET} {BOLD}{name}{RESET}")
for key, value in args.items():
print(f" {CYAN}{key}:{RESET} {value}")
def print_tool_result(result):
preview = json.dumps(result, indent=2)[:300]
print(f" {GREEN}[Result]{RESET} {preview}")
if len(json.dumps(result)) > 300:
print(f" {GREEN}...truncated{RESET}")
What does this code do?
- print_tool_call prints the tool's name in yellow/bold, then loops through each argument and prints the key in cyan. This shows you exactly what the AI asked the tool to do.
- print_tool_result converts the result dictionary to a JSON string, trims it to 300 characters, and prints it in green. If the full result is longer, it shows a truncation notice so you know data was clipped.
- Save agent.py.
Wire the helpers into the agent loop
The helper functions exist, but nothing calls them yet. You need to insert them into the tool execution section of run_agent() so they fire every time the AI uses a tool.
- Inside the run_agent() function, find this block of code inside the inner for part in response.candidates[0].content.parts: loop:
fn_args = dict(part.function_call.args)
if fn_name in TOOL_FUNCTIONS:
result = TOOL_FUNCTIONS[fn_name](**fn_args)
else:
result = {"error": f"Unknown function: {fn_name}"}
response = chat.send_message(
- Replace that block so it now calls print_tool_call before execution and print_tool_result after. The updated code should look like this:
fn_args = dict(part.function_call.args)
print_tool_call(fn_name, fn_args)
# Execute the tool
if fn_name in TOOL_FUNCTIONS:
result = TOOL_FUNCTIONS[fn_name](**fn_args)
else:
result = {"error": f"Unknown function: {fn_name}"}
print_tool_result(result)
# Send the result back to the model
response = chat.send_message(
What changed?
You added two lines. print_tool_call(fn_name, fn_args) fires right before the tool runs, so you see the AI's request. print_tool_result(result) fires right after, so you see what the tool returned.
Together, they make the agent loop visible: request, execute, result, repeat.
- Save agent.py.
Seeing a NameError?
- Make sure print_tool_call and print_tool_result are defined above run_agent() in the file, not inside it.
- Check that the function names match exactly, including underscores.
My agent is crashing with a NameError on print_tool_call or print_tool_result. Help me figure out what went wrong.
Test the transparent agent
Time to see your agent think out loud. You'll ask a question that forces multiple tool calls so you can watch the full decision-making chain.
Before you run this, think about what will happen when you ask the agent to summarize this project. How many tool calls do you think it will make, and which tools will it pick?
- Run the agent by entering this command in your terminal:
python3 agent.py
- At the You: prompt, type:
Summarize this project.
This time, instead of just seeing the final answer, you'll see the agent's thinking process play out in color. The yellow [Tool Call] lines show which tool the AI chose and what arguments it passed. The green [Result] lines show what the tool returned.
You should see the agent call list_files first to discover the project structure, then read_file to examine key files, and finally produce a summary based on what it actually read.
What is the agent loop?
What you're watching in the console is the agent loop pattern. The AI receives a task, decides which tool to call, your code executes the tool, the result goes back to the AI, and it decides the next step. This cycle repeats until the AI has enough information to answer.
Every AI assistant, automation platform, and coding copilot runs this same pattern under the hood. You've just built one from scratch.
- Type quit to exit the agent.
Not seeing colored output?
- Make sure you added print_tool_call(fn_name, fn_args) before the if fn_name in TOOL_FUNCTIONS: line, not after it.
- Check that print_tool_result(result) comes after the tool execution block, not inside the if/else.
- If you see the agent answer but with no tool output, the print calls may be at the wrong indentation level. They should be inside the if part.function_call: block.
My agent runs but I don't see any colored tool call output. Help me figure out where to place the print calls.
✔️ Awesome, I've got everything!
Great. Make sure you've saved agent.py.
ⓧ I'd like to double check the full code
Your agent.py is now the final version. Scroll up through your file and confirm these sections appear in order:
- Imports (os, json, genai, types) at the top.
- load_api_key(), client setup, and ANSI color constants.
- Three tool functions: list_files, read_file, search_in_files.
- SYSTEM_PROMPT, TOOL_FUNCTIONS dict, and tool_declarations list.
- print_tool_call() and print_tool_result() helper functions (the new ones from this step).
- run_agent() with print_tool_call and print_tool_result called inside the tool execution loop.
- if __name__ == "__main__": at the bottom.
Your agent is complete. You can now point it at any code folder on your machine, ask a question, and watch it reason through the answer step by step. Next up, there's an optional Secret Mission if you want to push your agent further.
Secret mission
Add a Code Statistics Tool
Your agent has three tools. Real agents have dozens. In this challenge, you will write a fourth tool that counts lines of code across a project, register it with the Gemini API, and teach the agent when to use it. By the end, asking "How big is this project?" triggers a formatted statistics report.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on your local machine, and the Gemini API free tier has no cost. There are no ongoing charges to worry about.
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- The code-analyzer-agent project folder on your Desktop (contains agent.py, .env, and the venv virtual environment).
- A Google AI Studio API key (free tier, no billing required).
Keep everything running
No action needed. Choose this if you plan to keep experimenting with your agent or want to extend it with more tools.
- Your project folder stays on your Desktop, ready to use any time.
- Your API key remains free as long as you stay within the Gemini free tier limits.
- If you closed your terminal, reactivate your virtual environment by navigating to the project folder and running source venv/bin/activate.
Pause - I'll come back to this later
Shut down the agent and deactivate your virtual environment to free up terminal resources. Your files and API key stay in place for next time.
- If the agent is still running, type quit and press Enter to exit.
- Deactivate your virtual environment by running:
deactivate
- When you are ready to come back, reactivate by running:
cd ~/Desktop/code-analyzer-agent
source venv/bin/activate
Delete - I don't want to use this again
Remove all project files from your machine and optionally delete your API key.
- If the agent is still running, type quit and press Enter to exit.
- Deactivate your virtual environment by running:
deactivate
- Delete the entire project folder by running:
macOS
rm -rf ~/Desktop/code-analyzer-agent
Windows
rmdir /s /q %USERPROFILE%\Desktop\code-analyzer-agent
- Confirm the folder is gone by running:
ls ~/Desktop
You should no longer see code-analyzer-agent in the output.
- To delete your API key, go to aistudio.google.com and navigate to your API keys.
- Find the key you created for this project and delete it.
Is deleting the API key required?
Deleting the key is optional. The Gemini free tier costs nothing, so a lingering key has no financial impact. Deleting it is good hygiene if you know you will not use it again.
Nice Work!
Nice Work!
Nice work! You just built a fully working AI agent from scratch using Python and the Gemini API. You can point it at any code folder on your Mac, ask a question in plain English, and watch it autonomously read files and reason through an answer.
You've learned how to:
- Implement function calling, the pattern where an AI decides which tools to use and your code executes them. This is the foundation behind every AI assistant and automation platform.
- Build the agent loop with conversation memory. The AI receives a task, decides which tool to call, your code executes the tool, the result goes back to the AI, and it decides the next step.
- Make an agent transparent by printing colored console output that traces every tool call, its arguments, and its results in real time so you can see the AI's decision-making process.
- Secret Mission: Extended the agent with a new count_lines tool, proving you can add any capability to an AI agent.
Ready to quiz yourself?