CrewAI vs LangGraph
Multi-agent orchestration vs stateful workflows
Overview
Multi-agent orchestration vs stateful workflows
Verdict
Multi-agent orchestration vs stateful workflows
Details
CrewAI vs LangGraph
Overview
A detailed comparison of two popular AI orchestration frameworks: CrewAI (by CrewAI Inc.) and LangGraph (by LangChain). Both enable building sophisticated AI agent systems, but with fundamentally different approaches to orchestration.
Quick Comparison Table
| Feature | CrewAI | LangGraph |
|---|---|---|
| Maintainer | CrewAI Inc. | LangChain |
| Paradigm | Role-based multi-agent teams | Stateful workflow graphs |
| Agent Definition | YAML/Python with roles, goals, backstories | TypedDict state + node functions |
| Process Types | Sequential, Hierarchical, Consensual | Custom graph with any topology |
| State Management | Task output chaining | Explicit TypedDict state |
| Human-in-the-Loop | Via callbacks and custom logic | Built-in interrupt/resume |
| Persistence | Limited (via custom implementation) | Built-in checkpointers |
| Streaming | Limited | Native streaming support |
| Multi-Agent | Role-based delegation | Graph nodes as specialized functions |
| Learning Curve | Low (declarative) | Moderate (graph concepts) |
| Best For | Content creation, research workflows | Complex pipelines, production systems |
Philosophy
CrewAI
"Multi-agent orchestration for humans"
CrewAI treats AI systems as teams of specialists:
- Agents have roles, goals, and backstories
- Tasks are assigned based on capabilities
- Process manages execution order
- Focus on natural language workflows
from crewai import Agent, Task, Crew, Process
researcher = Agent(
role='Senior Research Analyst',
goal='Uncover cutting-edge developments in AI',
backstory='You are a senior research analyst...',
)
writer = Agent(
role='Technical Content Writer',
goal='Create compelling content about AI',
backstory='You are a technical writer...',
)
research_task = Task(
description='Research AI agent frameworks',
expected_output='A detailed research report',
agent=researcher,
)
write_task = Task(
description='Write an article based on research',
expected_output='A well-structured article',
agent=writer,
context=[research_task],
)
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, write_task],
process=Process.sequential,
)
result = crew.kickoff()
LangGraph
"Stateful, multi-actor applications with LLMs"
LangGraph treats AI systems as computational graphs:
- Nodes represent actions (LLM calls, tools, etc.)
- Edges define control flow
- State flows through the graph
- Explicit, deterministic execution
from langgraph.graph import StateGraph, END
from typing import TypedDict, List
from langchain_core.messages import BaseMessage
class AgentState(TypedDict):
messages: List[BaseMessage]
research: dict
draft: str
iteration: int
def research_node(state: AgentState) -> dict:
# Perform research
return {"research": results, "iteration": state["iteration"] + 1}
def write_node(state: AgentState) -> dict:
# Write based on research
return {"draft": article}
builder = StateGraph(AgentState)
builder.add_node("research", research_node)
builder.add_node("write", write_node)
builder.add_edge("research", "write")
builder.set_entry_point("research")
builder.add_edge("write", END)
workflow = builder.compile()
result = workflow.invoke({"messages": [], "iteration": 0})
Agent Definition
CrewAI
Declarative agent definition:
# agents.yaml
researcher:
role: >
Senior Research Analyst
goal: >
Uncover cutting-edge developments in {topic}
backstory: >
You're a senior research analyst with deep expertise in {topic}.
You have a knack for identifying emerging trends...
verbose: true
allow_delegation: false
# Load and create
with open("config/agents.yaml") as f:
agents_config = yaml.safe_load(f)
researcher = Agent(
config=agents_config["researcher"],
llm=ChatOpenAI(model="gpt-4o"),
tools=[search_tool],
verbose=True,
)
Features:
- YAML configuration
- Role-based identity
- Goal-driven behavior
- Backstory for personality
- Delegation support
LangGraph
Programmatic node definition:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(model="gpt-4o")
async def research_node(state: AgentState) -> dict:
prompt = ChatPromptTemplate.from_messages([
("system", "You are a research assistant."),
("human", "Research: {topic}"),
])
chain = prompt | llm
response = await chain.ainvoke({"topic": state["topic"]})
return {"research": {"content": response.content}}
Features:
- Full Python control
- Type-safe state
- Custom logic per node
- Dependency injection
- Async support
Process Management
CrewAI
Three built-in process types:
# Sequential - tasks run in order
crew = Crew(
agents=[researcher, writer, editor],
tasks=[research_task, writing_task, editing_task],
process=Process.sequential,
)
# Hierarchical - manager agent delegates
crew = Crew(
agents=[researcher, writer, editor, manager],
tasks=[research_task, writing_task, editing_task],
process=Process.hierarchical,
manager_llm=ChatOpenAI(model="gpt-4o"),
)
# Consensual - all agents contribute
crew = Crew(
agents=[agent1, agent2, agent3],
tasks=[task1, task2, task3],
process=Process.consensual,
)
LangGraph
Custom graph topology:
from langgraph.graph import StateGraph, START, END
from typing import Literal
def should_continue(state: AgentState) -> Literal["write", "finalize"]:
if state["iteration"] >= 3:
return "finalize"
return "write"
builder = StateGraph(AgentState)
builder.add_node("research", research_node)
builder.add_node("write", write_node)
builder.add_node("finalize", finalize_node)
builder.add_edge(START, "research")
builder.add_edge("research", "write")
builder.add_conditional_edges("write", should_continue, {
"write": "write",
"finalize": "finalize",
})
builder.add_edge("finalize", END)
workflow = builder.compile()
Features:
- Any graph topology
- Conditional edges
- Parallel branches
- Loops and cycles
- Dynamic routing
State Management
CrewAI
Task output chaining:
research_task = Task(
description='Research AI frameworks',
expected_output='A detailed research report',
agent=researcher,
output_file='outputs/research.md',
)
writing_task = Task(
description='Write article based on research',
expected_output='A well-structured article',
agent=writer,
context=[research_task], # Access research output
output_file='outputs/article.md',
)
editing_task = Task(
description='Edit article for quality',
expected_output='A polished, publication-ready article',
agent=editor,
context=[writing_task], # Access draft
output_file='outputs/final.md',
)
LangGraph
Explicit TypedDict state:
from typing import TypedDict, Annotated, List
from langgraph.graph import add_messages
class AgentState(TypedDict):
messages: Annotated[List[BaseMessage], add_messages]
topic: str
research: dict
draft: str
iteration: int
def research_node(state: AgentState) -> dict:
# Read from state
topic = state["topic"]
# Write to state
return {
"research": results,
"iteration": state["iteration"] + 1,
}
def write_node(state: AgentState) -> dict:
# Access research from state
research = state["research"]
return {"draft": article}
Human-in-the-Loop
CrewAI
Via callbacks and custom logic:
def approval_callback(output: str, task: Task):
"""Custom approval callback."""
print(f"Task output: {output}")
# Implement approval logic
return output
task = Task(
description='Write article',
expected_output='An article',
agent=writer,
callback=approval_callback,
)
# Or use hierarchical process with manager review
crew = Crew(
agents=[researcher, writer, editor, manager],
tasks=[research_task, writing_task, editing_task],
process=Process.hierarchical,
manager_llm=ChatOpenAI(model="gpt-4o"),
)
LangGraph
Built-in interrupt and resume:
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
workflow = builder.compile(checkpointer=checkpointer)
# Interrupt before a node
builder.interrupt_before("review")
# Run workflow
config = {"configurable": {"thread_id": "session-1"}}
workflow.invoke(input_data, config=config)
# Check state
snapshot = workflow.get_state(config)
if snapshot.next:
# Human needs to review
print("Waiting for human approval...")
# Resume after approval
workflow.invoke(None, config=config)
# Time travel
history = workflow.get_state_history(config)
for checkpoint in history:
print(f"Checkpoint: {checkpoint.values}")
Persistence
CrewAI
Limited built-in support:
# Custom persistence via output files
task = Task(
output_file='outputs/research.md',
)
# Or custom implementation
import json
from crewai import Crew
class PersistentCrew(Crew):
def save_state(self, path: str):
state = {
"agents": [a.role for a in self.agents],
"tasks": [t.description for t in self.tasks],
}
with open(path, 'w') as f:
json.dump(state, f)
def load_state(self, path: str):
with open(path) as f:
state = json.load(f)
# Reconstruct crew
LangGraph
Built-in checkpointers:
# In-memory (development)
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
# SQLite (persistent)
from langgraph.checkpoint.sqlite import SqliteSaver
checkpointer = SqliteSaver.from_conn_string("checkpoints.db")
# PostgreSQL (production)
from langgraph.checkpoint.postgres import PostgresSaver
checkpointer = PostgresSaver(conn)
workflow = builder.compile(checkpointer=checkpointer)
# Persistence is automatic
config = {"configurable": {"thread_id": "session-1"}}
workflow.invoke(input_data, config=config)
# State persists across runs
snapshot = workflow.get_state(config)
Streaming
CrewAI
Limited streaming support:
# Stream via verbose output
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, writing_task],
verbose=True,
)
result = crew.kickoff()
# Verbose output shows progress
LangGraph
Native streaming support:
# Stream updates
async for event in workflow.astream(input_data, config=config, stream_mode="updates"):
for node_name, node_output in event.items():
print(f"[{node_name}] {node_output}")
yield node_output
# Stream state
async for state in workflow.astream(input_data, config=config, stream_mode="values"):
print(f"Step: {state['iteration']}")
yield state
# Stream custom
async for event in workflow.astream(input_data, config=config, stream_mode="custom"):
yield event
Multi-Agent Patterns
CrewAI
Role-based team:
# Define specialized agents
researcher = Agent(
role='Research Specialist',
goal='Find accurate information',
backstory='Expert researcher...',
)
writer = Agent(
role='Content Writer',
goal='Create engaging content',
backstory='Professional writer...',
)
editor = Agent(
role='Content Editor',
goal='Ensure quality and accuracy',
backstory='Meticulous editor...',
)
seo_agent = Agent(
role='SEO Specialist',
goal='Optimize for search',
backstory='SEO expert...',
)
# Assemble crew
crew = Crew(
agents=[researcher, writer, editor, seo_agent],
tasks=[research_task, writing_task, editing_task, seo_task],
process=Process.sequential,
memory=True, # Enable agent memory
cache=True, # Enable caching
)
LangGraph
Specialized nodes:
# Define node functions
async def researcher_node(state: AgentState) -> dict:
"""Research step."""
results = await perform_research(state["topic"])
return {"research": results}
async def writer_node(state: AgentState) -> dict:
"""Writing step."""
draft = await write_content(state["research"])
return {"draft": draft}
async def editor_node(state: AgentState) -> dict:
"""Editing step."""
edited = await edit_content(state["draft"])
return {"draft": edited}
async def seo_node(state: AgentState) -> dict:
"""SEO optimization."""
optimized = await optimize_seo(state["draft"])
return {"draft": optimized}
# Build graph
builder = StateGraph(AgentState)
builder.add_node("research", researcher_node)
builder.add_node("write", writer_node)
builder.add_node("edit", editor_node)
builder.add_node("seo", seo_node)
builder.add_edge("research", "write")
builder.add_edge("write", "edit")
builder.add_edge("edit", "seo")
workflow = builder.compile()
Tool Integration
CrewAI
Built-in and custom tools:
from crewai_tools import tool
from langchain_community.tools import DuckDuckGoSearchRun
from crewai_tools import LangChainTool
# Built-in tool
search_tool = LangChainTool(
tool=DuckDuckGoSearchRun(),
name="DuckDuckGo Search",
description="Search the web for information",
)
# Custom tool
@tool
def fetch_webpage(url: str) -> str:
"""Fetch and return the content of a webpage."""
import requests
response = requests.get(url)
return response.text
# Add to agent
researcher = Agent(
config=agents_config["researcher"],
tools=[search_tool, fetch_webpage],
)
LangGraph
LangChain tools:
from langchain.tools import tool
from langchain_community.tools import DuckDuckGoSearchRun
@tool
def fetch_webpage(url: str) -> str:
"""Fetch and return the content of a webpage."""
import requests
response = requests.get(url)
return response.text
# Add to agent
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent
tools = [fetch_webpage, DuckDuckGoSearchRun()]
agent = create_tool_calling_agent(llm, tools, prompt)
Error Handling
CrewAI
Callback-based error handling:
def error_callback(error: Exception, task: Task):
print(f"Task {task.description} failed: {error}")
# Implement retry logic or fallback
research_task = Task(
config=tasks_config["research_task"],
agent=researcher,
callback=error_callback,
)
# Or use try-except
try:
result = crew.kickoff()
except Exception as e:
print(f"Crew failed: {e}")
LangGraph
Node-level error handling:
from langgraph.errors import NodeInterrupt
async def resilient_node(state: AgentState) -> dict:
max_retries = 3
for attempt in range(max_retries):
try:
result = await perform_action()
return {"result": result}
except Exception as e:
if attempt == max_retries - 1:
raise
await asyncio.sleep(2 ** attempt)
# Or use edges for error routing
def on_error(error: Exception) -> str:
return "error_handler"
builder.add_conditional_edges("node", should_handle, {
"success": "next_node",
"error": "error_handler",
})
Best Use Cases
Choose CrewAI When:
- ✅ Content creation workflows: Blog posts, articles, reports
- ✅ Research pipelines: Multi-step research processes
- ✅ Role-based teams: Clear agent roles and responsibilities
- ✅ Rapid prototyping: Quick setup with YAML configuration
- ✅ Non-technical users: Declarative, easy to understand
- ✅ Sequential processes: Linear task execution
Choose LangGraph When:
- ✅ Complex workflows: Non-linear, conditional execution
- ✅ Production systems: Persistence, streaming, error handling
- ✅ State management: Explicit state tracking and debugging
- ✅ Human-in-the-loop: Interrupts, approvals, time travel
- ✅ Custom topologies: Any graph structure needed
- ✅ Testing: Deterministic, testable workflows
Performance Comparison
| Metric | CrewAI | LangGraph |
|---|---|---|
| Setup time | Fast (declarative) | Moderate (programmatic) |
| Execution speed | Similar (depends on LLM) | Similar (depends on LLM) |
| Memory usage | Low | Low-Moderate |
| Scalability | Good for linear workflows | Excellent for complex workflows |
| Debugging | Verbose output | State inspection, tracing |
Migration Guide
From CrewAI to LangGraph
# CrewAI
researcher = Agent(role='Researcher', ...)
writer = Agent(role='Writer', ...)
crew = Crew(agents=[researcher, writer], tasks=[r_task, w_task])
result = crew.kickoff()
# LangGraph
class State(TypedDict):
research: dict
draft: str
builder = StateGraph(State)
builder.add_node("research", research_node)
builder.add_node("write", write_node)
builder.add_edge("research", "write")
workflow = builder.compile()
result = workflow.invoke({"research": {}, "draft": ""})
From LangGraph to CrewAI
# LangGraph
builder = StateGraph(State)
builder.add_node("research", research_node)
workflow = builder.compile()
result = workflow.invoke(input)
# CrewAI
researcher = Agent(role='Researcher', ...)
research_task = Task(description='Research...', agent=researcher)
crew = Crew(agents=[researcher], tasks=[research_task])
result = crew.kickoff()
Conclusion
Both frameworks are excellent for building AI agent systems. The choice depends on your needs:
- CrewAI excels at role-based multi-agent teams with declarative configuration, ideal for content creation and research workflows.
- LangGraph excels at complex stateful workflows with explicit control flow, ideal for production systems requiring persistence and human-in-the-loop.
For rapid prototyping and content workflows, I recommend CrewAI. For production systems requiring reliability and control, LangGraph is the better choice.
Resources
- CrewAI Documentation: https://docs.crewai.com/
- CrewAI GitHub: https://github.com/crewAIInc/crewAI
- LangGraph Documentation: https://langchain-ai.github.io/langgraph/
- LangGraph GitHub: https://github.com/langchain-ai/langgraph
Last updated: May 2026
