Skip the framework wars — build with raw API calls first
I see the same question in every AI agent Discord: "Should I use LangGraph, CrewAI, or AutoGen?" Wrong question. The right question is: "What does my agent actually need to do?"
Here's the uncomfortable truth: most agents don't need a framework at all. They need 50 lines of API calls and some basic state management. The framework comes later, if ever.
I've built agents with all the popular frameworks. I've also built them with raw OpenAI API calls and a JSON file for memory. Guess which ones are still running in production six months later?
The agents built with raw API calls ship faster, break less, and cost less to maintain. Frameworks add complexity you don't need until you need it.
Start here instead:
import openai
import json
from datetime import datetime
class SimpleAgent:
def __init__(self, name):
self.name = name
self.memory_file = f"{name}_memory.json"
self.load_memory()
def load_memory(self):
try:
with open(self.memory_file, 'r') as f:
self.memory = json.load(f)
except FileNotFoundError:
self.memory = {"conversations": [], "facts": {}}
def save_memory(self):
with open(self.memory_file, 'w') as f:
json.dump(self.memory, f, indent=2)
def think(self, user_input):
context = self.build_context(user_input)
response = openai.chat.completions.create(
model="gpt-4",
messages=context
)
self.remember_interaction(user_input, response.choices[0].message.content)
return response.choices[0].message.content
def build_context(self, user_input):
messages = [
{"role": "system", "content": f"You are {self.name}. Previous facts you know: {self.memory['facts']}"},
{"role": "user", "content": user_input}
]
# Add recent conversation history
for conv in self.memory["conversations"][-5:]:
messages.insert(-1, {"role": "user", "content": conv["user"]})
messages.insert(-1, {"role": "assistant", "content": conv["agent"]})
return messages
def remember_interaction(self, user_input, agent_response):
self.memory["conversations"].append({
"timestamp": datetime.now().isoformat(),
"user": user_input,
"agent": agent_response
})
self.save_memory()This is 40 lines. It has memory, context, and persistence. It costs $0.03 per conversation instead of the $2-5 you'll spend debugging framework abstractions.
Want tools? Add a function calling layer:
def get_weather(location):
# Your weather API call here
return f"Weather in {location}: 72°F, sunny"
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"}
}
}
}
}
]
# In your think() method:
response = openai.chat.completions.create(
model="gpt-4",
messages=context,
tools=tools
)
if response.choices[0].message.tool_calls:
# Handle tool calls
for tool_call in response.choices[0].message.tool_calls:
if tool_call.function.name == "get_weather":
args = json.loads(tool_call.function.arguments)
result = get_weather(args["location"])
# Add result to context and call againYou now have a working agent with tools, memory, and conversation history. No framework dependencies, no version conflicts, no abstraction layers hiding what's actually happening.
When do you need a framework? When you have multiple agents that need to coordinate, when you need complex workflow orchestration, or when you're building something that needs to scale beyond a few conversations per minute.
But 80% of "agent" projects are really just "chatbot with a few API calls." Don't over-engineer it.
The raw API approach teaches you what agents actually do under the hood. You'll understand token usage, context management, and error handling. When you do eventually need a framework, you'll pick the right one because you know what problems you're actually solving.
Framework paralysis kills more agent projects than bad code ever will. Start simple, ship fast, add complexity when you hit real limitations.