Back to Home
Coding Agents

The Agent Framework Wars: LangChain vs CrewAI vs AutoGen vs LangGraph

Priya Patel

Product manager at an AI startup. Explores how agents reshape workflows.

April 30, 202617 min read

I've built production systems with all four of these frameworks over the past eighteen months. Each one has cost me a weekend debugging session, each one has saved me weeks of work at different points...

The Four Major Agent Frameworks: A Practitioner's Deep Dive

LangGraph vs. CrewAI vs. AutoGen vs. OpenAI Agents SDK

I've built production systems with all four of these frameworks over the past eighteen months. Each one has cost me a weekend debugging session, each one has saved me weeks of work at different points. This isn't a surface-level feature comparison — it's the article I wish I'd had before committing to architectures that were painful to migrate away from.

The four frameworks I'm comparing are LangGraph, CrewAI, AutoGen (Microsoft's AG2), and OpenAI Agents SDK. They represent four fundamentally different philosophies about how agents should be built, and that philosophical difference matters more than any feature matrix.


The Test Task

Every comparison needs a consistent benchmark. I'll implement the same multi-step task in all four frameworks:

Topic Researcher & Reviewer: Given a topic, an agent (or set of agents) must research it using a search tool, write a concise summary, then review that summary for accuracy and completeness — revising if necessary.

This task exercises tool use, multi-step reasoning, self-correction, and structured output — the core primitives that matter in real applications.


1. Architecture

LangGraph: Explicit State Machines

LangGraph, built by the LangChain team, models agent workflows as directed graphs. You define nodes (functions that do work), edges (transitions between nodes), and a shared state object that flows through the graph.

[Start] → [Research Node] → [Write Node] → [Review Node] → {approved?}
                                                                  │
                                                    No → [Revise Node] → [Review Node]
                                                    Yes → [End]

The key insight: LangGraph treats agent control flow as a first-class citizen. You don't get an opaque loop — you get an explicit, inspectable, debuggable graph. The state is a typed dictionary you define, and every node reads from and writes to it.

# Core architectural pattern
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
import operator

class ResearchState(TypedDict):
    topic: str
    research: str
    summary: str
    review_feedback: str
    approved: bool
    revision_count: Annotated[int, operator.add]

graph = StateGraph(ResearchState)
graph.add_node("research", research_node)
graph.add_node("write", write_node)
graph.add_node("review", review_node)
graph.add_node("revise", revise_node)

What this gets you: Full visibility into every transition. You can visualize the graph, set breakpoints on nodes, replay from any state, and stream intermediate results. For production systems where debugging matters, this is enormous.

What it costs you: Verbose setup. A simple single-agent task can require 50+ lines of boilerplate. The mental model of "I'm building a state machine" doesn't come naturally if you're used to imperative code.

CrewAI: Role-Based Delegation

CrewAI models agents as team members with roles, goals, and backstories. A "Crew" is a team of agents working through a sequence of tasks. The architecture is deliberately simple: agents get tasks, tasks get executed in order (or hierarchically), and results get passed downstream.

[Crew]
  ├── Agent: Researcher (role: "Senior Research Analyst")
  │     └── Task: "Research {topic}"
  ├── Agent: Writer (role: "Technical Writer")  
  │     └── Task: "Write summary based on research"
  └── Agent: Reviewer (role: "Quality Assurance Editor")
        └── Task: "Review and approve/revise summary"

The abstraction is high-level by design. You describe who does what in natural language, and CrewAI handles the orchestration.

What this gets you: Extremely fast prototyping. The role/goal/backstory pattern produces surprisingly good results because it primes the LLM effectively. For demos and straightforward workflows, nothing is faster.

What it costs you: Limited control over execution flow. Conditional branching, loops, and error recovery are bolt-on features rather than core primitives. When workflows get complex, you're fighting the abstraction instead of leveraging it.

AutoGen: Multi-Agent Conversations

Microsoft's AutoGen (now under the AG2 umbrella) models everything as conversations between agents. Agents chat with each other, and the conversation is the computation. A GroupChat with a manager agent can orchestrate multiple specialized agents that discuss and refine work.

[GroupChat Manager]
  ├── ResearchAgent ←→ WriterAgent ←→ ReviewAgent
  │         ↕              ↕              ↕
  │      (tools)      (tools)        (tools)
  └── Conversation terminates when ReviewAgent says "APPROVED"

The radical idea: you don't write orchestration code. You define agents with system prompts and tools, put them in a room, and let them figure it out through conversation.

What this gets you: Emergent behavior. Agents can surprise you with creative solutions to problems you didn't explicitly code for. The conversation metaphor maps well to human team dynamics.

What it costs you: Non-determinism and cost. Every conversation turn is an LLM call. Conversations can spiral, repeat themselves, or go off-track. Debugging "why did the agents decide to do that?" is genuinely difficult.

OpenAI Agents SDK: Minimal Primitives

OpenAI's Agents SDK (successor to the experimental Swarm project) takes a minimalist approach. Agents are LLMs with instructions and tools. The Runner executes them, and handoffs let agents transfer control to each other.

[Runner]
  └── research_agent (tools: [search])
        → handoff to writer_agent
            → handoff to reviewer_agent  
                → handoff back to writer_agent (if revisions needed)

The architecture is intentionally thin — roughly 100 lines of core code. It's less a framework and more a structured way to call OpenAI's API with tools and multi-agent coordination.

What this gets you: Near-zero abstraction overhead. If you understand the OpenAI API, you understand the SDK. Performance is excellent because there's minimal framework overhead between your code and the API.

What it costs you: OpenAI lock-in (though the provider interface is extensible). Limited built-in support for complex graph-like workflows. You'll build your own orchestration for anything beyond sequential handoffs.


2. The Code: Same Task, Four Frameworks

Here's where theory meets reality. Let's implement our research-and-review task in each framework.

LangGraph Implementation

from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, SystemMessage
from typing import TypedDict, Annotated, Literal
import operator

class State(TypedDict):
    topic: str
    research_notes: str
    summary: str
    review_verdict: str
    revision_count: int

llm = ChatOpenAI(model="gpt-4o")

@tool
def web_search(query: str) -> str:
    """Search the web for information."""
    # In production: use Tavily, Serper, etc.
    return f"Search results for: {query}"

def research_node(state: State) -> dict:
    messages = [
        SystemMessage(content="You are a research analyst. Use the search tool to gather information."),
        HumanMessage(content=f"Research this topic thoroughly: {state['topic']}")
    ]
    response = llm.bind_tools([web_search]).invoke(messages)
    return {"research_notes": response.content}

def write_node(state: State) -> dict:
    messages = [
        SystemMessage(content="You are a technical writer. Write a clear, accurate summary."),
        HumanMessage(content=f"Based on this research, write a 200-word summary:\n\n{state['research_notes']}")
    ]
    response = llm.invoke(messages)
    return {"summary": response.content}

def review_node(state: State) -> dict:
    messages = [
        SystemMessage(content="""You are a quality reviewer. Evaluate the summary for accuracy 
        and completeness. Respond with EXACTLY 'APPROVED' if good, or provide specific 
        feedback for revision."""),
        HumanMessage(content=f"Research notes:\n{state['research_notes']}\n\nSummary:\n{state['summary']}")
    ]
    response = llm.invoke(messages)
    verdict = "approved" if "APPROVED" in response.content else "revise"
    return {"review_verdict": response.content, "revision_count": 1}

def should_continue(state: State) -> Literal["revise", "end"]:
    if state.get("revision_count", 0) >= 3:
        return "end"
    if "APPROVED" in state.get("review_verdict", ""):
        return "end"
    return "revise"

def revise_node(state: State) -> dict:
    messages = [
        SystemMessage(content="Revise the summary based on the reviewer's feedback."),
        HumanMessage(content=f"Original summary:\n{state['summary']}\n\nFeedback:\n{state['review_verdict']}")
    ]
    response = llm.invoke(messages)
    return {"summary": response.content}

# Build the graph
graph = StateGraph(State)
graph.add_node("research", research_node)
graph.add_node("write", write_node)
graph.add_node("review", review_node)
graph.add_node("revise", revise_node)

graph.set_entry_point("research")
graph.add_edge("research", "write")
graph.add_edge("write", "review")
graph.add_conditional_edges("review", should_continue, {"revise": "revise", "end": END})
graph.add_edge("revise", "review")

app = graph.compile()
result = app.invoke({"topic": "Latest developments in AI agents", "revision_count": 0})

Lines of code: ~85. LLM calls: 4-6 (depending on revisions). Transparency: Every state transition is explicit and inspectable.

CrewAI Implementation

from crewai import Agent, Task, Crew, Process
from crewai_tools import SerperDevTool

search_tool = SerperDevTool()

researcher = Agent(
    role="Senior Research Analyst",
    goal="Conduct thorough research on the given topic",
    backstory="You are an experienced researcher with a talent for finding key insights.",
    tools=[search_tool],
    llm="gpt-4o",
    verbose=True
)

writer = Agent(
    role="Technical Writer",
    goal="Write clear, concise summaries based on research",
    backstory="You are a skilled writer who excels at distilling complex topics into brief summaries.",
    llm="gpt-4o"
)

reviewer = Agent(
    role="Quality Assurance Editor",
    goal="Ensure summaries are accurate and complete",
    backstory="You are a meticulous editor who catches errors and gaps others miss.",
    llm="gpt-4o"
)

research_task = Task(
    description="Research the topic: {topic}. Gather comprehensive information.",
    expected_output="Detailed research notes with key findings and sources",
    agent=researcher
)

writing_task = Task(
    description="Based on the research, write a 200-word summary of the topic.",
    expected_output="A clear, well-structured 200-word summary",
    agent=writer,
    context=[research_task]  # Receives output from research_task
)

review_task = Task(
    description="""Review the summary for accuracy against the research. 
    If it needs revision, provide specific feedback and the writer will revise.
    If it's good, output 'REVIEW APPROVED' followed by the final summary.""",
    expected_output="Either approval with final summary, or specific revision feedback",
    agent=reviewer,
    context=[research_task, writing_task]
)

crew = Crew(
    agents=[researcher, writer, reviewer],
    tasks=[research_task, writing_task, review_task],
    process=Process.sequential,
    verbose=True
)

result = crew.kickoff(inputs={"topic": "Latest developments in AI agents"})
print(result)

Lines of code: ~55. LLM calls: 3+ (the review task may trigger implicit re-execution). Transparency: Moderate — you see task outputs but not the internal reasoning of the orchestration.

AutoGen Implementation

from autogen import ConversableAgent, GroupChat, GroupChatManager, register_function

# Define the LLM configuration
llm_config = {
    "model": "gpt-4o",
    "api_key": "your-key",
    "cache_seed": None
}

# Tool function
def web_search(query: str) -> str:
    """Search the web for information on a topic."""
    return f"Search results for: {query}"  # Replace with real search

# Create agents
researcher = ConversableAgent(
    name="Researcher",
    system_message="""You are a research analyst. Use the search tool to gather 
    information on the given topic. When done, present your findings to the Writer.
    Always say 'TERMINATE' when you've finished your research.""",
    llm_config=llm_config,
    human_input_mode="NEVER"
)

writer = ConversableAgent(
    name="Writer",
    system_message="""You are a technical writer. When you receive research from 
    the Researcher, write a 200-word summary. Present it to the Reviewer for feedback.
    If the Reviewer requests changes, revise and resubmit. Say 'TERMINATE' when 
    the summary is approved.""",
    llm_config=llm_config,
    human_input_mode="NEVER"
)

reviewer = ConversableAgent(
    name="Reviewer",
    system_message="""You are a quality assurance editor. Review the Writer's 
    summary for accuracy and completeness. If it's good, say 'APPROVED' and 
    'TERMINATE'. If it needs work, provide specific feedback to the Writer.""",
    llm_config=llm_config,
    human_input_mode="NEVER"
)

# Register the search tool with the researcher
register_function(
    web_search,
    caller=researcher,
    executor=researcher,
    name="web_search",
    description="Search the web for information"
)

# Set up the group chat
groupchat = GroupChat(
    agents=[researcher, writer, reviewer],
    messages=[],
    max_round=15,
    speaker_selection_method="auto"  # Let the manager decide who speaks
)

manager = GroupChatManager(
    groupchat=groupchat,
    llm_config=llm_config
)

# Start the conversation
researcher.initiate_chat(
    manager,
    message="Research and create a summary about: Latest developments in AI agents",
    summary_method="last_msg"
)

Lines of code: ~65. LLM calls: 8-15+ (every message in the group chat is an LLM call, including the manager's routing decisions). Transparency: Low — you see the conversation log, but understanding why the manager chose a particular speaker requires reading through potentially long transcripts.

OpenAI Agents SDK Implementation

from agents import Agent, Runner, function_tool
from agents.model_settings import ModelSettings

@function_tool
def web_search(query: str) -> str:
    """Search the web for information on a topic."""
    return f"Search results for: {query}"  # Replace with real search

research_agent = Agent(
    name="Researcher",
    instructions="""You are a research analyst. Use the search tool to gather 
    comprehensive information on the given topic. Return detailed research notes.""",
    tools=[web_search],
    model="gpt-4o"
)

writer_agent = Agent(
    name="Writer",
    instructions="""You are a technical writer. Given research notes, write a 
    clear 200-word summary. Return ONLY the summary.""",
    model="gpt-4o"
)

reviewer_agent = Agent(
    name="Reviewer",
    instructions="""You are a quality editor. Review the summary against the 
    research notes. If the summary is accurate and complete, output your final 
    assessment starting with 'APPROVED:'. If it needs work, provide specific 
    feedback starting with 'REVISE:'.""",
    model="gpt-4o"
)

async def run_research_pipeline(topic: str) -> str:
    # Step 1: Research
    research_result = await Runner.run(
        research_agent,
        input=f"Research this topic: {topic}"
    )
    research_notes = research_result.final_output
    
    # Step 2: Write
    writer_result = await Runner.run(
        writer_agent,
        input=f"Write a summary based on these research notes:\n\n{research_notes}"
    )
    summary = writer_result.final_output
    
    # Step 3: Review with revision loop
    for attempt in range(3):
        review_result = await Runner.run(
            reviewer_agent,
            input=f"Research notes:\n{research_notes}\n\nSummary to review:\n{summary}"
        )
        
        if review_result.final_output.startswith("APPROVED"):
            return summary
        
        # Extract feedback and revise
        feedback = review_result.final_output.replace("REVISE:", "").strip()
        revision_result = await Runner.run(
            writer_agent,
            input=f"Revise this summary based on feedback:\n\nSummary:\n{summary}\n\nFeedback:\n{feedback}"
        )
        summary = revision_result.final_output
    
    return summary

# Run it
import asyncio
result = asyncio.run(run_research_pipeline("Latest developments in AI agents"))

Lines of code: ~70. LLM calls: 4-8 (exactly one per Runner.run call — no hidden overhead). Transparency: Highest — the code is the orchestration. No hidden state machines, no conversation managers. You see exactly what's happening.


3. Ease of Use

Criterion LangGraph CrewAI AutoGen OpenAI Agents SDK
Time to first working demo 2-3 hours 30 minutes 1-2 hours 45 minutes
Learning curve Steep — graph concepts, state typing Gentle — role metaphors Moderate — conversation patterns Shallow — standard API patterns
Debugging experience Excellent (graph visualization, state inspection) Poor (limited introspection) Difficult (long conversation logs) Good (standard debugging tools work)
Error messages Clear, actionable Vague, often misleading Verbose, hard to parse Clear (inherited from OpenAI SDK)
Documentation quality Good, improving rapidly Adequate, some gaps Fragmented across versions Concise, well-structured

My honest take: CrewAI gets you to a demo fastest, but that speed advantage evaporates the moment you need to debug why an agent made a bad decision. LangGraph has the steepest learning curve but pays it back tenfold when you're maintaining a system in production. OpenAI Agents SDK strikes the best balance for teams already in the OpenAI ecosystem.


4. Performance & Cost

This is where framework architecture has real financial impact.

LLM Call Overhead

Task: Research → Write → Review (1 revision)

LangGraph:    5 LLM calls (research + write + review + revise + re-review)
CrewAI:       3-4 LLM calls (but internal orchestration adds hidden calls)
AutoGen:      10-15 LLM calls (each group chat message = 1+ call, manager routing = calls)
OpenAI SDK:   5 LLM calls (exactly one per Runner.run)

AutoGen's conversational approach is the most expensive by a significant margin. In my benchmarks, a typical multi-agent task costs 2-3x more in AutoGen compared to LangGraph or OpenAI SDK due to the overhead of the GroupChatManager and conversational back-and-forth.

Latency

LangGraph and OpenAI Agents SDK are the fastest because they have minimal framework overhead. CrewAI adds moderate overhead from its orchestration layer. AutoGen is the slowest due to the multi-turn conversation model.

Streaming

LangGraph supports node-level streaming — you can stream the output of each node as it executes. OpenAI Agents SDK supports standard OpenAI streaming. CrewAI has basic streaming support. AutoGen's streaming is conversation-level, which is less useful for UIs.


5. Ecosystem & Integrations

LangGraph / LangChain

The largest ecosystem by far:

  • 200+ integrations (databases, APIs, vector stores, search engines)
  • LangSmith for tracing and evaluation (genuinely excellent)
  • LangServe for deployment
  • Community-contributed templates and tools
  • First-class support for RAG, structured output, and tool use

The downside: LangChain's ecosystem has a quality variance problem. Some integrations are excellent; others are barely maintained wrappers.

CrewAI

Growing ecosystem:

  • crewai-tools package with ~30 built-in tools
  • CrewAI Enterprise for deployment
  • Template marketplace
  • Integration with LangChain tools (can use them as CrewAI tools)

The ecosystem is smaller but more opinionated, which means less choice but more consistency.

AutoGen

Microsoft-backed ecosystem:

  • AutoGen Studio for visual prototyping
  • Integration with Azure services
  • AutoGen Bench for evaluation
  • Growing community extensions

The ecosystem is fragmented between AutoGen 0.2, 0.4 (AG2), and the various experimental packages. Version confusion is a real problem.

OpenAI Agents SDK

Minimal but focused:

  • Native OpenAI tool integration (code interpreter, file search, web search built-in)
  • Tracing via OpenAI dashboard
  • Handoff and guardrail primitives
  • Third-party model support via LiteLLM/provider interface

The ecosystem is intentionally small. OpenAI's position is that the SDK should be thin and integrations should happen at the tool level.


6. Best Use Cases

Choose LangGraph When:

  • You're building a production system that needs to be debugged and maintained
  • Your workflow has complex branching (conditional paths, loops, human-in-the-loop)
  • You need state persistence (pause and resume agent execution)
  • You want fine-grained streaming for user interfaces
  • Your team values explicit control over implicit behavior
# LangGraph excels at this: interruptible, resumable workflows
from langgraph.checkpoint.sqlite import SqliteSaver

memory = SqliteSaver.from_conn_string(":memory:")
app = graph.compile(checkpointer=memory)

# Thread can be resumed after hours/days
config = {"configurable": {"thread_id": "user-123"}}
result = app.invoke({"topic": "AI agents"}, config)
# ... later ...
result = app.invoke(None, config)  # Picks up where it left off

Choose CrewAI When:

  • You're prototyping or building a demo
  • Your workflow maps naturally to team roles (researcher, writer, reviewer)
  • You want the fastest path from idea to working agent
  • The workflow is linear or simple sequential
  • Non-technical stakeholders need to understand the agent design

Choose AutoGen When:

  • You want emergent multi-agent behavior — agents debating and refining solutions
  • Your task benefits from adversarial review (e.g., code generation + code review)
  • You're building conversational AI systems where the conversation is the product
  • You're in the Microsoft/Azure ecosystem and want tight integration
  • You have budget for higher LLM call volumes and value flexibility over cost

Choose OpenAI Agents SDK When:

  • You're already using OpenAI's API and want minimal additional complexity
  • You want maximum control with minimum abstraction
  • Your agents are relatively simple (tool use + handoffs)
  • You value performance and cost efficiency
  • You prefer imperative code over declarative configurations

7. What Nobody Tells You

LangGraph's Hidden Cost

The state management is powerful but can become a maintenance burden. I've seen teams create state objects with 20+ fields, and debugging which node wrote which field at which step becomes its own challenge. Keep your state minimal.

CrewAI's Ceiling

CrewAI hits a wall around the 4-5 agent mark. The sequential process is reliable but inflexible. The hierarchical process (where a manager agent delegates) works but adds latency and cost. When I've needed more than basic orchestration, I've ended up fighting the framework.

AutoGen's Non-Determinism Problem

The same input can produce wildly different conversation paths. In testing, I've seen a GroupChat take 6 rounds one time and 14 rounds another for the same task. This makes evaluation and regression testing genuinely difficult. The max_round parameter is a blunt instrument.

OpenAI Agents SDK's Limits

The SDK is intentionally minimal, which means you'll build your own abstractions for anything non-trivial. The handoff mechanism works well for linear pipelines but gets awkward for complex routing. If you find yourself building a custom state machine on top of the SDK, you probably want LangGraph.


Summary Matrix

Dimension LangGraph CrewAI AutoGen OpenAI Agents SDK
Architecture Explicit state graph Role-based delegation Multi-agent conversation Minimal runner + handoffs
Best for Production systems Prototyping Emergent behavior Simple agent pipelines
Cost efficiency High Medium Low Highest
Debugging Excellent Poor Difficult Good
Learning curve Steep Gentle Moderate Shallow
Ecosystem Largest Growing Fragmented Minimal
Vendor lock-in Low (multi-model) Low (multi-model) Low (multi-model) Medium (OpenAI-first)
Production readiness High Medium Medium High
Multi-agent support Manual (you build it) Built-in (sequential/hierarchical) Built-in (conversation) Built-in (handoffs)

My Recommendation

If I'm starting a new production project today and need agents that work reliably at scale: LangGraph. The upfront investment in learning the graph model pays dividends in debuggability and maintainability.

If I'm building a quick prototype to validate an idea: CrewAI. Nothing gets you to a working demo faster.

If I need a lightweight agent with tools and handoffs: OpenAI Agents SDK. The minimal abstraction means fewer surprises.

If I want to explore what happens when agents argue with each other: AutoGen. Just set a budget alert first.

The honest truth is that no framework has won yet. The agent space is moving fast enough that the landscape will look different in six months. Pick the one that matches your current constraints, but design your application so the agent framework is a swappable component — not the foundation of your architecture.

Keywords

AI agentcoding-agents