mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-10 23:48:09 -05:00
Compare commits
6 Commits
feat/copil
...
fix/router
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2283d37e5 | ||
|
|
ac7e93827f | ||
|
|
f1b8745625 | ||
|
|
d7718e6b84 | ||
|
|
9d7ab37e3d | ||
|
|
b65a9d33cd |
@@ -2,7 +2,6 @@
|
||||
title: Router
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { Image } from '@/components/ui/image'
|
||||
|
||||
@@ -102,11 +101,18 @@ Input (Lead) → Router
|
||||
└── [Self-serve] → Workflow (Automated Onboarding)
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
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:
|
||||
|
||||
- The context doesn't clearly match any of the defined route descriptions
|
||||
- The AI determines that none of the available routes are appropriate
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Write clear route descriptions**: Each route description should clearly explain when that route should be selected. Be specific about the criteria.
|
||||
- **Make routes mutually exclusive**: When possible, ensure route descriptions don't overlap to prevent ambiguous routing decisions.
|
||||
- **Include an error/fallback route**: Add a catch-all route for unexpected inputs that don't match other routes.
|
||||
- **Connect an error path**: Handle cases where no route matches by connecting an error handler for graceful fallback behavior.
|
||||
- **Use descriptive route titles**: Route titles appear in the workflow canvas, so make them meaningful for readability.
|
||||
- **Test with diverse inputs**: Ensure the Router handles various input types, edge cases, and unexpected content.
|
||||
- **Monitor routing performance**: Review routing decisions regularly and refine route descriptions based on actual usage patterns.
|
||||
|
||||
@@ -654,17 +654,20 @@ export function ConditionInput({
|
||||
}
|
||||
|
||||
const removeBlock = (id: string) => {
|
||||
if (isPreview || disabled || conditionalBlocks.length <= 2) return
|
||||
if (isPreview || disabled) return
|
||||
// Condition mode requires at least 2 blocks (if/else), router mode requires at least 1
|
||||
const minBlocks = isRouterMode ? 1 : 2
|
||||
if (conditionalBlocks.length <= minBlocks) return
|
||||
|
||||
// Remove any associated edges before removing the block
|
||||
const handlePrefix = isRouterMode ? `router-${id}` : `condition-${id}`
|
||||
const edgeIdsToRemove = edges
|
||||
.filter((edge) => edge.sourceHandle?.startsWith(`condition-${id}`))
|
||||
.filter((edge) => edge.sourceHandle?.startsWith(handlePrefix))
|
||||
.map((edge) => edge.id)
|
||||
if (edgeIdsToRemove.length > 0) {
|
||||
batchRemoveEdges(edgeIdsToRemove)
|
||||
}
|
||||
|
||||
if (conditionalBlocks.length === 1) return
|
||||
shouldPersistRef.current = true
|
||||
setConditionalBlocks((blocks) => updateBlockTitles(blocks.filter((block) => block.id !== id)))
|
||||
|
||||
@@ -816,7 +819,9 @@ export function ConditionInput({
|
||||
<Button
|
||||
variant='ghost'
|
||||
onClick={() => removeBlock(block.id)}
|
||||
disabled={isPreview || disabled || conditionalBlocks.length === 1}
|
||||
disabled={
|
||||
isPreview || disabled || conditionalBlocks.length <= (isRouterMode ? 1 : 2)
|
||||
}
|
||||
className='h-auto p-0 text-[var(--text-error)] hover:text-[var(--text-error)]'
|
||||
>
|
||||
<Trash className='h-[14px] w-[14px]' />
|
||||
|
||||
@@ -863,7 +863,8 @@ export const WorkflowBlock = memo(function WorkflowBlock({
|
||||
return parsed.map((item: unknown, index: number) => {
|
||||
const routeItem = item as { id?: string; value?: string }
|
||||
return {
|
||||
id: routeItem?.id ?? `${id}-route-${index}`,
|
||||
// Use stable ID format that matches ConditionInput's generateStableId
|
||||
id: routeItem?.id ?? `${id}-route${index + 1}`,
|
||||
value: routeItem?.value ?? '',
|
||||
}
|
||||
})
|
||||
@@ -873,7 +874,8 @@ export const WorkflowBlock = memo(function WorkflowBlock({
|
||||
logger.warn('Failed to parse router routes value', { error, blockId: id })
|
||||
}
|
||||
|
||||
return [{ id: `${id}-route-route1`, value: '' }]
|
||||
// Fallback must match ConditionInput's default: generateStableId(blockId, 'route1') = `${blockId}-route1`
|
||||
return [{ id: `${id}-route1`, value: '' }]
|
||||
}, [type, subBlockState, id])
|
||||
|
||||
/**
|
||||
|
||||
@@ -987,6 +987,14 @@ const WorkflowContent = React.memo(() => {
|
||||
const handleId = conditionHandles[0].getAttribute('data-handleid')
|
||||
if (handleId) return handleId
|
||||
}
|
||||
} else if (block.type === 'router_v2') {
|
||||
const routerHandles = document.querySelectorAll(
|
||||
`[data-nodeid^="${block.id}"][data-handleid^="router-"]`
|
||||
)
|
||||
if (routerHandles.length > 0) {
|
||||
const handleId = routerHandles[0].getAttribute('data-handleid')
|
||||
if (handleId) return handleId
|
||||
}
|
||||
} else if (block.type === 'loop') {
|
||||
return 'loop-end-source'
|
||||
} else if (block.type === 'parallel') {
|
||||
|
||||
@@ -115,25 +115,26 @@ Description: ${route.value || 'No description provided'}
|
||||
)
|
||||
.join('\n')
|
||||
|
||||
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.
|
||||
return `You are a DETERMINISTIC routing agent. You MUST select exactly ONE option.
|
||||
|
||||
Available Routes:
|
||||
${routesInfo}
|
||||
|
||||
Context to analyze:
|
||||
Context to route:
|
||||
${context}
|
||||
|
||||
Instructions:
|
||||
1. Carefully analyze the context against each route's description
|
||||
2. Select the route that best matches the context's intent and requirements
|
||||
3. Consider the semantic meaning, not just keyword matching
|
||||
4. If multiple routes could match, choose the most specific one
|
||||
ROUTING RULES:
|
||||
1. ALWAYS prefer selecting a route over NO_MATCH
|
||||
2. Pick the route whose description BEST matches the context, even if it's not a perfect match
|
||||
3. If the context is even partially related to a route's description, select that route
|
||||
4. ONLY output NO_MATCH if the context is completely unrelated to ALL route descriptions
|
||||
|
||||
Response Format:
|
||||
Return ONLY the route ID as a single string, no punctuation, no explanation.
|
||||
Example: "route-abc123"
|
||||
OUTPUT FORMAT:
|
||||
- Output EXACTLY one route ID (copied exactly as shown above) OR "NO_MATCH"
|
||||
- No explanation, no punctuation, no additional text
|
||||
- Just the route ID or NO_MATCH
|
||||
|
||||
Remember: Your response must be ONLY the route ID - no additional text, formatting, or explanation.`
|
||||
Your response:`
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -278,14 +278,24 @@ export class RouterBlockHandler implements BlockHandler {
|
||||
const result = await response.json()
|
||||
|
||||
const chosenRouteId = result.content.trim()
|
||||
|
||||
if (chosenRouteId === 'NO_MATCH' || chosenRouteId.toUpperCase() === 'NO_MATCH') {
|
||||
logger.info('Router determined no route matches the context, routing to error path')
|
||||
throw new Error('Router could not determine a matching route for the given context')
|
||||
}
|
||||
|
||||
const chosenRoute = routes.find((r) => r.id === chosenRouteId)
|
||||
|
||||
// Throw error if LLM returns invalid route ID - this routes through error path
|
||||
if (!chosenRoute) {
|
||||
const availableRoutes = routes.map((r) => ({ id: r.id, title: r.title }))
|
||||
logger.error(
|
||||
`Invalid routing decision. Response content: "${result.content}", available routes:`,
|
||||
routes.map((r) => ({ id: r.id, title: r.title }))
|
||||
`Invalid routing decision. Response content: "${result.content}". Available routes:`,
|
||||
availableRoutes
|
||||
)
|
||||
throw new Error(
|
||||
`Router could not determine a valid route. LLM response: "${result.content}". Available route IDs: ${routes.map((r) => r.id).join(', ')}`
|
||||
)
|
||||
throw new Error(`Invalid routing decision: ${chosenRouteId}`)
|
||||
}
|
||||
|
||||
// Find the target block connected to this route's handle
|
||||
|
||||
Reference in New Issue
Block a user