Getting Started with AI Agents in Python: A Beginner's Guide
AI agents are programmatically driven systems that use large language models (LLMs) to reason through tasks, decide which tools to execute, and autonomously take actions until a goal is achieved. Unlike simple prompt-and-response applications, an AI agent operates in a dynamic feedback loop: it evaluates context, invokes tools like external APIs or database queries, processes the tool output, and determines the next step.
In this beginner-friendly guide, you will learn how AI agents work under the hood by building a clean, lightweight agent loop in Python from scratch using the OpenAI Python SDK. We will also explore how tool calling works and briefly look at when you should adopt frameworks like Pydantic AI, LangGraph, or CrewAI.
The Core Components of an AI Agent
Before writing code, it helps to understand the four primary building blocks of any AI agent:
- The Model (Brain): An LLM that understands user instructions, reasons through problem steps, and formulates action plans.
- Tools (Capabilities): Python functions that give the agent abilities beyond text generation—such as searching the web, executing code, or querying database tables.
- The Loop (Execution Cycle): The control structure that continuously sends prompt context to the model, inspects whether the model requested a tool execution, calls the required function, feeds the tool output back to the model, and repeats until a final answer is returned.
- Memory / State (Context): The history of messages and tool responses that informs subsequent decisions across conversation turns.
Prerequisites
To follow along with the tutorial, ensure you have Python 3.10+ installed along with the official OpenAI library:
pip install openai pydantic
Set your OpenAI API key as an environment variable in your terminal:
export OPENAI_API_KEY="your-api-key-here"
Step 1: Defining Tools for the Agent
Tools are standard Python functions decorated or described using JSON schema definitions. Modern LLM APIs support native function calling, where you provide function definitions to the model and it returns structured tool call requests when needed.
Let us define two simple tools: a mock weather finder and a basic calculator.
import json
from openai import OpenAI
client = OpenAI()
# Tool 1: Mock weather tool
def get_weather(location: str) -> str:
"""Returns fake weather data for a given location."""
data = {
"london": "15°C, Partly Cloudy",
"tokyo": "22°C, Sunny",
"new york": "18°C, Rain"
}
loc_clean = location.lower().strip()
return data.get(loc_clean, f"20°C, Clear (default for {location})")
# Tool 2: Basic calculator
def calculate(expression: str) -> str:
"""Evaluates a basic math expression securely."""
try:
# Keep evaluation simple for demonstration
result = eval(expression, {"__builtins__": None}, {})
return str(result)
except Exception as err:
return f"Error evaluating expression: {err}"
# Map tool names to actual Python functions
AVAILABLE_TOOLS = {
"get_weather": get_weather,
"calculate": calculate
}
Next, define the tools schema so the LLM understands how and when to invoke these functions:
tools_schema = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather information for a specific city.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city name, e.g., London or Tokyo"
}
},
"required": ["location"]
}
}
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "Perform basic arithmetic calculations.",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression, e.g., '15 * 4 + 10'"
}
},
"required": ["expression"]
}
}
}
]
Step 2: Building the Core Agent Loop
The heart of an agent is an iterative loop. In each turn:
- We pass the conversation history to the model.
- If the model returns standard text, the agent is finished and returns the answer.
- If the model requests one or more tool calls, we execute those functions, append the results to the message history, and repeat the loop.
Here is a complete function implementing the agent loop with turn limits to prevent infinite loops:
def run_agent(user_prompt: str, max_turns: int = 5) -> str:
messages = [
{
"role": "system",
"content": "You are a helpful AI assistant equipped with tools. Always use available tools to retrieve factual answers when required."
},
{
"role": "user",
"content": user_prompt
}
]
for turn in range(max_turns):
print(f"\n--- Agent Turn {turn + 1} ---")
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools_schema,
tool_choice="auto"
)
response_message = response.choices[0].message
messages.append(response_message)
# Check if the model wants to call tools
tool_calls = response_message.tool_calls
if not tool_calls:
# Model didn't request any tool, final answer reached
print("Agent finished reasoning.")
return response_message.content
# Process each requested tool call
for tool_call in tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
print(f"Tool Requested: {func_name}({func_args})")
tool_func = AVAILABLE_TOOLS.get(func_name)
if tool_func:
if func_name == "get_weather":
result = tool_func(location=func_args.get("location"))
elif func_name == "calculate":
result = tool_func(expression=func_args.get("expression"))
else:
result = "Unknown tool requested."
else:
result = f"Error: Tool {func_name} not implemented."
print(f"Tool Output: {result}")
# Append tool result back to the context
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
return "Agent reached maximum execution turns without completing the request."
Step 3: Running the Agent
Now you can test your Python agent with a complex query that requires multiple tool executions:
if __name__ == "__main__":
prompt = "What is the weather in Tokyo right now? Also, calculate what 25 multiplied by 18 is."
print(f"User Request: {prompt}")
final_response = run_agent(prompt)
print("\n=== Final Answer ===")
print(final_response)
When you run this script, the agent:
- Recognizes that it needs weather for Tokyo and a math calculation.
- Calls
get_weather(location="Tokyo")andcalculate(expression="25 * 18"). - Receives
22°C, Sunnyand450. - Synthesizes both results into a friendly final response.
When to Move from Raw Loops to Agent Frameworks
Building a raw loop from scratch gives you full control and helps you understand how agent state and tool calling work. However, as agent applications grow in complexity, handling custom edge cases, state persistence, structured input validation, and multi-agent delegation becomes challenging.
Here is when you should consider using high-level Python agent frameworks:
- Pydantic AI: Excellent for type-safe structured data extraction, tool outputs validated via Pydantic models, and strict output typing.
- LangGraph: Best when you need explicit control over workflows, state machines, cyclic execution graphs, long-term memory, and human-in-the-loop approval steps.
- CrewAI: Ideal for role-based multi-agent teams where specialized agents collaborate to complete complex project workflows.
- OpenAI Agents SDK: Useful when building native workflows heavily aligned with OpenAI assistant platforms and standardized tool handoffs.
Summary
Getting started with AI agents in Python does not require learning complex dependencies right away. By understanding the loop—sending context, parsing tool calls, executing functions, and returning output—you can construct custom, lightweight agents tailored to your exact needs.
As your requirements expand to multi-agent collaboration, memory persistence, or human intervention, higher-level frameworks like Pydantic AI and LangGraph offer robust abstractions built on these exact foundation principles.
Comments
No comments yet. Have a question or a tip? Be the first to share it.