Routing & Guards
Routing & Guards
Section titled “Routing & Guards”Routing and guard nodes control the flow of execution through your graphs. Routers direct requests to appropriate handlers based on conditions. Guards validate data and enforce constraints before processing continues.
Rule-Based Routing
Section titled “Rule-Based Routing”RuleRouter evaluates conditions against envelope data and routes to the first matching target.
Basic Rule Router
Section titled “Basic Rule Router”router := petalflow.NewRuleRouter("triage", petalflow.RuleRouterConfig{ Routes: []petalflow.RouteRule{ { When: petalflow.RouteCondition{ Var: "priority", Op: petalflow.OpEquals, Value: "high", }, To: "urgent_handler", }, { When: petalflow.RouteCondition{ Var: "priority", Op: petalflow.OpEquals, Value: "medium", }, To: "standard_handler", }, }, Default: "low_priority_handler",})Comparison Operators
Section titled “Comparison Operators”| Operator | Description | Example |
|---|---|---|
OpEquals | Exact match | priority == "high" |
OpNotEquals | Not equal | status != "closed" |
OpGt | Greater than | score > 80 |
OpGte | Greater than or equal | score >= 80 |
OpLt | Less than | attempts < 3 |
OpLte | Less than or equal | attempts <= 3 |
OpContains | String contains substring | message contains "urgent" |
OpStartsWith | String starts with | email startsWith "admin" |
OpEndsWith | String ends with | email endsWith "@company.com" |
OpIn | Value in list | category in ["billing", "sales"] |
OpNotIn | Value not in list | status notIn ["spam", "deleted"] |
OpEmpty | Value is empty/nil | notes is empty |
OpNotEmpty | Value exists and not empty | customer_id is not empty |
OpMatches | Regex match | phone matches "^\+1" |
OpBetween | Value in range | amount between [100, 1000] |
Complex Routing Examples
Section titled “Complex Routing Examples”Multi-condition routing with numeric comparisons:
router := petalflow.NewRuleRouter("order_router", petalflow.RuleRouterConfig{ Routes: []petalflow.RouteRule{ // Large orders from VIP customers { When: petalflow.RouteCondition{ And: []petalflow.RouteCondition{ {Var: "order_total", Op: petalflow.OpGte, Value: 10000}, {Var: "customer_tier", Op: petalflow.OpEquals, Value: "vip"}, }, }, To: "vip_large_order", }, // Any large order { When: petalflow.RouteCondition{ Var: "order_total", Op: petalflow.OpGte, Value: 5000, }, To: "large_order_review", }, // International shipping { When: petalflow.RouteCondition{ Var: "shipping_country", Op: petalflow.OpNotEquals, Value: "US", }, To: "international_shipping", }, }, Default: "standard_fulfillment",})String pattern matching:
router := petalflow.NewRuleRouter("email_router", petalflow.RuleRouterConfig{ Routes: []petalflow.RouteRule{ // Internal emails { When: petalflow.RouteCondition{ Var: "sender_email", Op: petalflow.OpEndsWith, Value: "@company.com", }, To: "internal_handler", }, // Known partners { When: petalflow.RouteCondition{ Var: "sender_domain", Op: petalflow.OpIn, Value: []string{"partner1.com", "partner2.com", "partner3.com"}, }, To: "partner_handler", }, // Suspicious patterns { When: petalflow.RouteCondition{ Var: "subject", Op: petalflow.OpMatches, Value: `(?i)(urgent|act now|limited time)`, }, To: "spam_review", }, }, Default: "general_inbox",})Compound Conditions
Section titled “Compound Conditions”Combine conditions with And and Or:
router := petalflow.NewRuleRouter("access_control", petalflow.RuleRouterConfig{ Routes: []petalflow.RouteRule{ // Admin access { When: petalflow.RouteCondition{ Or: []petalflow.RouteCondition{ {Var: "role", Op: petalflow.OpEquals, Value: "admin"}, {Var: "role", Op: petalflow.OpEquals, Value: "superuser"}, }, }, To: "admin_panel", }, // Verified users with premium subscription { When: petalflow.RouteCondition{ And: []petalflow.RouteCondition{ {Var: "email_verified", Op: petalflow.OpEquals, Value: true}, {Var: "subscription", Op: petalflow.OpIn, Value: []string{"premium", "enterprise"}}, }, }, To: "premium_features", }, }, Default: "basic_access",})LLM-Based Routing
Section titled “LLM-Based Routing”LLMRouter uses a language model to classify inputs and route based on semantic understanding.
Basic LLM Router
Section titled “Basic LLM Router”router := petalflow.NewLLMRouter("intent_classifier", client, petalflow.LLMRouterConfig{ Model: "gpt-4o-mini", InputKey: "user_message", Categories: []petalflow.Category{ { Name: "billing", Description: "Questions about invoices, payments, charges, refunds, or pricing", Examples: []string{"Why was I charged twice?", "How do I update my payment method?"}, }, { Name: "technical", Description: "Technical issues, bugs, errors, integration help, or how-to questions", Examples: []string{"The API returns a 500 error", "How do I authenticate?"}, }, { Name: "sales", Description: "Product inquiries, feature comparisons, pricing plans, or upgrades", Examples: []string{"What's included in the Pro plan?", "Can I get a demo?"}, }, { Name: "general", Description: "General questions, feedback, or topics not matching other categories", Examples: []string{"What are your business hours?", "I have a suggestion"}, }, },})Custom Classification Prompt
Section titled “Custom Classification Prompt”Override the default classification prompt:
router := petalflow.NewLLMRouter("sentiment_router", client, petalflow.LLMRouterConfig{ Model: "gpt-4o-mini", InputKey: "feedback", Categories: []petalflow.Category{ {Name: "positive", Description: "Satisfied, happy, or complimentary feedback"}, {Name: "negative", Description: "Dissatisfied, frustrated, or complaint feedback"}, {Name: "neutral", Description: "Factual, informational, or mixed feedback"}, }, PromptTemplate: `Analyze the sentiment of this customer feedback and classify it.
Feedback: {{.Vars.feedback}}
Categories:{{range .Categories}}- {{.Name}}: {{.Description}}{{end}}
Consider the overall tone, specific language used, and implied emotions.Respond with exactly one category name.`,})Confidence Thresholds
Section titled “Confidence Thresholds”Route uncertain classifications to human review:
router := petalflow.NewLLMRouter("confident_classifier", client, petalflow.LLMRouterConfig{ Model: "gpt-4o-mini", InputKey: "query", Categories: categories, ConfidenceThreshold: 0.8, LowConfidenceTarget: "human_review",})Multi-Label Classification
Section titled “Multi-Label Classification”When inputs may match multiple categories:
router := petalflow.NewLLMRouter("tag_classifier", client, petalflow.LLMRouterConfig{ Model: "gpt-4o-mini", InputKey: "document", MultiLabel: true, Categories: []petalflow.Category{ {Name: "urgent", Description: "Time-sensitive issues requiring immediate attention"}, {Name: "security", Description: "Security-related concerns or vulnerabilities"}, {Name: "compliance", Description: "Regulatory or compliance matters"}, {Name: "customer_facing", Description: "Impacts external customers"}, }, OutputKey: "tags", // Array of matched categories})Guard Nodes
Section titled “Guard Nodes”GuardianNode validates envelope data against constraints. Use guards to enforce data quality,
prevent invalid states, and fail fast on bad inputs.
Required Field Validation
Section titled “Required Field Validation”guard := petalflow.NewGuardianNode("input_validator", petalflow.GuardianNodeConfig{ Checks: []petalflow.GuardCheck{ { Var: "customer_id", Op: petalflow.OpNotEmpty, Message: "Customer ID is required", }, { Var: "email", Op: petalflow.OpNotEmpty, Message: "Email address is required", }, { Var: "order_items", Op: petalflow.OpNotEmpty, Message: "Order must contain at least one item", }, }, OnFail: petalflow.GuardActionReject,})Format Validation
Section titled “Format Validation”guard := petalflow.NewGuardianNode("format_validator", petalflow.GuardianNodeConfig{ Checks: []petalflow.GuardCheck{ // Email format { Var: "email", Op: petalflow.OpMatches, Value: `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`, Message: "Invalid email format", }, // Phone format (US) { Var: "phone", Op: petalflow.OpMatches, Value: `^\+?1?[-.\s]?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$`, Message: "Invalid phone number format", }, // UUID format { Var: "request_id", Op: petalflow.OpMatches, Value: `^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`, Message: "Request ID must be a valid UUID", }, }, OnFail: petalflow.GuardActionReject,})Range and Length Validation
Section titled “Range and Length Validation”guard := petalflow.NewGuardianNode("bounds_validator", petalflow.GuardianNodeConfig{ Checks: []petalflow.GuardCheck{ // String length { Var: "username", Op: petalflow.OpMinLength, Value: 3, Message: "Username must be at least 3 characters", }, { Var: "username", Op: petalflow.OpMaxLength, Value: 50, Message: "Username cannot exceed 50 characters", }, // Numeric range { Var: "quantity", Op: petalflow.OpBetween, Value: []int{1, 1000}, Message: "Quantity must be between 1 and 1000", }, // Minimum value { Var: "price", Op: petalflow.OpGt, Value: 0, Message: "Price must be greater than 0", }, }, OnFail: petalflow.GuardActionReject,})Guard Actions
Section titled “Guard Actions”| Action | Behavior |
|---|---|
GuardActionReject | Stop execution and return error |
GuardActionWarn | Log warning and continue |
GuardActionRoute | Route to specified error handler |
GuardActionSkip | Skip remaining nodes in branch |
GuardActionTransform | Apply transformation and continue |
Routing on Validation Failure
Section titled “Routing on Validation Failure”guard := petalflow.NewGuardianNode("input_guard", petalflow.GuardianNodeConfig{ Checks: []petalflow.GuardCheck{ {Var: "query", Op: petalflow.OpNotEmpty, Message: "Query cannot be empty"}, {Var: "query", Op: petalflow.OpMaxLength, Value: 10000, Message: "Query too long"}, }, OnFail: petalflow.GuardActionRoute, FailRoute: "validation_error_handler", ErrorKey: "validation_errors",})
// Error handler nodeerrorHandler := petalflow.NewTransformNode("validation_error_handler", petalflow.TransformNodeConfig{ InputKeys: []string{"validation_errors"}, OutputKey: "response", Transform: func(inputs map[string]any) (any, error) { errors := inputs["validation_errors"].([]string) return map[string]any{ "success": false, "errors": errors, "message": "Please fix the following issues and try again", }, nil },})Content Moderation Guards
Section titled “Content Moderation Guards”PII Detection
Section titled “PII Detection”piiGuard := petalflow.NewGuardianNode("pii_guard", petalflow.GuardianNodeConfig{ Checks: []petalflow.GuardCheck{ // SSN pattern { Var: "user_input", Op: petalflow.OpNotMatches, Value: `\b\d{3}-\d{2}-\d{4}\b`, Message: "Input contains SSN pattern", }, // Credit card pattern { Var: "user_input", Op: petalflow.OpNotMatches, Value: `\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b`, Message: "Input contains credit card pattern", }, }, OnFail: petalflow.GuardActionRoute, FailRoute: "pii_redaction",})LLM-Based Content Moderation
Section titled “LLM-Based Content Moderation”Use an LLM to detect policy violations:
moderationNode := petalflow.NewLLMNode("content_moderator", client, petalflow.LLMNodeConfig{ Model: "gpt-4o-mini", SystemPrompt: `You are a content moderator. Analyze the input for policy violations.Respond with JSON: {"safe": true/false, "violations": ["list", "of", "violations"]}`, PromptTemplate: "Content to review: {{.Vars.user_content}}", OutputKey: "moderation_result", ResponseFormat: petalflow.ResponseFormatJSON,})
moderationGuard := petalflow.NewGuardianNode("moderation_guard", petalflow.GuardianNodeConfig{ Checks: []petalflow.GuardCheck{ { Var: "moderation_result.safe", Op: petalflow.OpEquals, Value: true, Message: "Content violates usage policy", }, }, OnFail: petalflow.GuardActionRoute, FailRoute: "content_blocked",})Combining Routers and Guards
Section titled “Combining Routers and Guards”Build robust workflows by combining routing and validation:
g := petalflow.NewGraph("support-workflow")
// Stage 1: Input validationg.AddNode(petalflow.NewGuardianNode("input_guard", petalflow.GuardianNodeConfig{ Checks: []petalflow.GuardCheck{ {Var: "ticket_id", Op: petalflow.OpNotEmpty, Message: "Ticket ID required"}, {Var: "message", Op: petalflow.OpNotEmpty, Message: "Message required"}, {Var: "message", Op: petalflow.OpMaxLength, Value: 50000, Message: "Message too long"}, }, OnFail: petalflow.GuardActionRoute, FailRoute: "invalid_input",}))
// Stage 2: Content moderationg.AddNode(petalflow.NewGuardianNode("content_guard", petalflow.GuardianNodeConfig{ Checks: []petalflow.GuardCheck{ {Var: "message", Op: petalflow.OpNotMatches, Value: blockedPatterns, Message: "Blocked content"}, }, OnFail: petalflow.GuardActionRoute, FailRoute: "content_blocked",}))
// Stage 3: Intent classificationg.AddNode(petalflow.NewLLMRouter("classify_intent", client, petalflow.LLMRouterConfig{ Model: "gpt-4o-mini", InputKey: "message", Categories: []petalflow.Category{ {Name: "billing", Description: "Billing and payment issues"}, {Name: "technical", Description: "Technical support"}, {Name: "general", Description: "General inquiries"}, },}))
// Stage 4: Priority routing within each categoryg.AddNode(petalflow.NewRuleRouter("priority_router", petalflow.RuleRouterConfig{ Routes: []petalflow.RouteRule{ {When: petalflow.RouteCondition{Var: "customer_tier", Op: petalflow.OpEquals, Value: "enterprise"}, To: "priority_queue"}, {When: petalflow.RouteCondition{Var: "sentiment", Op: petalflow.OpEquals, Value: "angry"}, To: "escalation_queue"}, }, Default: "standard_queue",}))
// Define edgesg.AddEdge("input_guard", "content_guard")g.AddEdge("content_guard", "classify_intent")g.AddEdge("classify_intent", "priority_router")g.SetEntry("input_guard")Testing Routing Logic
Section titled “Testing Routing Logic”Unit Testing Rules
Section titled “Unit Testing Rules”func TestPriorityRouter(t *testing.T) { router := petalflow.NewRuleRouter("test_router", petalflow.RuleRouterConfig{ Routes: []petalflow.RouteRule{ {When: petalflow.RouteCondition{Var: "priority", Op: petalflow.OpEquals, Value: "high"}, To: "urgent"}, {When: petalflow.RouteCondition{Var: "priority", Op: petalflow.OpEquals, Value: "low"}, To: "normal"}, }, Default: "normal", })
tests := []struct { name string priority string expected string }{ {"high priority routes to urgent", "high", "urgent"}, {"low priority routes to normal", "low", "normal"}, {"unknown priority uses default", "medium", "normal"}, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { env := petalflow.NewEnvelope() env.SetVar("priority", tt.priority)
target := router.Evaluate(env) assert.Equal(t, tt.expected, target) }) }}Testing Guards
Section titled “Testing Guards”func TestInputGuard(t *testing.T) { guard := petalflow.NewGuardianNode("test_guard", petalflow.GuardianNodeConfig{ Checks: []petalflow.GuardCheck{ {Var: "email", Op: petalflow.OpNotEmpty, Message: "Email required"}, {Var: "email", Op: petalflow.OpMatches, Value: `^.+@.+\..+$`, Message: "Invalid email"}, }, OnFail: petalflow.GuardActionReject, })
tests := []struct { name string email string shouldErr bool }{ {"valid email passes", "user@example.com", false}, {"empty email fails", "", true}, {"invalid format fails", "not-an-email", true}, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { env := petalflow.NewEnvelope() env.SetVar("email", tt.email)
err := guard.Execute(context.Background(), env) if tt.shouldErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) }}Integration Testing with Event Tracking
Section titled “Integration Testing with Event Tracking”func TestRoutingPath(t *testing.T) { graph := buildSupportGraph() runtime := petalflow.NewRuntime()
var visitedNodes []string handler := func(event petalflow.Event) { if event.Kind == petalflow.EventNodeStart { visitedNodes = append(visitedNodes, event.NodeID) } }
env := petalflow.NewEnvelope() env.SetVar("priority", "high") env.SetVar("message", "I need help urgently!")
_, err := runtime.Run(context.Background(), graph, env, petalflow.RunOptions{ EventHandler: handler, }) require.NoError(t, err)
// Verify the expected path was taken assert.Contains(t, visitedNodes, "input_guard") assert.Contains(t, visitedNodes, "classify_intent") assert.Contains(t, visitedNodes, "urgent_handler")}