Skip to content

Commit 3ed1775

Browse files
Sg312icecrasher321
andauthored
fix(router): fix router ports (#2757)
* Fix router block * Fix autoconnect edge for router * Fix lint * router block error path decision * improve router prompt --------- Co-authored-by: Vikhyath Mondreti <[email protected]>
1 parent baa54b4 commit 3ed1775

File tree

7 files changed

+54
-23
lines changed

7 files changed

+54
-23
lines changed

apps/docs/content/docs/en/blocks/router.mdx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
title: Router
33
---
44

5-
import { Callout } from 'fumadocs-ui/components/callout'
65
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
76
import { Image } from '@/components/ui/image'
87

@@ -102,11 +101,18 @@ Input (Lead) → Router
102101
└── [Self-serve] → Workflow (Automated Onboarding)
103102
```
104103

104+
## Error Handling
105+
106+
When the Router cannot determine an appropriate route for the given context, it will route to the **error path** instead of arbitrarily selecting a route. This happens when:
107+
108+
- The context doesn't clearly match any of the defined route descriptions
109+
- The AI determines that none of the available routes are appropriate
110+
105111
## Best Practices
106112

107113
- **Write clear route descriptions**: Each route description should clearly explain when that route should be selected. Be specific about the criteria.
108114
- **Make routes mutually exclusive**: When possible, ensure route descriptions don't overlap to prevent ambiguous routing decisions.
109-
- **Include an error/fallback route**: Add a catch-all route for unexpected inputs that don't match other routes.
115+
- **Connect an error path**: Handle cases where no route matches by connecting an error handler for graceful fallback behavior.
110116
- **Use descriptive route titles**: Route titles appear in the workflow canvas, so make them meaningful for readability.
111117
- **Test with diverse inputs**: Ensure the Router handles various input types, edge cases, and unexpected content.
112118
- **Monitor routing performance**: Review routing decisions regularly and refine route descriptions based on actual usage patterns.

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -654,17 +654,20 @@ export function ConditionInput({
654654
}
655655

656656
const removeBlock = (id: string) => {
657-
if (isPreview || disabled || conditionalBlocks.length <= 2) return
657+
if (isPreview || disabled) return
658+
// Condition mode requires at least 2 blocks (if/else), router mode requires at least 1
659+
const minBlocks = isRouterMode ? 1 : 2
660+
if (conditionalBlocks.length <= minBlocks) return
658661

659662
// Remove any associated edges before removing the block
663+
const handlePrefix = isRouterMode ? `router-${id}` : `condition-${id}`
660664
const edgeIdsToRemove = edges
661-
.filter((edge) => edge.sourceHandle?.startsWith(`condition-${id}`))
665+
.filter((edge) => edge.sourceHandle?.startsWith(handlePrefix))
662666
.map((edge) => edge.id)
663667
if (edgeIdsToRemove.length > 0) {
664668
batchRemoveEdges(edgeIdsToRemove)
665669
}
666670

667-
if (conditionalBlocks.length === 1) return
668671
shouldPersistRef.current = true
669672
setConditionalBlocks((blocks) => updateBlockTitles(blocks.filter((block) => block.id !== id)))
670673

@@ -816,7 +819,9 @@ export function ConditionInput({
816819
<Button
817820
variant='ghost'
818821
onClick={() => removeBlock(block.id)}
819-
disabled={isPreview || disabled || conditionalBlocks.length === 1}
822+
disabled={
823+
isPreview || disabled || conditionalBlocks.length <= (isRouterMode ? 1 : 2)
824+
}
820825
className='h-auto p-0 text-[var(--text-error)] hover:text-[var(--text-error)]'
821826
>
822827
<Trash className='h-[14px] w-[14px]' />

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,8 @@ export const WorkflowBlock = memo(function WorkflowBlock({
863863
return parsed.map((item: unknown, index: number) => {
864864
const routeItem = item as { id?: string; value?: string }
865865
return {
866-
id: routeItem?.id ?? `${id}-route-${index}`,
866+
// Use stable ID format that matches ConditionInput's generateStableId
867+
id: routeItem?.id ?? `${id}-route${index + 1}`,
867868
value: routeItem?.value ?? '',
868869
}
869870
})
@@ -873,7 +874,8 @@ export const WorkflowBlock = memo(function WorkflowBlock({
873874
logger.warn('Failed to parse router routes value', { error, blockId: id })
874875
}
875876

876-
return [{ id: `${id}-route-route1`, value: '' }]
877+
// Fallback must match ConditionInput's default: generateStableId(blockId, 'route1') = `${blockId}-route1`
878+
return [{ id: `${id}-route1`, value: '' }]
877879
}, [type, subBlockState, id])
878880

879881
/**

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,14 @@ const WorkflowContent = React.memo(() => {
987987
const handleId = conditionHandles[0].getAttribute('data-handleid')
988988
if (handleId) return handleId
989989
}
990+
} else if (block.type === 'router_v2') {
991+
const routerHandles = document.querySelectorAll(
992+
`[data-nodeid^="${block.id}"][data-handleid^="router-"]`
993+
)
994+
if (routerHandles.length > 0) {
995+
const handleId = routerHandles[0].getAttribute('data-handleid')
996+
if (handleId) return handleId
997+
}
990998
} else if (block.type === 'loop') {
991999
return 'loop-end-source'
9921000
} else if (block.type === 'parallel') {

apps/sim/blocks/blocks/router.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -115,25 +115,26 @@ Description: ${route.value || 'No description provided'}
115115
)
116116
.join('\n')
117117

118-
return `You are an intelligent routing agent. Your task is to analyze the provided context and select the most appropriate route from the available options.
118+
return `You are a DETERMINISTIC routing agent. You MUST select exactly ONE option.
119119
120120
Available Routes:
121121
${routesInfo}
122122
123-
Context to analyze:
123+
Context to route:
124124
${context}
125125
126-
Instructions:
127-
1. Carefully analyze the context against each route's description
128-
2. Select the route that best matches the context's intent and requirements
129-
3. Consider the semantic meaning, not just keyword matching
130-
4. If multiple routes could match, choose the most specific one
126+
ROUTING RULES:
127+
1. ALWAYS prefer selecting a route over NO_MATCH
128+
2. Pick the route whose description BEST matches the context, even if it's not a perfect match
129+
3. If the context is even partially related to a route's description, select that route
130+
4. ONLY output NO_MATCH if the context is completely unrelated to ALL route descriptions
131131
132-
Response Format:
133-
Return ONLY the route ID as a single string, no punctuation, no explanation.
134-
Example: "route-abc123"
132+
OUTPUT FORMAT:
133+
- Output EXACTLY one route ID (copied exactly as shown above) OR "NO_MATCH"
134+
- No explanation, no punctuation, no additional text
135+
- Just the route ID or NO_MATCH
135136
136-
Remember: Your response must be ONLY the route ID - no additional text, formatting, or explanation.`
137+
Your response:`
137138
}
138139

139140
/**

apps/sim/executor/handlers/router/router-handler.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,14 +278,24 @@ export class RouterBlockHandler implements BlockHandler {
278278
const result = await response.json()
279279

280280
const chosenRouteId = result.content.trim()
281+
282+
if (chosenRouteId === 'NO_MATCH' || chosenRouteId.toUpperCase() === 'NO_MATCH') {
283+
logger.info('Router determined no route matches the context, routing to error path')
284+
throw new Error('Router could not determine a matching route for the given context')
285+
}
286+
281287
const chosenRoute = routes.find((r) => r.id === chosenRouteId)
282288

289+
// Throw error if LLM returns invalid route ID - this routes through error path
283290
if (!chosenRoute) {
291+
const availableRoutes = routes.map((r) => ({ id: r.id, title: r.title }))
284292
logger.error(
285-
`Invalid routing decision. Response content: "${result.content}", available routes:`,
286-
routes.map((r) => ({ id: r.id, title: r.title }))
293+
`Invalid routing decision. Response content: "${result.content}". Available routes:`,
294+
availableRoutes
295+
)
296+
throw new Error(
297+
`Router could not determine a valid route. LLM response: "${result.content}". Available route IDs: ${routes.map((r) => r.id).join(', ')}`
287298
)
288-
throw new Error(`Invalid routing decision: ${chosenRouteId}`)
289299
}
290300

291301
// Find the target block connected to this route's handle

bun.lock

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"lockfileVersion": 1,
3-
"configVersion": 0,
43
"workspaces": {
54
"": {
65
"name": "simstudio",

0 commit comments

Comments
 (0)