No history yet

Stateful Graph Fundamentals

From Chains to Graphs

While LangChain Expression Language (LCEL) chains are excellent for creating linear, sequential workflows, many real-world applications require more complex logic. What if you need your application to loop, make decisions, or revisit previous steps? This is where a more robust structure is needed.

Enter LangGraph. It shifts the paradigm from a straight line to a flexible graph. Instead of just chaining components together, you build a state machine that can direct workflows with cycles, branches, and persistent memory. It gives you precise control over the flow of your application.

LangGraph is a library from LangChain that lets you build stateful, graph-based applications for LLMs.

The Four Pillars of LangGraph

Understanding LangGraph comes down to four core concepts that work together: State, Nodes, Edges, and the Graph itself.

While all four are important, the most critical concept is the State. It's the memory or 'brain' of your application. Everything else revolves around reading from and writing to this shared state.

  • State: A single object, typically a dictionary or model, that holds all the data passed between nodes. It's the single source of truth for the entire workflow.
  • Nodes: Python functions or other callables that perform a unit of work. Each node receives the current state, performs its logic, and returns a dictionary containing only the updates to the state.
  • Edges: These connect the nodes, defining the path of execution. An edge dictates which node runs next. Crucially, they can be conditional, enabling branching logic based on the current state.
  • Graph: The StateGraph is the object where you define the structure. You add your nodes and edges to it and then compile it into a runnable application.

Defining the Graph State

Every LangGraph application begins with defining its state. This object will be passed to every node in your graph. Since nodes update the state rather than replacing it, the state accumulates information as it flows through the graph. The simplest way to define state is with Python's TypedDict.

from typing import TypedDict, List
from langgraph.graph import StateGraph, END

# 1. Define the state object
# This class inherits from TypedDict, which provides type hints for dictionary keys.
# The state object acts as the shared memory for all nodes in the graph.
class AgentState(TypedDict):
    # 'messages' will hold a list of strings representing the conversation.
    messages: List[str]

# 2. Define the nodes
# Each node is a function that takes the current state as input.
def add_hello(state: AgentState):
    # It performs some logic...
    print("---ENTERING HELLO NODE---")
    new_messages = state['messages'] + ["Hello"]
    # ...and returns a dictionary with the fields to update.
    return {"messages": new_messages}

def add_world(state: AgentState):
    print("---ENTERING WORLD NODE---")
    new_messages = state['messages'] + ["World"]
    return {"messages": new_messages}

# 3. Define the graph
workflow = StateGraph(AgentState)

# 4. Add the nodes
workflow.add_node("hello_node", add_hello)
workflow.add_node("world_node", add_world)

# 5. Add the edges
workflow.set_entry_point("hello_node")
workflow.add_edge("hello_node", "world_node")
workflow.add_edge("world_node", END)

# 6. Compile the graph into a runnable app
app = workflow.compile()

# 7. Run the graph
initial_state = {"messages": []}
final_state = app.invoke(initial_state)

print(final_state)
# Expected Output:
# ---ENTERING HELLO NODE---
# ---ENTERING WORLD NODE---
# {'messages': ['Hello', 'World']}

Let's break down the lifecycle shown in the code:

  1. Define: We create a StateGraph instance and pass our AgentState TypedDict to it. This tells the graph what structure its state will have.
  2. Add Nodes: We use workflow.add_node() to register our Python functions as nodes, giving each a unique string name.
  3. Add Edges: We define the flow. set_entry_point() specifies the first node to run. add_edge() connects one node to the next. The special END value signifies that the workflow should terminate after a node completes.
  4. Compile: The workflow.compile() method takes our definition and creates an executable object. This object can then be invoked, just like an LCEL chain.

Notice the node function signature: def add_hello(state: AgentState):. Every node function accepts a single argument, the current state, and it returns a dictionary. This dictionary contains only the keys from the state that need to be updated. LangGraph handles merging this update into the main state object for you. This 'add-only' approach is key to how state persists and evolves through the graph.

Now, let's test your understanding of these core concepts.

Quiz Questions 1/5

What is the primary advantage of using LangGraph over a standard LangChain Expression Language (LCEL) chain?

Quiz Questions 2/5

In a LangGraph application, what is the fundamental role of the 'State' object?

With these fundamentals, you can start building applications that go beyond simple linear sequences, enabling sophisticated, agent-like behaviors.