#27 LangGraph Tutorial: Introduction to Multi-Agent Systems in Python
LangGraph is a specialized framework designed to extend LangChain beyond linear pipelines, enabling the development of robust, stateful, cyclic, and multi-agent AI systems. While standard LangChain excels at sequential reasoning chains and basic tool calling, complex production workflows—such as non-linear decision trees, conditional loops, and collaborative agent swarms—require a graph-based coordination architecture governed by an explicit global state.
1. Why LangGraph? Limitations of Linear LangChain Workflows
In a conventional LangChain setup, application flow is predominantly linear. When an end-user sends a prompt to a Large Language Model (LLM), the model either produces a direct completion or queries an attached external tool.
While this single-agent pattern works well for straightforward question-answering systems, it suffers from severe structural bottlenecks when applied to enterprise-grade AI applications:
- Strictly Predetermined Steps: Execution sequences are essentially predefined. The model cannot dynamically branch across isolated sub-systems, backtrack based on business rules, or coordinate multiple distinct sub-agents without fragile prompt hacking.
- Absence of Non-Linear Routing: A single agent struggles to handle disparate customer intents (e.g., differentiating between tracking an existing package, requesting a refund, or handling general small talk) without polluting the prompt context.
- Lack of Stateful Persistence: Chaining multiple interdependent operations requires a shared, validated memory store. Standard sequential chains make it difficult to inspect, modify, and persist execution state across modular boundaries.
LangGraph solves this by transforming sequential tool chains into a directed state machine.
2. Core Mental Model: From Chains to Stateful Multi-Agent Graphs
The fundamental transition from LangChain to LangGraph is rooted in graph theory:
- LangChain (Chains): Connects language models and utilities sequentially (). Execution moves forward in a single line.
- LangGraph (State Graphs): Connects operations as an interconnected network of nodes and edges (). Cycles, loops, conditional branching, and multi-agent coordination become first-class architectural patterns.
Architectural Comparison
3. Real-World Architecture Walkthroughs
Case Study A: E-Commerce Support System
Consider a customer service assistant handling diverse operational requests. A monolithic prompt cannot reliably manage order lookups, validation, database queries, and refund policy evaluations simultaneously. In LangGraph, this is decomposed into specialized nodes:
Case Study B: Movie Ticket Booking System
A transactional multi-step agent must read seat inventories, validate constraints, request user confirmations, and handle payments in a coordinated sequence:
4. LangGraph Core Building Blocks
Every LangGraph system is constructed using three primary primitives:
- State: The centralized data structure representing the single source of truth across the entire lifecycle of the graph. Every node receives the current state and returns an updated state.
- Nodes: Discrete Python functions or callable objects that execute a unit of business logic (e.g., executing an LLM call, querying an API, formatting a response).
- Edges: Direct the pathway between nodes.
- Standard Edges: Deterministic transitions that route execution directly from one node to the next.
- Conditional Edges: Dynamic routing decisions that evaluate the current state and determine which node should execute next based on programmatic conditional logic.
- Special Sentinels (
START&END):START: The entry point node that receives initial external input and passes it to the first operational node.END: The terminal node indicating the graph has finished its execution pipeline.
5. The Central Nervous System: Defining State with Pydantic
Before registering nodes or tracing routes, the graph requires an explicit data schema known as the State.
State stores:
- User queries and raw conversational inputs.
- Extracted parameters (e.g., extracted
order_idor session credentials). - Router decisions.
- Intermediate computational outputs and tool execution payloads.
By utilizing Pydantic's BaseModel, LangGraph guarantees runtime validation across the entire execution life cycle:
from pydantic import BaseModel
class GreetState(BaseModel):
message: str = ""
Note: Whenever a node executes, LangGraph supplies the current state instance into that node. The node modifies or produces updates to this state and returns it back to the graph runtime to be merged into the global state.
6. Building Your First Graph: Step-by-Step Implementation
The following implementation sets up an end-to-end operational graph that takes user input, runs it through a greeting node, and terminates execution.
Step 1: Import Core Dependencies
from pydantic import BaseModel
from langgraph.graph import StateGraph, START, END
Step 2: Define the State Model
class GreetState(BaseModel):
message: str = ""
Step 3: Implement the Node Function
A node is a native Python function accepting the state object and returning the modified state.
def greet(state: GreetState) -> GreetState:
# Overwrite the message property in the state
state.message = "Hello, How is you?"
return state
Step 4: Assemble and Compile the Graph
# 1. Instantiate the StateGraph with the state schema
graph = StateGraph(GreetState)
# 2. Add the node to the graph registry
# graph.add_node(unique_node_name, function_reference)
graph.add_node("greet", greet)
# 3. Define the edges between nodes
# Direct execution from START to the 'greet' node
graph.add_edge(START, "greet")
# Direct execution from the 'greet' node to END
graph.add_edge("greet", END)
# 4. Compile the graph into an executable runnable
final_graph = graph.compile()
Step 5: Execute the Graph
Execution is triggered using the .invoke() method, passing a dictionary or object matching the state schema:
# Invoking the graph with an initial payload
response = final_graph.invoke({"message": "Hello"})
print(response)
Output:
{'message': 'Hello, How is you?'}
7. State Mutability: Overwriting vs. Appending State Data
A common architectural trap when getting started with LangGraph is unintentional state overwriting.
The Overwrite Pitfall
In the basic implementation above, if a user invokes the graph with a custom payload:
response = final_graph.invoke({"message": "I love LangGraph"})
print(response)
The output remains:
{'message': 'Hello, How is you?'}
Because the node logic explicitly overwrites state.message = "Hello, How is you?", the incoming parameter is lost.
The Correct Stateful Mutation Pattern
To preserve incoming context and update the state incrementally, you must explicitly read and concatenate or mutate the existing fields:
def greet(state: GreetState) -> GreetState:
# Preserve incoming state.message and append new content
state.message = f"{state.message} All Good"
return state
Now, re-compiling and executing the graph preserves the original user context:
# Re-instantiate and re-compile
graph = StateGraph(GreetState)
graph.add_node("greet", greet)
graph.add_edge(START, "greet")
graph.add_edge("greet", END)
final_graph = graph.compile()
response = final_graph.invoke({"message": "I love LangGraph"})
print(response)
Output:
{'message': 'I love LangGraph All Good'}
8. Multi-Node Chaining: Adding Sequential Processing
Real-world graphs consist of multiple processing steps linked sequentially or conditionally. To demonstrate multi-node coordination, let's inject a second node (upper_case) that transforms the output of greet into uppercase text.
Implementation
from pydantic import BaseModel
from langgraph.graph import StateGraph, START, END
# Define shared state schema
class GreetState(BaseModel):
message: str = ""
# Define Node 1: Greeting and concatenation
def greet(state: GreetState) -> GreetState:
state.message = f"{state.message} All Good"
return state
# Define Node 2: Transformation logic
def upper_case(state: GreetState) -> GreetState:
state.message = state.message.upper()
return state
# Build the graph pipeline
workflow = StateGraph(GreetState)
# Register both nodes
workflow.add_node("greet", greet)
workflow.add_node("upper_case", upper_case)
# Connect edges to define execution order
workflow.add_edge(START, "greet")
workflow.add_edge("greet", "upper_case")
workflow.add_edge("upper_case", END)
# Compile into an executable engine
app = workflow.compile()
# Invoke with initial input
result = app.invoke({"message": "I love LangGraph"})
print(result)
Execution Output:
{'message': 'I LOVE LANGGRAPH ALL GOOD'}
9. Graph Visualization with IPython Display
Complex state graphs can become difficult to debug purely by tracing code. LangGraph provides built-in visual inspection utilities using Mermaid.js rendering:
from IPython.display import Image, display
# Render the compiled graph structure as a PNG image
display(Image(app.get_graph().draw_mermaid_png()))
This generates a clean graphical representation showing the exact topology of the application:
This visualization confirms:
- Entry points (
START) and exits (END) are correctly wired. - Nodes are bound in the expected topological order.
- There are no orphaned or unreachable nodes in the execution path.
Summary and Key Takeaways
- Linear vs. Graph Paradigms: LangChain handles linear chains (), whereas LangGraph accommodates loops, cycles, branches, and multi-agent coordination topologies.
- Single vs. Multi-Agent Systems: In complex applications (e.g., e-commerce customer support, ticket booking), a single agent with tools struggles with scaling context; LangGraph isolates responsibilities into modular, dedicated nodes.
- Nodes are Python Functions: Any standard Python function that takes a state object and returns an updated state object can serve as an operational node in LangGraph.
- Edges Dictate Flow: Standard edges (
add_edge) connect nodes deterministically, while conditional edges (add_conditional_edges) dynamically route execution paths based on runtime state evaluations. - State is the Single Source of Truth: State defines the global data schema across all operations, typically validated and enforced using Pydantic's
BaseModel. - Avoid Unintentional State Overwrites: Modifying state requires explicitly deciding whether to overwrite properties or append/merge new data with existing values.
- Strict Compilation Lifecycle: A graph must be populated with nodes, connected via edges, and explicitly compiled (
graph.compile()) before it can be invoked with.invoke(). - First-Class Visual Debugging: LangGraph integrates with graph-drawing utilities (
draw_mermaid_png()), making it easy to visually audit and verify complex agent workflows before deploying to production.