跳转到内容

Router Node

此内容尚不支持你的语言。

The router node is a multi-way branch driven by a context variable’s value, rather than an evaluated expression. It’s the standard way to dispatch on a classified intent produced by an earlier llm node. For a strictly two-way boolean branch, see Condition Node instead — the two node types use completely different routing mechanisms and are not interchangeable.

See Workflow Overview for the node/edge envelope this fits into.

FieldTypeRequiredDescription
variablestringYesThe name of the context variable to inspect. Its current value (converted to a string) is matched against outgoing edge labels.
defaultTargetstringNoThe node ID to route to when no outgoing edge’s label matches the variable’s value. If omitted and nothing matches, execution simply does not advance past this node.
circuitBreaker{ maxConsecutiveDefaultRoutes: number, target: string }NoA safety net for an incomplete classification taxonomy — see the callout below. Both sub-fields are required if circuitBreaker is present at all: target must be a non-empty node ID, and maxConsecutiveDefaultRoutes must be a positive integer, or the workflow fails validation at save time.

How routing actually works: the logic lives on edges, not the node

Section titled “How routing actually works: the logic lives on edges, not the node”

Unlike condition, a router node does not itself decide where to go — it just reads ctx[variable], converts it to a string, and hands that value off. The workflow runner then looks at the router’s outgoing edges and follows whichever one has a label that string-equals that value. If no edge matches, it falls back to defaultTarget (if set).

{
"id": "e_billing",
"source": "dispatch",
"target": "handle_billing",
"label": "billing"
}

This edge is only ever followed when ctx[variable] (whatever router.config.variable points to) equals the string "billing" — plain string equality, no expression evaluation at all.

Gotcha: the circuit breaker exists because classification taxonomies are never complete

Section titled “Gotcha: the circuit breaker exists because classification taxonomies are never complete”

A classifier llm node is prompted with a fixed list of categories (“respond with ONLY one word: billing, technical, or general”), but real user messages don’t reliably stay inside that list — a model can drift, or a genuinely new category of request can show up that the prompt author never anticipated. Every one of those falls through to defaultTarget. Without a circuit breaker, a session stuck in this state loops through the same default branch turn after turn with no natural signal that anything is wrong — it looks like normal traffic in every dashboard.

circuitBreaker closes that gap. It counts this specific router node’s own history of default-routing outcomes for the current session (persisted per turn, so it works across multiple separate messages, not just within one execution). If the router falls through to defaultTarget maxConsecutiveDefaultRoutes times in a row, the next fall-through is force-routed to circuitBreaker.target instead of defaultTarget — breaking the loop by sending the session somewhere explicit (typically a human-handoff or a “let me connect you with support” branch) rather than letting it default forever.

A few things worth knowing about the mechanism:

  • It’s generic — it works for any taxonomy or variable, not a hardcoded category string. Any router node can opt in.
  • The streak resets the moment a real (non-default) route fires — only consecutive default-routes count.
  • It’s visible in the execution trace: a NodeTraceEntry for a router node with circuitBreaker configured carries a routedToDefault: true/false flag on every run. Seeing that flag true several turns running for the same node, right before the circuit breaker fires, is a reliable sign your classification prompt’s category list needs expanding — the circuit breaker is a safety net, not a fix for the underlying prompt gap.

A returns-and-exchanges assistant classifies intent into three known categories, with a circuit breaker in case the classifier ever drifts into unrecognized territory three turns in a row:

{
"nodes": [
{
"id": "classify",
"type": "llm",
"label": "Classify Intent",
"config": {
"outputVar": "intent",
"promptTemplate": "Classify this message: \"{{userMessage}}\". Respond with ONLY one word: return_request, exchange_request, or general.",
"streamOutput": false
},
"position": { "x": 0, "y": 0 }
},
{
"id": "dispatch",
"type": "router",
"label": "Intent Dispatcher",
"config": {
"variable": "intent",
"defaultTarget": "respond_general",
"circuitBreaker": {
"maxConsecutiveDefaultRoutes": 3,
"target": "handoff_to_human"
}
},
"position": { "x": 0, "y": 100 }
},
{
"id": "handle_return",
"type": "response",
"label": "Handle Return",
"config": {
"messageTemplate": "I can start a return for you. What's the order number?"
},
"position": { "x": -200, "y": 200 }
},
{
"id": "handle_exchange",
"type": "response",
"label": "Handle Exchange",
"config": {
"messageTemplate": "Happy to set up an exchange. What's the order number?"
},
"position": { "x": 0, "y": 200 }
},
{
"id": "respond_general",
"type": "response",
"label": "Respond to General Query",
"config": {
"messageTemplate": "I can help with returns and exchanges — what would you like to do?"
},
"position": { "x": 200, "y": 200 }
},
{
"id": "handoff_to_human",
"type": "response",
"label": "Circuit Breaker Handoff",
"config": {
"messageTemplate": "I want to make sure you get the right help — connecting you with a support agent now.",
"mood": "concerned"
}
}
],
"edges": [
{ "id": "e1", "source": "classify", "target": "dispatch" },
{
"id": "e2",
"source": "dispatch",
"target": "handle_return",
"label": "return_request"
},
{
"id": "e3",
"source": "dispatch",
"target": "handle_exchange",
"label": "exchange_request"
},
{
"id": "e4",
"source": "dispatch",
"target": "respond_general",
"label": "general"
}
]
}

Note there is no edge with label matching handoff_to_human directly off dispatch — the circuit breaker doesn’t need one. It’s a separate force-route mechanism that reads circuitBreaker.target as a node ID directly, bypassing edge-label matching entirely once tripped.

If a user’s message classifies as anything other than return_request, exchange_request, or general three turns in a row (for example, the classifier keeps outputting an unanticipated word), the fourth default fall-through routes straight to handoff_to_human instead of respond_general.

  • Condition Node — the two-way, expression-based alternative to router.
  • LLM Node — typically the node that produces the classified value a router reads.
  • Response Node — a common target on every branch of a router.
  • Sub-Agent Node — a common target on a classified branch when the branch itself should hand off to a specialist agent, rather than answer inline.
  • Workflow Overview — full node/edge reference and edge-label semantics.
  • Best Practices — classification-prompt reliability patterns.
  • Examples — complete end-to-end workflows.
  • Workflows API Reference — mutations for creating and publishing workflows.