Building Intelligent Feedback Systems: A Deep Dive into Conditional Agentic Workflows with LangGraph
How LangGraph conditional edges let a workflow branch on what it has just learned, illustrated with an automated customer-review triage.
How conditional edges let a LangGraph workflow branch on what it has just learned, illustrated with an automated customer-review triage. The original Towards AI article argues that plain LangChain chains are designed for linear flows and struggle with branching logic and loops, while LangGraph supports conditional transitions based on LLM output, tool results and state variables.
Why Branching Needs a Graph
A chain runs its steps in a fixed order. A review-triage system cannot: a positive review needs a thank-you, a negative one needs a support ticket, and anything else only needs to be stored. The next step depends on the result of the previous one, which is what a conditional workflow expresses.
Core Concepts
- State: a shared structure (typically a
TypedDict) that every node reads and updates. - Nodes: functions that receive the state and return the fields they want to update.
- Edges: fixed transitions from one node to the next.
- Conditional edges: a routing function inspects the state and returns the name of the next node.
- Compilation:
compile()turns the builder into a runnable graph.
A Minimal Review-Triage Graph
The example below classifies a review, then routes it with a conditional edge. The classifier is a keyword rule so that the code runs offline; in a real system this node would call an LLM. It was run with langgraph 1.2.18.
from typing import Literal, TypedDict
from langgraph.graph import END, START, StateGraph
class ReviewState(TypedDict):
review: str
sentiment: str
action: str
def classify(state: ReviewState) -> dict:
# Stand-in for an LLM call: a keyword rule keeps the example runnable offline.
text = state["review"].lower()
if any(w in text for w in ("broken", "refund", "terrible")):
return {"sentiment": "negative"}
if any(w in text for w in ("love", "great", "excellent")):
return {"sentiment": "positive"}
return {"sentiment": "neutral"}
def route(state: ReviewState) -> Literal["thank", "escalate", "log"]:
return {"positive": "thank", "negative": "escalate"}.get(state["sentiment"], "log")
def thank(state: ReviewState) -> dict:
return {"action": "send thank-you message"}
def escalate(state: ReviewState) -> dict:
return {"action": "open a support ticket"}
def log(state: ReviewState) -> dict:
return {"action": "store for later analysis"}
builder = StateGraph(ReviewState)
builder.add_node("classify", classify)
builder.add_node("thank", thank)
builder.add_node("escalate", escalate)
builder.add_node("log", log)
builder.add_edge(START, "classify")
builder.add_conditional_edges("classify", route)
for node in ("thank", "escalate", "log"):
builder.add_edge(node, END)
graph = builder.compile()
print(graph.invoke({"review": "The screen arrived broken, I want a refund."})["action"])
print(graph.invoke({"review": "I love it, excellent battery."})["action"])
print(graph.invoke({"review": "It works."})["action"])The route function returns the name of the next node, and because its return type is a Literal, LangGraph can infer the possible branches. Every branch then leads to END. The three calls at the bottom print open a support ticket, send thank-you message and store for later analysis.
Practical Notes
- Keep the routing function small, deterministic and free of side effects: it only reads the state and returns a node name.
- Always provide a default branch, as
logdoes here, so that an unexpected classification never leaves the workflow without a next step. - When the classifier is an LLM, validate its output (for example with a structured-output schema) before it reaches the router.
Conclusion
Conditional edges turn a workflow that must branch into an explicit, inspectable graph: the state says what happened, and the routing function decides what happens next. For the full discussion of the review-triage use case, read the original article.