Skip to content

LangGraph

LangGraph is a framework for building stateful, multi-step agent workflows as directed graphs. Part of the LangChain ecosystem but focused on complex agent orchestration with explicit control flow, typed state, and native support for cycles.

Key Facts

  • Agents are defined as graphs: nodes (functions) connected by edges (transitions)
  • State is a typed dictionary passed between nodes
  • Supports conditional routing, cycles (retry loops), and parallel execution
  • Built-in interrupt nodes for human-in-the-loop
  • More control than LangChain agents but requires explicit graph design

LangGraph vs LangChain Agents

Feature LangChain Agents LangGraph
Control flow Implicit (LLM decides) Explicit (graph defines)
State management Limited (memory) Rich (typed state dict)
Debugging Hard (agent decides path) Clear (follow graph edges)
Cycles/loops Limited Native support
Human-in-the-loop Manual Built-in interrupt nodes

Core Concepts

State

from typing import TypedDict
from langgraph.graph import StateGraph

class AgentState(TypedDict):
    messages: list
    next_step: str
    results: dict

Nodes

Functions that receive state, process it, and return modified state:

def research_node(state: AgentState) -> AgentState:
    result = search_tool.invoke(state["messages"][-1])
    state["results"]["research"] = result
    return state

def analyze_node(state: AgentState) -> AgentState:
    analysis = llm.invoke(f"Analyze: {state['results']['research']}")
    state["results"]["analysis"] = analysis
    return state

Edges (including conditional)

graph = StateGraph(AgentState)
graph.add_node("research", research_node)
graph.add_node("analyze", analyze_node)
graph.add_node("respond", respond_node)

# Simple edge
graph.add_edge("analyze", "respond")

# Conditional routing
graph.add_conditional_edges(
    "research",
    route_function,  # returns node name based on state
    {"needs_analysis": "analyze", "ready": "respond"}
)

Compilation and Execution

app = graph.compile()
result = app.invoke({
    "messages": [user_message],
    "next_step": "research",
    "results": {}
})

Patterns

Human-in-the-Loop

def human_review_node(state):
    action = state["proposed_action"]
    approval = get_human_approval(action)
    state["approved"] = approval
    return state

graph.add_node("propose", agent_propose)
graph.add_node("review", human_review_node)
graph.add_node("execute", agent_execute)

graph.add_edge("propose", "review")
graph.add_conditional_edges(
    "review",
    lambda s: "execute" if s["approved"] else "propose",
    {"execute": "execute", "propose": "propose"}
)

Retry Loop

def should_retry(state):
    if state["quality_score"] < 0.8 and state["attempts"] < 3:
        return "retry"
    return "done"

graph.add_conditional_edges("evaluate", should_retry, {
    "retry": "generate",
    "done": "output"
})

Multi-Agent Graph

# Supervisor routes to specialized agents
graph.add_node("supervisor", supervisor_node)
graph.add_node("researcher", research_agent)
graph.add_node("writer", writing_agent)

graph.add_conditional_edges(
    "supervisor",
    lambda s: s["next_agent"],
    {"research": "researcher", "write": "writer", "done": END}
)
graph.add_edge("researcher", "supervisor")
graph.add_edge("writer", "supervisor")

When to Use LangGraph

  • Multi-step workflows with conditional branching
  • Agents needing explicit state management
  • Human-in-the-loop approval steps
  • Complex multi-agent orchestration
  • Workflows requiring retry logic or iterative refinement
  • When you need to debug exactly which path the agent took

Gotchas

  • Graph design requires upfront planning - more work than a simple ReAct agent
  • State must be serializable for persistence/checkpointing
  • Conditional edge functions must handle all possible state values
  • Cycles without termination conditions create infinite loops
  • Debugging requires understanding the full graph structure, not just individual nodes

See Also