Chapter 56 min read

Chapter 5: Building Real Graphs (Topology)

In the previous chapters, we mastered the State Layer. We now know exactly how data travels through our application, how it is validated, and how it is merged using Overwrites and Reducers.

Now, we must master the Topology Layer.

How do we dictate which node executes first? How do we split execution down two different paths? How do we safely merge parallel paths back together? Most importantly, how do we create the looping cycles that make AI Agents so powerful?

In this chapter, we will dive deeply into the StateGraph API. We will not use any LLMs yet. We will focus purely on building bulletproof control flow using add_node, add_edge, and add_conditional_edges.


5.1 The StateGraph Builder

Every LangGraph application begins with the StateGraph object.

Think of StateGraph as a blank canvas. When you initialize it, you must pass it the blueprint of your state (your TypedDict or Pydantic schema). This ensures that the graph knows exactly what data structure to expect as it orchestrates execution.

from langgraph.graph import StateGraph
from typing import TypedDict

class MyState(TypedDict):
    count: int

# Initialize the canvas
builder = StateGraph(MyState)

Once you have a builder, you do three things:

  1. Add Nodes: Register the workers.
  2. Add Edges: Draw the lines connecting the workers.
  3. Compile: Lock the canvas and turn it into a runnable application.

What Does compile() Actually Do?

When you write app = builder.compile(), you are doing far more than just instantiating an object.

  Builder Phase
        ↓
  Register Nodes
        ↓
  Register Edges
        ↓
  Validate Topology
        ↓
     Compile
        ↓
  Runnable Graph

During compilation, LangGraph validates your entire architecture. It checks if every edge points to a valid registered node. It verifies that your conditional edge mappings are correct. If you have an edge pointing to a node named "sales", but you forgot to register "sales", compile() will immediately throw an error. It freezes the topology into an immutable, high-performance executable graph.


5.2 Linear Execution and Visualization

The simplest topology is a straight line.

To register a worker, use .add_node(name, function). To connect them, use .add_edge(start_node, end_node). Remember: You must always tell the graph where to begin using START, and where to finish using END.

from langgraph.graph import START, END

def add_one(state: MyState):
    return {"count": state["count"] + 1}

def multiply_two(state: MyState):
    return {"count": state["count"] * 2}

builder.add_node("adder", add_one)
builder.add_node("multiplier", multiply_two)

# Draw the straight line
builder.add_edge(START, "adder")
builder.add_edge("adder", "multiplier")
builder.add_edge("multiplier", END)

app = builder.compile()

Visualizing Your Graph

When debugging complex workflows, reading Python code is difficult. Because LangGraph explicitly defines topology, you can instantly render a visual diagram of your compiled application.

# Renders a Mermaid.js diagram to the console!
print(app.get_graph().draw_mermaid())

5.3 Branching (add_conditional_edges)

Linear execution is fine for simple scripts, but enterprise applications require branching. We achieve this using Conditional Edges.

A conditional edge requires three things:

  1. The Origin Node: Which node just finished executing?
  2. The Routing Function: A Python function that reads the State and returns a symbolic string representing where to go next.
  3. The Mapping Dictionary: A dictionary that converts the symbolic string into the actual registered node name.
# 1. The Routing Function
def route_priority(state: TicketState):
    if state["priority"] == "urgent":
        return "high"
    else:
        return "low"

# 2. Attach the edge with a Mapping
builder.add_conditional_edges(
    "classifier",        # Origin Node
    route_priority,      # Routing Function
    {
        # Mapping Dictionary: Symbolic String -> Actual Node Name
        "high": "tier_2_support_node",
        "low": "tier_1_support_node"
    }
)

The mapping dictionary is the safest way to build routing logic. It allows your routing function to return simple symbolic strings ("high"/"low"), decoupling the business logic from the literal node names registered in the graph.


5.4 Fan-Out and Fan-In (Parallelism)

Graphs are not limited to single lines and conditional branches. A node can point to multiple nodes simultaneously (Fan-Out), and multiple nodes can converge back into a single node (Fan-In).

          Search Docs
         /           \
Question              Combine
         \           /
          Search DB
# Fan-Out: Execute BOTH searches concurrently!
builder.add_edge("question_analyzer", "search_docs_node")
builder.add_edge("question_analyzer", "search_db_node")

# Fan-In: Both must finish before combining!
builder.add_edge("search_docs_node", "combine_node")
builder.add_edge("search_db_node", "combine_node")

As we learned in Chapter 4, this is exactly where Reducers shine. The combine_node relies on reducers to safely aggregate the parallel incoming lists.


5.5 Loops, Cycles, and Safety

Traditional LCEL chains are Directed Acyclic Graphs (DAGs). They cannot loop backwards. LangGraph supports infinite cycles out of the box, which is the foundational mechanic of Agentic reasoning.

To create a loop, you simply draw an edge pointing backwards.

       START
         ↓
  ┌─→ Node A
  │      ↓
  └── Node B
         ↓
        END

WARNING: If your state logic is flawed, this graph will run forever. In production, an infinite loop hitting an LLM API will cost thousands of dollars. You must protect your cycles with a failsafe state variable.

Loop Safety Implementation

class RetryState(TypedDict):
    input_query: str
    retry_count: int  # The failsafe counter!

def node_b_router(state: RetryState):
    # Safety Check FIRST
    if state["retry_count"] >= 3:
        print("Max retries reached. Halting.")
        return END
        
    # Standard logic
    if state["is_successful"]:
        return END
    
    # Loop back if not successful
    return "node_a"

# Ensure Node B increments the counter
def node_b(state: RetryState):
    return {"retry_count": state["retry_count"] + 1}

5.6 Topology Patterns Reference

As you build enterprise graphs, keep this mental toolbox of core topology patterns in mind:

Linear

Strict sequential execution. A → B → C

Branching

Mutually exclusive paths. Only one branch executes.

     A
    / \
   B   C

Loop

Cyclic execution. Requires a termination condition.

A → B
↑   ↓
└───┘

Fan-Out

Parallel execution. Multiple nodes execute concurrently.

      A
    / | \
   B  C  D

Fan-In

Convergence. The graph waits for parallel branches to finish before proceeding.

B
 \
  \
   D
  /
 /
C

5.7 Summary

You now possess the tools to construct any application topology imaginable. You understand how to use compile() to validate structure, how to build resilient loops, and how to branch and fan-out execution.

We have successfully stripped away all the magic. You know exactly how LangGraph works beneath the hood as a state machine orchestrator.

It is finally time to introduce Large Language Models. In the next chapter, we will tackle the most complex state aggregation problem in AI: Conversational Memory and the MessagesState.