Building Your First AI Agent
Everything covered so far — LLMs, prompts, tools, memory, and the agent loop — comes together in this topic. A complete, working AI Agent will be built from scratch, step by step. By the end, there will be a real agent capable of taking a task, reasoning through it, calling tools, and delivering an answer.
The agent being built here is a Simple Research Assistant that can search the web and answer questions.
What the Agent Will Do
- Accept a user question in plain English
- Decide if it needs to search the web
- Call a web search tool if needed
- Use the search results to form an answer
- Respond to the user with a clear, concise answer
Project Structure
first_agent/ ├── .env ← API keys ├── agent.py ← Main agent logic ├── tools.py ← Tool definitions and implementations └── run.py ← Entry point to run the agent
Step 1 — Define the Tools
Create tools.py with a simulated web search tool. In production, a real search API (like SerpAPI or Tavily) would be used here.
# tools.py
import json
def web_search(query: str) -> str:
"""
Simulate a web search. In production, replace this with
a call to SerpAPI, Tavily, or Google Custom Search API.
"""
# Simulated results for demonstration
mock_results = {
"python programming language": {
"title": "Python (programming language) - Wikipedia",
"summary": "Python is a high-level, general-purpose programming language "
"created by Guido van Rossum in 1991. It is known for its simple "
"syntax, readability, and wide use in AI, web development, "
"data science, and automation."
},
"openai gpt-4": {
"title": "GPT-4 - OpenAI",
"summary": "GPT-4 is a large multimodal model released by OpenAI in March 2023. "
"It can accept image and text inputs and produce text outputs. "
"It is currently available via the OpenAI API."
}
}
# Check if query matches any mock result
for key in mock_results:
if key in query.lower():
result = mock_results[key]
return json.dumps({
"query": query,
"result": result["summary"],
"source": result["title"]
})
# Default response for unmatched queries
return json.dumps({
"query": query,
"result": f"Search results for '{query}': Several relevant articles found. "
f"Key finding: This topic has extensive documentation and community "
f"support available online.",
"source": "web"
})
# Tool definitions for the OpenAI API
TOOL_DEFINITIONS = [
{
"type": "function",
"function": {
"name": "web_search",
"description": (
"Search the web for information on any topic. "
"Use this when the user asks a question that requires "
"looking up current or factual information."
),
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query to look up"
}
},
"required": ["query"]
}
}
}
]
# Tool router — maps tool names to actual functions
TOOL_MAP = {
"web_search": web_search
}
Step 2 — Build the Agent Core
Create agent.py with the full agent loop:
# agent.py
import os
import json
from dotenv import load_dotenv
import openai
from tools import TOOL_DEFINITIONS, TOOL_MAP
load_dotenv()
client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
SYSTEM_PROMPT = """You are ResearchBot, a helpful research assistant.
Your job is to answer the user's questions accurately.
RULES:
- If the question requires factual or current information, use the web_search tool first.
- After getting search results, synthesise the information and give a clear answer.
- If the question is conversational or does not need research, answer directly.
- Always be concise and informative.
- If you cannot find relevant information, say so honestly."""
def run_agent(user_question: str, verbose: bool = True) -> str:
"""
Run the agent loop for a given user question.
Returns the final answer as a string.
"""
# Initialise conversation
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_question}
]
if verbose:
print(f"\n{'='*50}")
print(f"User: {user_question}")
print(f"{'='*50}")
# Agent loop — maximum 5 iterations to prevent infinite loops
max_steps = 5
for step in range(max_steps):
if verbose:
print(f"\n[Step {step + 1}] Agent is thinking...")
# Call the LLM
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=TOOL_DEFINITIONS,
tool_choice="auto", # Let the model decide when to use tools
temperature=0.3,
max_tokens=800
)
message = response.choices[0].message
# Add the assistant's response to conversation history
messages.append(message)
# Check if the model wants to call a tool
if message.tool_calls:
for tool_call in message.tool_calls:
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.function.arguments)
if verbose:
print(f"[Step {step + 1}] Tool called: {tool_name}")
print(f"[Step {step + 1}] Arguments: {tool_args}")
# Execute the tool
if tool_name in TOOL_MAP:
tool_result = TOOL_MAP[tool_name](**tool_args)
else:
tool_result = json.dumps({"error": f"Unknown tool: {tool_name}"})
if verbose:
result_preview = tool_result[:150] + "..." if len(tool_result) > 150 else tool_result
print(f"[Step {step + 1}] Tool result: {result_preview}")
# Add the tool result back to conversation
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": tool_result
})
else:
# No tool call — the agent has a final answer
final_answer = message.content
if verbose:
print(f"\n{'='*50}")
print(f"Agent: {final_answer}")
print(f"{'='*50}\n")
return final_answer
# If max steps reached without a final answer
return "I was unable to complete the task within the allowed steps."
Step 3 — Create the Entry Point
Create run.py to test the agent:
# run.py
from agent import run_agent
if __name__ == "__main__":
# Test 1: Question requiring web search
run_agent("What is the Python programming language?")
# Test 2: Conversational question (no search needed)
run_agent("What is 15 multiplied by 24?")
# Test 3: Another factual question
run_agent("Tell me about GPT-4 by OpenAI.")
Step 4 — Run the Agent
python run.py
Expected Output for Test 1
==================================================
User: What is the Python programming language?
==================================================
[Step 1] Agent is thinking...
[Step 1] Tool called: web_search
[Step 1] Arguments: {'query': 'python programming language'}
[Step 1] Tool result: {"query": "python programming language", "result": "Python is a
high-level, general-purpose programming language created by Guido van Rossum in 1991...
[Step 2] Agent is thinking...
==================================================
Agent: Python is a high-level, general-purpose programming language developed by
Guido van Rossum in 1991. It is widely used in AI and machine learning, web
development, data science, and automation. Python is known for its clean syntax
and readability, making it one of the most beginner-friendly languages in the world.
==================================================
Understanding What Happened
| Step | What the Agent Did |
|---|---|
| Step 1 — Receive input | Read the user's question about Python |
| Step 1 — Reason | Decided a web search was needed for factual info |
| Step 1 — Act | Called web_search("python programming language") |
| Step 2 — Observe | Received search results |
| Step 2 — Reason | Synthesised the results into a clear answer |
| Step 2 — Final answer | Responded to the user without calling another tool |
Adding a Second Tool — Calculator
Let us add a calculator tool to handle maths questions:
# Add to tools.py
def calculate(expression: str) -> str:
"""Safely evaluate a mathematical expression."""
try:
# Use eval with restricted scope for safety
result = eval(expression, {"__builtins__": {}}, {})
return json.dumps({"expression": expression, "result": result})
except Exception as e:
return json.dumps({"error": f"Could not evaluate: {str(e)}"})
# Add to TOOL_DEFINITIONS list:
{
"type": "function",
"function": {
"name": "calculate",
"description": "Evaluate a mathematical expression. Use for arithmetic calculations.",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "A Python math expression, e.g., '15 * 24' or '(100 - 20) * 0.18'"
}
},
"required": ["expression"]
}
}
}
# Add to TOOL_MAP:
"calculate": calculate
# Test the new tool
run_agent("What is 18% GST on a bill of ₹12,500?")
# Agent calls: calculate("12500 * 0.18") → 2250
# Agent responds: "18% GST on ₹12,500 is ₹2,250.
# The total bill including GST would be ₹14,750."
Connecting a Real Search API (Production Setup)
Replace the mock search with a real one using the Tavily API (a search API designed for LLM agents):
# Install: pip install tavily-python
from tavily import TavilyClient
import os
tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
def web_search(query: str) -> str:
"""Real web search using Tavily API."""
result = tavily.search(query=query, search_depth="basic", max_results=3)
summaries = []
for item in result.get("results", []):
summaries.append({
"title": item.get("title"),
"snippet": item.get("content", "")[:300],
"url": item.get("url")
})
return json.dumps({"query": query, "results": summaries})
Summary
The first complete AI Agent is now built and running. It follows the Observe → Think → Act loop, uses tool definitions and a tool router, manages conversation history across multiple steps, and delivers a final answer when the task is complete. Adding more tools is as simple as defining a new function and registering it in the tool definitions and tool map. This foundation will be expanded significantly in the upcoming topics.
