LangGraph Tutorial (Python): building conditional routing for advanced developers
This tutorial builds a LangGraph workflow that routes requests conditionally based on message content and state. You need this when a single linear chain stops being enough and you want deterministic branching for support triage, claim handling, fraud checks, or any agent flow where the next step depends on what the model or rules detect.
What You'll Need
- •Python 3.10+
- •
langgraph - •
langchain-core - •
langchain-openai - •An OpenAI API key set as
OPENAI_API_KEY - •Basic familiarity with
StateGraph, nodes, and edges in LangGraph - •A terminal and a virtual environment
Step-by-Step
- •Create a typed state object and define the routing problem first.
For advanced routing, keep state explicit so every node knows exactly what it can read and write.
from typing import TypedDict, Annotated, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_core.messages import HumanMessage, AIMessage
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
route: str
risk_score: int
- •Add the worker nodes that do real work.
One node classifies intent, another handles low-risk requests, and another escalates to a human-style review path.
def classify_node(state: AgentState):
text = state["messages"][-1].content.lower()
if "refund" in text or "cancel" in text:
return {"route": "billing", "risk_score": 2}
if "fraud" in text or "stolen" in text:
return {"route": "fraud", "risk_score": 9}
return {"route": "general", "risk_score": 4}
def billing_node(state: AgentState):
return {"messages": [AIMessage(content="Billing flow selected. I can help with refunds or cancellations.")]}
def fraud_node(state: AgentState):
return {"messages": [AIMessage(content="Fraud flow selected. Escalating for immediate review.")]}
def general_node(state: AgentState):
return {"messages": [AIMessage(content="General support flow selected. How can I help further?")]}
- •Write the conditional router as a pure function.
This is the part advanced developers care about: routing should be deterministic, testable, and isolated from side effects.
def route_decision(state: AgentState) -> Literal["billing", "fraud", "general"]:
if state["route"] == "billing":
return "billing"
if state["route"] == "fraud":
return "fraud"
return "general"
- •Build the graph and wire conditional edges to each branch.
The graph starts with classification, then routes into one of three paths before ending.
graph = StateGraph(AgentState)
graph.add_node("classifier", classify_node)
graph.add_node("billing", billing_node)
graph.add_node("fraud", fraud_node)
graph.add_node("general", general_node)
graph.add_edge(START, "classifier")
graph.add_conditional_edges(
"classifier",
route_decision,
{
"billing": "billing",
"fraud": "fraud",
"general": "general",
},
)
graph.add_edge("billing", END)
graph.add_edge("fraud", END)
graph.add_edge("general", END)
app = graph.compile()
- •Run it with different inputs and inspect the final state.
Use this to validate that routing is correct before you connect any external tools or APIs.
inputs = [
{"messages": [HumanMessage(content="I want a refund for my order")], "route": "", "risk_score": 0},
{"messages": [HumanMessage(content="My card was stolen")], "route": "", "risk_score": 0},
{"messages": [HumanMessage(content="How do I update my address?")], "route": "", "risk_score": 0},
]
for item in inputs:
result = app.invoke(item)
print("Route:", result["route"], "| Risk:", result["risk_score"])
print("Last message:", result["messages"][-1].content)
print("---")
Testing It
Run the script locally and confirm each input lands in the expected branch. A refund request should produce billing, a stolen card request should produce fraud, and a generic support question should go to general.
If you want stronger verification, write unit tests against classify_node and route_decision separately before testing the compiled graph. That keeps failures localized when you change routing logic later.
Also inspect the returned state after each invocation. In production graphs, bugs usually show up as stale state keys or unexpected branch selection, not as syntax errors.
Next Steps
- •Add a fallback branch for unknown or low-confidence classifications
- •Replace rule-based classification with an LLM node that returns structured JSON
- •Extend the graph with tool nodes for CRM lookup, policy retrieval, or escalation queues
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit