Why do we import 100MB of frameworks to run a 50-line LLM reasoning loop? A developer argues that building an AI agent from scratch with vanilla Python and the OpenAI SDK is simpler and more transparent than using bloated frameworks like AutoGen, LangChain, or CrewAI. The post demonstrates a minimal agent with a reasoning loop, tool calling, and state management in under 50 lines of code, emphasizing that complex orchestration frameworks are unnecessary for basic agentic design. Stop Importing Bloated Frameworks: Build a Python AI Agent from Scratch You want to build an AI agent. So you head to the docs of a popular orchestration framework, copy the boilerplate, import 20 modules, and spin up an agent. It works—until it doesn't. Suddenly, you're looking at a 50-line stack trace originating from a library wrapper. You don't know where the query failed, what the exact prompt was, or why the tool call failed to parse. Here is the truth: You don't need AutoGen, LangChain, or CrewAI to build a working AI agent. You just need vanilla Python and a basic understanding of the three core pillars of agentic design. The Three Pillars of an AI Agent Any basic agent can be broken down into three simple components: role and content passed to and from the LLM. while loop that calls the LLM, checks if it wants to use a tool, runs the tool if requested, appends the result to the State, and repeats until the LLM returns a final answer.Let’s build one. This example uses the official openai SDK, but the same logic applies to Anthropic, Gemini, or local models running via Ollama. python python import os import json from openai import OpenAI Initialize client client = OpenAI api key=os.environ.get "OPENAI API KEY" 1. Define the tools our agent can use def get weather location : if "tokyo" in location.lower : return "Tokyo is sunny and 25°C." return "Cool and rainy, 15°C." Map the function name to the actual function object tools map = { "get weather": get weather } Define the JSON schema so the LLM knows how to call it tool definition = { "type": "function", "function": { "name": "get weather", "description": "Get the current weather for a location", "parameters": { "type": "object", "properties": { "location": {"type": "string"} }, "required": "location" } } } 2. The Agent reasoning loop def run agent user prompt : Initialize the State Memory messages = {"role": "system", "content": "You are a helpful assistant. Call tools when necessary."}, {"role": "user", "content": user prompt} Run the loop max 5 turns to prevent infinite runs for in range 5 : response = client.chat.completions.create model="gpt-4o-mini", messages=messages, tools= tool definition message = response.choices 0 .message messages.append message Check if the model wants to call a tool if message.tool calls: for tool call in message.tool calls: name = tool call.function.name args = json.loads tool call.function.arguments print f" Calling tool: {name} with args: {args}" tool output = tools map name args Append tool response back to state messages.append { "role": "tool", "tool call id": tool call.id, "name": name, "content": tool output } else: If no tool was called, this is the final answer return message.content Run it if name == " main ": result = run agent "What is the weather like in Tokyo right now?" print f"\n Agent Response : {result}"