LA

LangGraph

17,000PythonStateful

Build stateful, multi-agent applications with LLMs using a graph-based approach.

PythonLangChainStateful

Overview

LangGraph is a library for building stateful, multi-actor applications with LLMs, used to create agent and multi-agent workflows. Built on top of LangChain, it provides a graph-based paradigm where each node represents a step in the workflow.

Features

  • Graph-based workflow definition
  • Persistent state management
  • Time-travel debugging
  • Human-in-the-loop support
  • Streaming and interrupt capabilities

Installation

pip install langgraph

Pros

  • +Powerful state management
  • +Great debugging tools
  • +Deep LangChain integration
  • +Production-ready

Cons

  • Tied to LangChain ecosystem
  • Complex for simple use cases
  • Requires understanding of graphs

Alternatives

Documentation

LangGraph Documentation

Introduction

LangGraph is a library for building stateful, multi-actor applications with LLMs. It extends LangChain with graph-based workflow definition, enabling you to build sophisticated AI agent systems with explicit state management and control flow.

Installation

pip install langgraph

Quick Start

Define Your State

from typing import TypedDict, Annotated
import operator

class State(TypedDict):
    messages: Annotated[list, operator.add]

Build Your Graph

from langgraph.graph import StateGraph, END

def node_1(state: State):
    return {"messages": ["Node 1 message"]}

def node_2(state: State):
    return {"messages": ["Node 2 message"]}

builder = StateGraph(State)
builder.add_node("node_1", node_1)
builder.add_node("node_2", node_2)
builder.add_edge("node_1", "node 2")
builder.set_entry_point("node_1")
builder.set_finish_point("node_2")
graph = builder.compile()

Run Your Graph

result = graph.invoke({"messages": []})
print(result["messages"])

Conditional Edges

def should_continue(state: State) -> str:
    if len(state["messages"]) > 5:
        return END
    return "node_2"

builder.add_conditional_edges("node_1", should_continue)

Human-in-the-Loop

from langgraph.checkpoint.memory import MemorySaver

memory = MemorySaver()
graph = builder.compile(checkpointer=memory)

# Interrupt before a node
config = {"configurable": {"thread_id": "1"}}
for event in graph.stream({"messages": []}, config, interrupt_before=["node_2"]):
    pass

# Resume after human approval
graph.invoke(None, config)

Time-Travel Debugging

# Get all checkpoints
checkpoints = list(memory.list({"thread_id": "1"}))

# Get a specific checkpoint
checkpoint = memory.get({"thread_id": "1"}, checkpoint_id)

# Restore to a previous state
graph.invoke(None, {"configurable": {"thread_id": "1", "checkpoint_id": checkpoint["id"]}})

Use Cases

LangGraph excels in scenarios requiring explicit state management and complex control flow:

Use CaseWhy LangGraph
Production AI PipelinesExplicit state, persistence, and error handling
Agent with Human ApprovalBuilt-in interrupt/resume for human-in-the-loop
Multi-Step Research WorkflowsConditional branching based on intermediate results
Stateful Chat ApplicationsPersistent conversation state across sessions
Complex Decision TreesConditional edges for dynamic routing

Pros & Cons

✅ Pros

  • Explicit state management — TypedDict-based state is type-safe and debuggable
  • Native persistence — Built-in checkpointers (memory, SQLite, PostgreSQL)
  • Time-travel debugging — Rewind to any checkpoint for debugging
  • Advanced human-in-the-loop — Interrupt before/after any node, resume with approval
  • Native streaming — Stream events, values, or updates in real-time
  • Parallel execution — Support for concurrent node execution
  • Production-ready — Battle-tested by LangChain team, enterprise-grade
  • Rich ecosystem — Integrates with LangChain tools, models, and memory

❌ Cons

  • Steeper learning curve — Graph concepts (nodes, edges, conditional edges) take time
  • More boilerplate — Requires explicit state definition and node functions
  • Python-focused — Primary language is Python (LangChain.js is less mature)
  • LangChain dependency — Tightly coupled to LangChain ecosystem
  • Overkill for simple tasks — Simpler frameworks may suffice for basic agents

Comparison with Alternatives

FeatureLangGraphCrewAIAutoGen
ParadigmStateful graphsRole-based teamsConversation-centric
StateExplicit TypedDictImplicit (messages)Implicit (conversation)
Control FlowExplicit edgesSequential/hierarchicalImplicit via messages
PersistenceBuilt-inExternalConversation history
StreamingNativeLimitedBasic
Human-in-the-loopAdvanced (interrupt/resume)BasicBuilt-in (human proxy)
Learning curveMedium-HighLow-MediumLow
Best forProduction workflowsContent pipelinesConversations

Real-World Examples

Example 1: Research Agent with Human Approval

from langgraph.graph import StateGraph, END, MessagesState
from langgraph.checkpoint.memory import MemorySaver

# Define a research workflow with human review
async def research_node(state: MessagesState):
    # Call LLM to research a topic
    response = await llm.ainvoke([("user", state["messages"][-1].content)])
    return {"messages": [response]}

async def review_node(state: MessagesState):
    # Human reviews the research output
    # Returns "approve" or "revise"
    decision = await human_review(state["messages"][-1].content)
    return {"decision": decision}

builder = StateGraph(MessagesState)
builder.add_node("research", research_node)
builder.add_node("review", review_node)

builder.add_edge("research", "review")
builder.add_conditional_edges(
    "review",
    lambda state: "approve" if state["decision"] == "approve" else "research",
    {"approve": END, "revise": "research"}
)

memory = MemorySaver()
graph = builder.compile(checkpointer=memory)

Example 2: Multi-Agent Research Team

from langgraph.graph import StateGraph, END
from langgraph.graph.state import CompiledStateGraph

class ResearchState(TypedDict):
    topic: str
    research: dict
    draft: str
    review: dict
    final: str

async def researcher_node(state: ResearchState):
    # Conduct research
    research = await conduct_research(state["topic"])
    return {"research": research}

async def writer_node(state: ResearchState):
    # Write draft based on research
    draft = await write_article(state["research"])
    return {"draft": draft}

async def editor_node(state: ResearchState):
    # Edit and improve the draft
    review = await edit_draft(state["draft"])
    return {"review": review}

async def finalizer_node(state: ResearchState):
    # Final polish
    final = await finalize(state["draft"], state["review"])
    return {"final": final}

builder = StateGraph(ResearchState)
builder.add_node("researcher", researcher_node)
builder.add_node("writer", writer_node)
builder.add_node("editor", editor_node)
builder.add_node("finalizer", finalizer_node)

builder.add_edge("researcher", "writer")
builder.add_edge("writer", "editor")
builder.add_edge("editor", "finalizer")
builder.add_edge("finalizer", END)

graph = builder.compile()

Best Practices

  1. Define clear state schemas — Use TypedDict for type safety and documentation.
  2. Use checkpointer for persistence — Enable time-travel debugging and recovery.
  3. Keep nodes focused — Each node should do one thing well.
  4. Use conditional edges for branching — Dynamic workflow paths based on state.
  5. Leverage streaming — Stream intermediate results for better UX.
  6. Test nodes independently — Each node is a pure function, easy to unit test.
  7. Use memory checkpointer in dev, persistent in prod — SQLite/Postgres for production.

Troubleshooting

IssueSolution
State not persistingEnsure checkpointer is passed to compile()
Node not executingCheck edge connections and entry/finish points
Conditional edge not workingVerify return value matches branch names
Memory issuesUse MemorySaver for dev, SqliteSaver for prod
Streaming not workingUse astream() instead of ainvoke()

Resources


Last updated: June 2026