LangGraph vs CrewAI vs AutoGen: Building the Same Pipeline Three Ways
The agent framework landscape is a mess of overlapping abstractions. Every week there's a new "best way to build AI agents" post that compares feature matrices and GitHub stars without ever building anything real. Rather than doing that, I built the same thing in all three -- LangGraph, CrewAI, and AutoGen -- and compared the experience hands-on.
Here's what I found.
The Task
I wanted something simple enough to finish in an afternoon but complex enough to expose the design philosophy of each framework. The task: a research-and-report pipeline. Given a topic, the system should search the web for relevant information, summarize the findings, and produce a structured markdown report with sections, citations, and a conclusion.
Three stages. Two agent roles (researcher and writer). One output format. Straightforward, but it forces each framework to reveal how it handles state, communication, and control flow.
I used the same LLM (GPT-4o) and the same search tool (Tavily) across all three implementations to keep the comparison fair. Each framework handles function calling differently, and that's one of the key variables the comparison reveals.
LangGraph
LangGraph thinks in graphs. You define nodes (functions that do work), edges (transitions between nodes), and state (a typed dictionary that flows through the graph). If you've ever built a state machine or drawn a workflow diagram, this will feel instantly familiar.
Here's the core of my pipeline:
from langgraph.graph import StateGraph, END
from typing import TypedDict
class ResearchState(TypedDict):
topic: str
search_results: list[str]
summary: str
report: str
def search_node(state: ResearchState) -> dict:
results = tavily_search(state["topic"])
return {"search_results": results}
def summarize_node(state: ResearchState) -> dict:
summary = llm.invoke(
f"Summarize these findings:\n{state['search_results']}"
)
return {"summary": summary.content}
def write_report_node(state: ResearchState) -> dict:
report = llm.invoke(
f"Write a structured report on {state['topic']}.\n"
f"Summary: {state['summary']}"
)
return {"report": report.content}
graph = StateGraph(ResearchState)
graph.add_node("search", search_node)
graph.add_node("summarize", summarize_node)
graph.add_node("write_report", write_report_node)
graph.set_entry_point("search")
graph.add_edge("search", "summarize")
graph.add_edge("summarize", "write_report")
graph.add_edge("write_report", END)
app = graph.compile()
result = app.invoke({"topic": "quantum computing breakthroughs 2025"})What I liked: Total control. Every transition is explicit. I can see exactly what happens and in what order. Adding conditional branching -- like routing to a different node if the search returns no results -- is just adding an edge with a condition function. Debugging is straightforward because the graph execution is deterministic and traceable.
What I didn't like: Boilerplate. For a simple linear pipeline, defining nodes, edges, entry points, and typed state feels like overkill. The TypedDict state management is powerful but verbose. You're writing infrastructure code before you write any agent logic.
Best for: Complex workflows with conditional branching, loops, human-in-the-loop steps, or any situation where you need to reason about control flow explicitly. If your pipeline has a "it depends" branch, LangGraph handles it cleanly.
CrewAI
CrewAI thinks in roles. You define agents with backstories and goals, assign them tasks, and assemble a crew. The metaphor is a team of specialists working together. It's the most intuitive of the three if you think about work in terms of delegation.
Here's the same pipeline:
from crewai import Agent, Task, Crew, Process
researcher = Agent(
role="Senior Research Analyst",
goal="Find comprehensive information on the given topic",
backstory="You are an expert researcher who excels at finding "
"and synthesizing information from multiple sources.",
tools=[tavily_tool],
llm=llm,
)
writer = Agent(
role="Technical Report Writer",
goal="Produce a well-structured markdown report from research findings",
backstory="You are a skilled technical writer who transforms "
"raw research into clear, structured reports.",
llm=llm,
)
research_task = Task(
description="Research the topic: quantum computing breakthroughs 2025. "
"Find key developments, major players, and recent papers.",
expected_output="A comprehensive list of findings with sources.",
agent=researcher,
)
writing_task = Task(
description="Write a structured markdown report based on the research findings. "
"Include sections, citations, and a conclusion.",
expected_output="A complete markdown report ready for publication.",
agent=writer,
)
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, writing_task],
process=Process.sequential,
)
result = crew.kickoff()What I liked: Speed to prototype. I went from zero to working pipeline in about 20 minutes. The role/task abstraction maps cleanly to how you'd actually describe the work to a human. The backstory field is surprisingly effective at shaping agent behavior without prompt engineering gymnastics.
What I didn't like: Less control over what happens between tasks. The framework manages inter-agent communication, and sometimes the writer agent didn't receive the context I expected from the researcher. Debugging meant reading through verbose logs to figure out what information was actually passed. The abstraction is great until you need to peek behind it.
Best for: Straightforward multi-agent pipelines where the workflow is linear or lightly branching. If you can describe your system as "person A does X, then person B does Y," CrewAI gets you there fast.
AutoGen
AutoGen thinks in conversations. Agents are participants in a group chat, and work gets done through message exchanges. It's the most open-ended of the three -- you define who's in the room and what they're supposed to do, then let the conversation unfold.
Here's the setup:
from autogen import AssistantAgent, UserProxyAgent, GroupChat, GroupChatManager
researcher = AssistantAgent(
name="Researcher",
system_message="You are a research specialist. When given a topic, "
"use available tools to search for information and "
"present your findings clearly.",
llm_config=llm_config,
)
writer = AssistantAgent(
name="Writer",
system_message="You are a technical report writer. Take research findings "
"from the Researcher and produce a structured markdown report "
"with sections, citations, and a conclusion.",
llm_config=llm_config,
)
user_proxy = UserProxyAgent(
name="Admin",
human_input_mode="NEVER",
code_execution_config={"work_dir": "output"},
)
group_chat = GroupChat(
agents=[user_proxy, researcher, writer],
messages=[],
max_round=10,
)
manager = GroupChatManager(groupchat=group_chat, llm_config=llm_config)
user_proxy.initiate_chat(
manager,
message="Research quantum computing breakthroughs in 2025 "
"and produce a structured markdown report.",
)What I liked: Emergent collaboration. The researcher and writer naturally iterated -- the writer would ask for clarification, the researcher would provide additional detail, and the final report was often richer than what the other frameworks produced. The conversational pattern allows agents to course-correct in ways that rigid pipelines can't.
What I didn't like: Unpredictability. Sometimes the conversation went off track. Sometimes the agents repeated themselves. Sometimes the writer started doing research instead of waiting for the researcher. The max_round parameter is a blunt instrument for controlling conversation length. I spent more time tuning system messages and stop conditions than I did writing actual pipeline logic.
Best for: Open-ended exploration, brainstorming, or tasks where you genuinely want agents to iterate and refine each other's work. Research synthesis, creative writing, and problem-solving benefit from the conversational loop. Production pipelines with SLAs do not.
The Verdict
After building the same thing three times, here's my honest ranking by use case:
LangGraph wins for production systems. If I'm building something that needs to be reliable, debuggable, and maintainable, I'm reaching for LangGraph every time. The graph abstraction is verbose but it's explicit. I can write tests for individual nodes. I can trace execution. I can add error handling at specific points. It's a state machine with LLM calls, and that's exactly what production multi-agent systems need.
CrewAI wins for prototyping. If I'm exploring a new idea or building a demo, CrewAI gets me there fastest. The mental model is so natural that I can explain my system to a non-technical person by describing the agents and their roles. The trade-off is control -- you're trusting the framework to handle the details.
AutoGen wins for research and exploration. If the task is genuinely open-ended and I want agents to surprise me with their collaboration, AutoGen's conversational model produces the most interesting results. But "interesting" and "reliable" are different goals.
None of them is universally best. The right choice depends on where you are in the build cycle and what you're optimizing for.
What Actually Matters
Here's the thing nobody in the framework debate wants to hear: the framework matters less than your eval pipeline. I've seen terrible systems built with great frameworks and great systems built with raw API calls and a for loop.
The questions that actually determine success are: How do you measure output quality? How do you catch regressions? How do you handle the 10% of inputs where the LLM does something unexpected? Those questions are framework-agnostic, and I covered a systematic approach to answering them in my post on evaluating AI agents.
Pick one. Build fast. Measure rigorously. If your eval suite is solid, switching frameworks later is a weekend of refactoring. If your eval suite is nonexistent, no framework will save you.
Related Posts
Custom Commands and Slash Commands: Building Your Own Claude Code CLI
Slash commands turn Claude Code into a personalized CLI. A markdown file becomes a reusable workflow you invoke with a single slash. Here's how to build them.
NotebookLM from the Terminal: Querying Your Docs with Claude Code
A Claude Code skill that queries Google NotebookLM notebooks directly from the terminal. Source-grounded answers from Gemini, with citations, without opening a browser.
I Track Calories and Plan Groceries from My Terminal
Claude Code isn't just for writing software. I built skills that track nutrition and automate grocery shopping at Wegmans, all from the terminal.