Skip to content

Raw Python vs LangGraph for AI Agents: When to Build From Scratch

The Problem

I was building my first multi-agent system and hit a wall: should I use LangGraph or write everything in raw Python?

Framework advocates said “never reinvent the wheel.” Bare-metal enthusiasts claimed “frameworks hide too much magic.” Both sides had valid points, but neither gave me a clear answer for my specific situation.

After digging through production war stories on Reddit and testing both approaches, I found the answer depends on what you’re optimizing for. Here’s what I learned.

What Frameworks Give You

Let me start with what LangGraph actually provides. I built the same simple agent both ways to compare.

The Framework Approach

langgraph_agent.py
from langgraph.graph import StateGraph, MessagesState
from langgraph.prebuilt import ToolNode
from langchain_openai import ChatOpenAI
from langchain.tools import tool
# Define a tool
@tool
def search(query: str) -> str:
"""Search for information."""
return f"Results for: {query}"
# Define the agent
def agent(state: MessagesState):
llm = ChatOpenAI(model="gpt-4")
response = llm.invoke(state["messages"])
return {"messages": [response]}
# Build the graph
graph = StateGraph(MessagesState)
graph.add_node("agent", agent)
graph.add_node("tools", ToolNode([search]))
graph.set_entry_point("agent")
graph.compile() # Built-in: tracing, retries, state persistence

The framework gives me several things for free:

  • Human-in-the-loop (HITL): Pause execution for human approval
  • Built-in tracing: See exactly what happened in each step
  • State persistence: Resume from checkpoints
  • Retry logic: Automatic retries on failures
  • Dependency injection: Pass context to tools easily

The Raw Python Approach

raw_agent.py
import json
from openai import OpenAI
from typing import TypedDict, Optional
class AgentState(TypedDict):
messages: list
context: Optional[dict]
class RawAgent:
def __init__(self, tools: dict, max_iterations: int = 10):
self.tools = tools
self.max_iterations = max_iterations
self.client = OpenAI()
def run(self, user_input: str, context: dict = None) -> str:
messages = [{"role": "user", "content": user_input}]
state = {"messages": messages, "context": context}
for iteration in range(self.max_iterations):
response = self.client.chat.completions.create(
model="gpt-4",
messages=self._format_messages(state)
)
decision = json.loads(response.choices[0].message.content)
if decision["action"] == "finish":
return decision["answer"]
if decision["action"] == "use_tool":
tool_name = decision["tool"]
tool_input = decision["input"]
result = self.tools[tool_name](tool_input, context)
state["messages"].append({
"role": "assistant",
"content": f"Tool {tool_name} returned: {result}"
})
return "Max iterations reached"
def _format_messages(self, state: dict) -> list:
# Custom message formatting logic
return state["messages"]
# Usage
tools = {"search": lambda q, ctx: f"Results for: {q}"}
agent = RawAgent(tools)
result = agent.run("What is LangGraph?")

I had to implement everything myself: the loop, the parsing, the state management. But I also had complete control over every decision.

What Production Teams Actually Do

Here’s what surprised me: most teams do both.

I found this insight from a Reddit discussion where experienced developers shared their production experiences:

“Most teams start with a framework, then slowly remove it. Frameworks are great for prototyping orchestration. But once agents hit real workloads, teams usually want tighter control over: memory, retries/failure handling, tool execution, cost + latency.” — Axirohq (Reddit, score: 5)

This matched what I saw in other discussions. The pattern is:

  1. Prototype with framework: Ship fast, validate the idea
  2. Hit framework limits: Edge cases the framework doesn’t handle well
  3. Gradually replace framework pieces: Custom memory, custom retry logic
  4. End up with mostly raw Python: With some framework utilities still useful

When to Choose Raw Python

I found raw Python makes sense when:

1. You Need Precise Control Over Memory

custom_memory.py
class CustomMemory:
"""Fine-grained control over what gets remembered."""
def __init__(self, max_tokens: int = 4000):
self.max_tokens = max_tokens
self.memories = []
def add(self, message: dict, importance: float = 1.0):
# Custom logic: keep important messages longer
# Compress low-importance messages
# Implement custom summarization
pass
def get_context(self) -> list:
# Custom retrieval logic
# Priority scoring
# Token counting
pass

Framework memory solutions often force you into their abstractions. Custom memory lets you optimize for your specific use case.

2. You’re Showcasing Technical Depth

If you’re applying for founding engineer roles or positions requiring deep AI systems knowledge, a raw Python agent demonstrates more understanding than gluing together framework components.

One developer put it bluntly:

“Do not use LangGraph, it’s like using a sledgehammer to crack a nut.” — Jorgestar29 (Reddit, score: 5)

While I don’t fully agree (frameworks have their place), this view represents what some technical interviewers think.

3. Framework Overhead Outweighs Benefits

For simple agents with straightforward flows, the framework’s abstraction layer adds complexity without corresponding value.

comparison.txt
Framework overhead:
- Learning curve: 2-4 weeks
- Debugging through abstraction layers
- Version compatibility issues
- Hidden behavior surprises
Raw Python overhead:
- Build everything yourself
- More initial code
- No built-in best practices

When to Choose Frameworks

Frameworks shine when you need:

1. Rapid Prototyping

langgraph_prototype.py
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
# Functional agent in 5 lines
model = ChatOpenAI(model="gpt-4")
tools = [...] # Your tools here
agent = create_react_agent(model, tools)
result = agent.invoke({"messages": [("user", "Analyze this data")]})

This took me 5 minutes with LangGraph. The raw Python version took 2 hours.

2. Built-in Features You’d Otherwise Build Anyway

A framework supporter noted:

“Using a framework saves you time, if you need: HITL, Dependency injection to tools, Tracing, REACT Loop, Chat session management.” — Jorgestar29 (Reddit, score: 5)

Each of these features requires significant effort to implement correctly. If you need all of them, the framework value proposition becomes clear.

3. Team Without Deep Agent Experience

framework_abstraction.py
# Framework handles edge cases you might miss
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph
checkpointer = MemorySaver()
graph = StateGraph(State)
# ... build graph ...
# Automatic state persistence and recovery
app = graph.compile(checkpointer=checkpointer)
# Can resume from any checkpoint
result = app.invoke(
{"input": "query"},
config={"configurable": {"thread_id": "conversation-123"}}
)

The framework handles checkpoint persistence, state recovery, and edge cases that are easy to miss in custom implementations.

The Decision Framework

I created this decision tree based on my research:

decision_tree.txt
Start here:
|
+-- Is this for a portfolio/interview?
| +-- Yes: Raw Python (shows depth)
| +-- No: Continue
|
+-- Do you need HITL, tracing, or checkpoint persistence?
| +-- Yes: Framework (don't reinvent)
| +-- No: Continue
|
+-- Is time-to-market critical?
| +-- Yes: Framework (faster start)
| +-- No: Continue
|
+-- Do you have specific memory/latency requirements?
| +-- Yes: Raw Python (more control)
| +-- No: Framework (good defaults)
|
+-- Is your team experienced with agents?
+-- Yes: Either works
+-- No: Framework (better guardrails)

My Recommendation

Based on my analysis:

Start with LangGraph if:

  • You need to ship fast
  • You want built-in tracing, HITL, state persistence
  • Your team is new to agents
  • You don’t have specific customization needs

Start with raw Python if:

  • You’re building for a technical interview/portfolio
  • You have specific memory or latency requirements
  • Your agent logic is simple enough that framework overhead hurts more than helps
  • You want to deeply understand how agents work

The hybrid approach:

  • Start with framework, plan to migrate specific components
  • Keep your business logic separate from framework abstractions
  • Document why you chose each approach for future team members

Summary

The raw Python vs LangGraph debate isn’t about which is “better.” It’s about what you’re optimizing for:

FactorRaw PythonLangGraph
Learning curveHigher (build everything)Lower (use abstractions)
Initial speedSlowerFaster
Long-term flexibilityMaximumConstrained by framework
Debugging difficultyDirect controlAbstraction layers
Best forPortfolio, custom needsProduction deadlines

Most production teams start with frameworks, then migrate to custom Python as requirements become specific. Neither approach is wrong—they just optimize for different things.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments