Module 4 of 10
Module 4State Machines

LangGraph: Cyclical State Machines & StateGraph

Why linear chains fail: Master LangGraph's Pregel-inspired graph architecture to build deterministic, cyclic, and fault-tolerant agent state machines.

1. Why Chains Aren't Enough: The Case for Cyclic Graphs

Standard pipelines (like LangChain Expression Language or LlamaIndex query engines) operate as Directed Acyclic Graphs (DAGs). Data flows strictly in one direction from Step A → Step B → Step C.

Real-world agents are fundamentally cyclic state machines:

  • An agent calls a tool → the tool returns data → the graph loops back to the agent.
  • If the data is insufficient, the agent calls a second tool → loops back again.
  • Only when the agent satisfies its stopping condition does the graph break the cycle and route to END.

LangGraph is built from the ground up to orchestrate these cyclical, stateful agent graphs.

2. Core Building Blocks: State, Nodes & Edges

LangGraph ConceptDefinitionRole in Agentic Flow
State & ReducersA TypedDict defining the centralized graph memory schema.Reducers (like add_messages) determine whether updates append to existing lists or overwrite keys.
NodesStandard Python functions: def node_name(state) -> dict.Perform work (call LLM, query API, parse output) and return partial state updates.
Normal EdgesStatic directional links: workflow.add_edge("A", "B").Guarantees that when Node A completes, Node B runs next.
Conditional EdgesRouting functions: workflow.add_conditional_edges(...).Dynamically decides which node executes next based on current state (e.g. should we call tools or exit?).

3. Understanding State Reducers (add_messages)

In LangGraph, nodes do not replace the entire state dictionary; they return incremental updates. Without a reducer, returning {"messages": [new_msg]} would wipe out all preceding conversation history!

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

# The AgentState definition
class AgentState(TypedDict):
    # 'add_messages' is the reducer: it appends new messages and updates existing ones by ID
    messages: Annotated[list[BaseMessage], add_messages]
    sender_id: str
    retry_count: int

4. Hands-On Python: End-to-End Tool-Calling Agent

Here is a complete, working LangGraph script implementing a robust agent with built-in tool execution and conditional routing:

from typing import TypedDict, Annotated, Literal
from langchain_core.messages import BaseMessage, HumanMessage
from langchain_core.tools import tool
from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode

# 1. Define Tools
@tool
def calculate_vat(amount: float, country_code: str) -> str:
    """Calculates VAT for a specific European country code."""
    vat_rates = {"DE": 0.19, "FR": 0.20, "UK": 0.20}
    rate = vat_rates.get(country_code.upper(), 0.15)
    tax = amount * rate
    return f"VAT for {country_code.upper()} on USD {amount:.2f} is USD {tax:.2f} (Rate: {rate*100}%)"

tools = [calculate_vat]
tool_node = ToolNode(tools)

# 2. Define State Schema
class AgentState(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]

# 3. Define the Agent Node (LLM with tools bound)
def call_agent_model(state: AgentState):
    # Mocking LLM response or binding tools via: llm.bind_tools(tools)
    messages = state["messages"]
    # Simulated response: Model calls calculate_vat
    return {"messages": messages}

# 4. Define Conditional Routing Function
def should_continue(state: AgentState) -> Literal["tools", "__end__"]:
    last_message = state["messages"][-1]
    # If the model requested tool calls, route to "tools"
    if hasattr(last_message, "tool_calls") and last_message.tool_calls:
        return "tools"
    # Otherwise, return final answer to the user
    return "__end__"

# 5. Assemble the StateGraph
workflow = StateGraph(AgentState)

# Add Nodes
workflow.add_node("agent", call_agent_model)
workflow.add_node("tools", tool_node)

# Set Entry Point
workflow.set_entry_point("agent")

# Add Edges
workflow.add_conditional_edges(
    "agent",
    should_continue,
    {
        "tools": "tools",
        "__end__": END
    }
)

# Cycle from tools back to agent!
workflow.add_edge("tools", "agent")

# 6. Compile Graph
app = workflow.compile()
print("LangGraph Agent compiled successfully!")
Key Architectural Principle: Notice the edge: workflow.add_edge("tools", "agent"). This creates the critical feedback cycle. The tool executes, outputs data, and returns control back to the model node so it can observe the result and formulate the next reasoning step.
Next Chapter

Memory, Checkpoints & Time Travel

Continue to the next advanced agentic AI engineering module.

Continue Learning →

Explore Related Tools

Hand-picked utilities and calculators related to this tool.

Related Tools