How to Build an AI Agent from Scratch in Python (2025 Guide)
Build a real AI agent with tools, memory, and multi-step reasoning in Python — using a free LLM API key. Full working code, no fluff.
What Is an AI Agent?
An AI agent is a program that uses a language model as its reasoning engine and can take actions in the world — calling tools, reading files, browsing the web, writing code, and making decisions across multiple steps. Unlike a simple chatbot that just responds to messages, an agent can plan, act, observe results, and adapt its plan based on what it learns.
In 2025, building agents has become dramatically simpler. This guide shows you how to build one from scratch in pure Python — no LangChain, no heavy framework — so you understand exactly what is happening under the hood.
What We Are Building
A Python agent that can:
- Answer questions using web search (simulated)
- Do math calculations
- Read and write files
- Remember previous steps in a conversation
- Decide which tool to use based on the task
Prerequisites
pip install openai
And a free API key from FreeLLMKeys.com.
Step 1 — Define Your Tools
Tools are just Python functions. The key is defining them in a format the LLM can understand — a JSON schema describing what each tool does and what arguments it takes.
import json
import math
# ── Tool implementations ──────────────────────────────────
def calculate(expression: str) -> str:
"""Safely evaluate a math expression."""
try:
allowed = {k: getattr(math, k) for k in dir(math) if not k.startswith('_')}
result = eval(expression, {"__builtins__": {}}, allowed)
return str(result)
except Exception as e:
return f"Error: {e}"
def read_file(path: str) -> str:
"""Read a local file and return its contents."""
try:
with open(path, 'r') as f:
return f.read()
except Exception as e:
return f"Error reading file: {e}"
def write_file(path: str, content: str) -> str:
"""Write content to a local file."""
try:
with open(path, 'w') as f:
f.write(content)
return f"File written successfully: {path}"
except Exception as e:
return f"Error writing file: {e}"
def web_search(query: str) -> str:
"""Simulate a web search (replace with real search API)."""
return f"[Simulated result for '{query}']: This is placeholder content. Replace with SerpAPI or Tavily."
# ── Tool registry ─────────────────────────────────────────
TOOLS = {
"calculate": calculate,
"read_file": read_file,
"write_file": write_file,
"web_search": web_search,
}
# ── Tool schema for the LLM ───────────────────────────────
TOOL_SCHEMAS = [
{
"type": "function",
"function": {
"name": "calculate",
"description": "Evaluate a mathematical expression. Use for any arithmetic or math.",
"parameters": {
"type": "object",
"properties": {"expression": {"type": "string", "description": "Math expression to evaluate, e.g. '2 ** 10' or 'math.sqrt(144)'"}},
"required": ["expression"]
}
}
},
{
"type": "function",
"function": {
"name": "read_file",
"description": "Read the contents of a local file.",
"parameters": {
"type": "object",
"properties": {"path": {"type": "string", "description": "File path to read"}},
"required": ["path"]
}
}
},
{
"type": "function",
"function": {
"name": "write_file",
"description": "Write content to a local file.",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string"},
"content": {"type": "string"}
},
"required": ["path", "content"]
}
}
},
{
"type": "function",
"function": {
"name": "web_search",
"description": "Search the web for current information.",
"parameters": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"]
}
}
},
]
Step 2 — Build the Agent Loop
The agent loop is the core of every AI agent. It works like this:
- Send the user's message + conversation history to the LLM
- If the LLM calls a tool → execute it → send result back to LLM → repeat
- If the LLM gives a text response → return it to the user
from openai import OpenAI
client = OpenAI(
base_url="https://aiapiv2.pekpik.com/v1",
api_key="sk-your-freellmkeys-key"
)
def run_agent(user_message: str, history: list = None) -> str:
if history is None:
history = []
# Add user message to history
messages = history + [{"role": "user", "content": user_message}]
print(f"\n🤔 Agent thinking...")
# Agent loop — runs until LLM gives a final answer
while True:
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a helpful AI assistant with access to tools. Use tools whenever they help you give a better answer. Think step by step."}
] + messages,
tools=TOOL_SCHEMAS,
tool_choice="auto"
)
msg = response.choices[0].message
# No tool call → final answer
if not msg.tool_calls:
return msg.content
# Process all tool calls
messages.append({"role": "assistant", "content": msg.content, "tool_calls": [
{"id": tc.id, "type": "function", "function": {"name": tc.function.name, "arguments": tc.function.arguments}}
for tc in msg.tool_calls
]})
for tool_call in msg.tool_calls:
fn_name = tool_call.function.name
fn_args = json.loads(tool_call.function.arguments)
print(f" 🔧 Calling tool: {fn_name}({fn_args})")
if fn_name in TOOLS:
result = TOOLS[fn_name](**fn_args)
else:
result = f"Unknown tool: {fn_name}"
print(f" ✅ Tool result: {result[:100]}...")
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
Step 3 — Run Your Agent
if __name__ == "__main__":
# Test 1: Math
answer = run_agent("What is the square root of 1764 multiplied by pi?")
print(f"\n📝 Answer: {answer}")
# Test 2: File operation
answer = run_agent("Create a file called hello.txt with the content 'Hello from AI agent!'")
print(f"\n📝 Answer: {answer}")
# Test 3: Multi-step task
answer = run_agent("Calculate 2^32 and write the result to a file called powers.txt")
print(f"\n📝 Answer: {answer}")
Step 4 — Add Memory
A stateless agent forgets everything after each call. Add conversation memory with a simple list:
def chat_with_agent():
history = []
print("AI Agent ready. Type 'quit' to exit.\n")
while True:
user_input = input("You: ").strip()
if user_input.lower() == 'quit':
break
response = run_agent(user_input, history)
print(f"\nAgent: {response}\n")
# Update history for next turn
history.append({"role": "user", "content": user_input})
history.append({"role": "assistant", "content": response})
chat_with_agent()
What to Build Next
This agent is the foundation. From here, you can add:
- Real web search: Replace the simulated search with Tavily API (free tier) or SerpAPI
- Code execution: Add a tool that runs Python code in a sandboxed subprocess
- Vector memory: Store past conversations in FAISS and retrieve relevant context automatically
- Multi-agent: Have one agent spawn sub-agents for specialized tasks
All of this works with a free FreeLLMKeys key. The agent framework itself costs nothing — you are only limited by the API rate limits, which are more than sufficient for development and personal use.