DEV Community

Why do we import 100MB of frameworks to run a 50-line LLM reasoning loop?

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:

  1. The State (Memory): A list of message dictionaries (role and content) passed to and from the LLM.
  2. The Schema (Tools): A dictionary mapping tool names to standard Python functions.
  3. The Loop (Reasoning): A standard 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.


Coding the Agent (under 60 lines of Python)

This example uses the official openai SDK, but the same logic applies to Anthropic, Gemini, or local models running via Ollama.


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}")


Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
venu_varma profile image
Venu gopal varma Bhupathiraju

-
Why You Should Start From Scratch
Building agents this way offers three massive advantages:

Zero-Abstraction Debugging: If your tool calls fail, you can simply print the raw JSON arguments payload or inspection lists. There is no custom library logic hidden from you.
Control Over Token Cost: Since you manage the messages array manually, you choose when to truncate history, summarize old messages, or prune system instructions.
Extreme Performance: You avoid the import overhead and instantiation lag of massive framework packages.
Before you reach for a heavy wrapper to orchestrate your next AI feature, try writing the loop yourself. You might find you only needed 50 lines of Python all along.

Are you building agents with raw loops or orchestration frameworks? Let's discuss in the comments!