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.

Building Intelligent Feedback Systems: A Deep Dive into Conditional Agentic Workflows with LangGraph

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.

Chain versus conditional graphStep AStep BStep CStep AConditionaltransitionStep BStep CChain: fixed orderGraph: branch on state, optionally loop backloop back
Figure. A chain runs its steps in a fixed order; a LangGraph workflow can branch on the state and loop back, which is the difference the original article builds on.

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"])
review_triage.py

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.

Review triage graph with a conditional edgeSTARTclassifysets sentimentrouteconditional edgeon sentimentthanksentiment positiveescalatesentiment negativelogany other sentimentEND
Figure. The graph built by the code above: one classification node, a conditional edge, three possible branches.

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 log does 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.

Sources

Subscribe for daily recipes. No spam, just food.