DS DevShelfHub Projects · AI tools
Tutorials / LangChain / LangGraph Basics
LangGraph Advanced · 12 min read Page 13 of 20

LangGraph Basics: Building Stateful AI Workflows in Python

By DevShelfHub

LangGraph extends LangChain with a graph execution model — define your application as nodes (Python functions) connected by edges, with a shared state object flowing between them.

Series progress13 / 20
LangGraph basics — build a stateful AI workflow with StateGraph, nodes, edges, and conditional routing

What is LangGraph?

LangGraph is a library for building stateful, multi-actor applications with LLMs. While LCEL chains flow in a straight line, LangGraph lets you build cycles, branches, and loops — exactly what you need for agents that can retry, reflect, or wait for human input.

Nodes

Python functions that read and update the shared state.

Edges

Connections between nodes — direct or conditional based on state.

State

A typed dict that persists across every node in the graph.

Key insight: LCEL is for pipelines (input → transform → output), like the RAG pipeline. LangGraph is for workflows with cycles, conditions, and persistence — the foundation for production agents.

Defining State

State is a TypedDict with Annotated fields. The annotation tells LangGraph how to merge updates — either replace or append.

python
from typing import Annotated, TypedDict
from langgraph.graph import add_messages
from langchain_core.messages import BaseMessage

class State(TypedDict):
    # add_messages reducer: new messages are appended, not replaced
    messages: Annotated[list[BaseMessage], add_messages]
    # Plain field: each node's return value replaces the previous
    next_step: str
    iteration_count: int

LangGraph ships the add_messages reducer for chat history. You can also write custom reducers — any function with signature (existing, update) → new.

Nodes

A node is any Python callable that receives the current state and returns a dict of updates. Only the keys you return are updated — other state fields are left unchanged.

python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage

llm = ChatOpenAI(model="gpt-4o-mini")

def call_llm(state: State) -> dict:
    """Call the LLM and return its response."""
    response = llm.invoke(state["messages"])
    # Returning {"messages": [response]} appends to the list
    return {"messages": [response]}

def check_length(state: State) -> dict:
    """Count iterations and update step."""
    count = state.get("iteration_count", 0) + 1
    return {"iteration_count": count}

Building the Graph

python
from langgraph.graph import StateGraph, START, END

# 1. Create the graph with your state schema
builder = StateGraph(State)

# 2. Add nodes
builder.add_node("llm", call_llm)
builder.add_node("counter", check_length)

# 3. Add edges (fixed)
builder.add_edge(START, "llm")        # graph starts at llm
builder.add_edge("llm", "counter")    # llm always goes to counter
builder.add_edge("counter", END)      # counter always ends

# 4. Compile into a runnable
graph = builder.compile()

# 5. Invoke
result = graph.invoke({
    "messages": [HumanMessage(content="What is LangGraph?")]
})
print(result["messages"][-1].content)  # AI response

Conditional Edges

Use add_conditional_edges to route to different nodes based on state. The router function returns the name of the next node.

python
def should_continue(state: State) -> str:
    """Route based on iteration count."""
    if state["iteration_count"] >= 3:
        return "end"
    last_message = state["messages"][-1]
    if "DONE" in last_message.content:
        return "end"
    return "continue"

builder = StateGraph(State)
builder.add_node("llm", call_llm)
builder.add_node("counter", check_length)

builder.add_edge(START, "llm")
builder.add_edge("llm", "counter")
builder.add_conditional_edges(
    "counter",                           # from this node
    should_continue,                     # call this function
    {"continue": "llm", "end": END},     # map return values to nodes
)

graph = builder.compile()

This creates a loop: the graph calls the LLM, checks state, and either loops back or ends — the core pattern for ReAct agents covered in LangGraph Agents.

MessagesState Shortcut

For chat-based graphs, LangGraph provides MessagesState — a built-in state with the messages field and add_messages reducer already configured.

python
from langgraph.graph import StateGraph, MessagesState, START, END

def call_llm(state: MessagesState):
    response = llm.invoke(state["messages"])
    return {"messages": [response]}

graph = (
    StateGraph(MessagesState)
    .add_node("llm", call_llm)
    .add_edge(START, "llm")
    .add_edge("llm", END)
    .compile()
)

result = graph.invoke({"messages": [("user", "Hello!")]})
print(result["messages"][-1].content)

Streaming Graph Output

LangGraph supports streaming so you can display partial results as the graph runs — node by node or token by token.

python
# Stream state updates after each node
for chunk in graph.stream({"messages": [("user", "Explain recursion")]}):
    for node_name, state_update in chunk.items():
        print(f"[{node_name}]", state_update)

# Stream LLM tokens (requires streaming=True on the model)
llm_streaming = ChatOpenAI(model="gpt-4o-mini", streaming=True)

for chunk in graph.stream(
    {"messages": [("user", "Write a poem")]},
    stream_mode="messages",  # token-level streaming
):
    print(chunk[0].content, end="", flush=True)

LangGraph Basics FAQ

What is LangGraph?

LangGraph is a LangChain library for building stateful, multi-actor applications with LLMs. You define your application as a graph of nodes (Python functions) connected by edges, with a shared state object flowing between them. Unlike a straight-line chain, a graph can include cycles, branches, and loops, which is exactly what agents need to retry, reflect, or wait for human input.

What is the difference between LangGraph and LCEL chains?

LCEL chains are for linear pipelines: input flows through a fixed sequence of transforms to an output. LangGraph is for workflows that need cycles, conditional routing, and persistent state across steps. Use LCEL when the flow is a straight line, and reach for LangGraph when you need loops, branching decisions, or an agent that calls tools repeatedly until it is done.

What is state and StateGraph in LangGraph?

State is a typed dictionary (a TypedDict) that persists across every node in the graph; each field can use a reducer that controls whether updates replace or append values. StateGraph is the builder you create with your state schema, then add nodes and edges to before calling compile() to produce a runnable graph. The add_messages reducer is built in for chat history.

What are nodes and edges in LangGraph?

A node is any Python callable that receives the current state and returns a dict of updates; only the keys you return are changed. An edge connects one node to the next. Fixed edges always go to the same node, while conditional edges call a router function that inspects the state and returns the name of the next node, letting the graph branch or loop.

When should I use LangGraph instead of a simple chain?

Use LangGraph when your application needs to loop, make decisions based on intermediate results, retain memory across steps, or pause for human input. Typical cases include ReAct agents that call tools until a task is complete, self-correcting workflows that reflect and retry, and multi-actor systems. For a single prompt-to-answer pass, a plain LCEL chain is simpler and enough.

How do I compile and run a LangGraph graph?

Create a StateGraph with your state schema, add your nodes with add_node, wire them with add_edge or add_conditional_edges, and connect START and END. Call builder.compile() to get a runnable graph, then use graph.invoke(initial_state) for a single result or graph.stream(initial_state) to display partial output node by node or token by token.

Quick jump: API Reference