Customer Support Router
Customer Support Router
Section titled “Customer Support Router”This example builds a complete customer support triage system that classifies incoming tickets by intent and urgency, then routes them to appropriate handlers with escalation paths.
What You’ll Build
Section titled “What You’ll Build”┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ Validate │────▶│ Classify │────▶│ Sentiment ││ Input │ │ Intent │ │ Analysis │└─────────────┘ └─────────────┘ └──────┬──────┘ │ ┌──────────────────────────┼──────────────────────────┐ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Billing │ │ Technical │ │ General │ │ Handler │ │ Handler │ │ Handler │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Priority │ │ Priority │ │ Format │ │ Router │ │ Router │ │ Response │ └─────────────┘ └─────────────┘ └─────────────┘Use Cases
Section titled “Use Cases”- Help desk automation: Automatically categorize and route support tickets
- Email triage: Sort incoming emails to appropriate departments
- Chat routing: Direct customer chat sessions to specialized agents
- Escalation management: Identify urgent issues requiring immediate attention
Complete Implementation
Section titled “Complete Implementation”Setup and Imports
Section titled “Setup and Imports”package main
import ( "context" "fmt" "log" "os" "time"
"github.com/petal-labs/iris/providers/openai" "github.com/petal-labs/petalflow" "github.com/petal-labs/petalflow/irisadapter")
func main() { // Initialize Iris provider provider := openai.New(os.Getenv("OPENAI_API_KEY")) client := irisadapter.NewProviderAdapter(provider)
// Build and run the support workflow graph := buildSupportGraph(client) runSupportWorkflow(graph)}Building the Graph
Section titled “Building the Graph”func buildSupportGraph(client *irisadapter.ProviderAdapter) petalflow.Graph { g := petalflow.NewGraph("support-triage")
// Stage 1: Input Validation validateNode := petalflow.NewGuardianNode("validate_input", petalflow.GuardianNodeConfig{ Checks: []petalflow.GuardCheck{ {Var: "ticket_id", Op: petalflow.OpNotEmpty, Message: "Ticket ID is required"}, {Var: "message", Op: petalflow.OpNotEmpty, Message: "Message content is required"}, {Var: "message", Op: petalflow.OpMaxLength, Value: 50000, Message: "Message exceeds maximum length"}, {Var: "customer_email", Op: petalflow.OpMatches, Value: `^.+@.+\..+$`, Message: "Invalid email format"}, }, OnFail: petalflow.GuardActionRoute, FailRoute: "validation_error", ErrorKey: "validation_errors", })
// Stage 2: Intent Classification (LLM-based) classifyNode := petalflow.NewLLMRouter("classify_intent", client, petalflow.LLMRouterConfig{ Model: "gpt-4o-mini", InputKey: "message", Categories: []petalflow.Category{ { Name: "billing", Description: "Billing, payments, invoices, charges, refunds, subscription changes, pricing questions", Examples: []string{ "I was charged twice this month", "How do I update my credit card?", "I want to cancel my subscription", }, }, { Name: "technical", Description: "Technical issues, bugs, errors, API problems, integration help, feature questions", Examples: []string{ "The API returns a 500 error", "I can't log into my account", "How do I integrate with webhooks?", }, }, { Name: "general", Description: "General inquiries, feedback, partnership requests, other topics", Examples: []string{ "What are your business hours?", "I'd like to provide feedback", "Do you offer enterprise plans?", }, }, }, })
// Stage 3: Sentiment Analysis sentimentNode := petalflow.NewLLMNode("analyze_sentiment", client, petalflow.LLMNodeConfig{ Model: "gpt-4o-mini", SystemPrompt: `Analyze customer sentiment. Respond with JSON:{"sentiment": "positive|neutral|negative|angry", "urgency": "low|medium|high", "escalate": true|false}
Set escalate=true if: customer mentions legal action, competitor switch, social media, or uses profanity.`, PromptTemplate: "Customer message: {{.Vars.message}}", OutputKey: "sentiment_analysis", ResponseFormat: petalflow.ResponseFormatJSON, })
// Billing Handler billingHandler := petalflow.NewLLMNode("billing_handler", client, petalflow.LLMNodeConfig{ Model: "gpt-4o", SystemPrompt: `You are a billing support specialist. Help with:- Payment issues and refunds- Subscription management- Invoice questions- Pricing clarification
Be empathetic and solution-oriented. If you can't resolve, explain next steps clearly.`, PromptTemplate: `Customer: {{.Vars.customer_email}}Ticket: {{.Vars.ticket_id}}Issue: {{.Vars.message}}
{{if .Vars.customer_history}}Previous interactions: {{.Vars.customer_history}}{{end}}
Provide a helpful response addressing their billing concern.`, OutputKey: "draft_response", Temperature: 0.3, })
// Technical Handler technicalHandler := petalflow.NewLLMNode("technical_handler", client, petalflow.LLMNodeConfig{ Model: "gpt-4o", SystemPrompt: `You are a technical support engineer. Help with:- Bug reports and error troubleshooting- API integration questions- Feature usage guidance- Performance issues
Be precise and technical. Include code examples when helpful.`, PromptTemplate: `Customer: {{.Vars.customer_email}}Ticket: {{.Vars.ticket_id}}Issue: {{.Vars.message}}
{{if .Vars.system_status}}Current system status: {{.Vars.system_status}}{{end}}
Provide technical guidance to resolve their issue.`, OutputKey: "draft_response", Temperature: 0.2, })
// General Handler generalHandler := petalflow.NewLLMNode("general_handler", client, petalflow.LLMNodeConfig{ Model: "gpt-4o", SystemPrompt: `You are a customer success representative. Help with:- General questions about the product- Feedback acknowledgment- Partnership inquiries- Anything not billing or technical
Be friendly and helpful. Connect customers to the right resources.`, PromptTemplate: `Customer: {{.Vars.customer_email}}Ticket: {{.Vars.ticket_id}}Message: {{.Vars.message}}
Provide a helpful, friendly response.`, OutputKey: "draft_response", Temperature: 0.5, })
// Priority Router (after each handler) priorityRouter := petalflow.NewRuleRouter("priority_router", petalflow.RuleRouterConfig{ Routes: []petalflow.RouteRule{ // Escalate angry customers or those flagged for escalation { When: petalflow.RouteCondition{ Or: []petalflow.RouteCondition{ {Var: "sentiment_analysis.escalate", Op: petalflow.OpEquals, Value: true}, {Var: "sentiment_analysis.sentiment", Op: petalflow.OpEquals, Value: "angry"}, }, }, To: "escalate_to_human", }, // High urgency goes to priority queue { When: petalflow.RouteCondition{ Var: "sentiment_analysis.urgency", Op: petalflow.OpEquals, Value: "high", }, To: "priority_queue", }, // VIP customers get priority { When: petalflow.RouteCondition{ Var: "customer_tier", Op: petalflow.OpIn, Value: []string{"enterprise", "premium"}, }, To: "priority_queue", }, }, Default: "standard_queue", })
// Escalation Handler escalateNode := petalflow.NewTransformNode("escalate_to_human", petalflow.TransformNodeConfig{ Transform: func(inputs map[string]any) (any, error) { return map[string]any{ "action": "escalate", "queue": "human_review", "reason": "Customer sentiment requires human attention", "priority": "high", "draft": inputs["draft_response"], "sentiment": inputs["sentiment_analysis"], }, nil }, InputKeys: []string{"draft_response", "sentiment_analysis"}, OutputKey: "routing_decision", })
// Priority Queue Handler priorityQueueNode := petalflow.NewTransformNode("priority_queue", petalflow.TransformNodeConfig{ Transform: func(inputs map[string]any) (any, error) { return map[string]any{ "action": "send", "queue": "priority", "response": inputs["draft_response"], "auto_send": true, }, nil }, InputKeys: []string{"draft_response"}, OutputKey: "routing_decision", })
// Standard Queue Handler standardQueueNode := petalflow.NewTransformNode("standard_queue", petalflow.TransformNodeConfig{ Transform: func(inputs map[string]any) (any, error) { return map[string]any{ "action": "send", "queue": "standard", "response": inputs["draft_response"], "auto_send": true, }, nil }, InputKeys: []string{"draft_response"}, OutputKey: "routing_decision", })
// Validation Error Handler validationErrorNode := petalflow.NewTransformNode("validation_error", petalflow.TransformNodeConfig{ Transform: func(inputs map[string]any) (any, error) { errors := inputs["validation_errors"].([]string) return map[string]any{ "action": "reject", "errors": errors, "message": "Please provide valid ticket information", }, nil }, InputKeys: []string{"validation_errors"}, OutputKey: "routing_decision", })
// Format Final Response formatNode := petalflow.NewTransformNode("format_response", petalflow.TransformNodeConfig{ Transform: func(inputs map[string]any) (any, error) { decision := inputs["routing_decision"].(map[string]any) return map[string]any{ "ticket_id": inputs["ticket_id"], "status": "processed", "routing": decision, "processed_at": time.Now().UTC(), }, nil }, InputKeys: []string{"routing_decision", "ticket_id"}, OutputKey: "final_result", })
// Add all nodes g.AddNode(validateNode) g.AddNode(classifyNode) g.AddNode(sentimentNode) g.AddNode(billingHandler) g.AddNode(technicalHandler) g.AddNode(generalHandler) g.AddNode(priorityRouter) g.AddNode(escalateNode) g.AddNode(priorityQueueNode) g.AddNode(standardQueueNode) g.AddNode(validationErrorNode) g.AddNode(formatNode)
// Define edges g.AddEdge("validate_input", "classify_intent") g.AddEdge("validate_input", "validation_error") // On validation failure g.AddEdge("classify_intent", "analyze_sentiment")
// Route from sentiment to appropriate handler based on classification g.AddEdge("analyze_sentiment", "billing_handler") g.AddEdge("analyze_sentiment", "technical_handler") g.AddEdge("analyze_sentiment", "general_handler")
// All handlers go through priority routing g.AddEdge("billing_handler", "priority_router") g.AddEdge("technical_handler", "priority_router") g.AddEdge("general_handler", "priority_router")
// Priority router outcomes g.AddEdge("priority_router", "escalate_to_human") g.AddEdge("priority_router", "priority_queue") g.AddEdge("priority_router", "standard_queue")
// All paths lead to format g.AddEdge("escalate_to_human", "format_response") g.AddEdge("priority_queue", "format_response") g.AddEdge("standard_queue", "format_response") g.AddEdge("validation_error", "format_response")
g.SetEntry("validate_input")
return g}Running the Workflow
Section titled “Running the Workflow”func runSupportWorkflow(graph petalflow.Graph) { runtime := petalflow.NewRuntime()
// Create envelope with ticket data env := petalflow.NewEnvelope() env.SetVar("ticket_id", "TKT-2024-001234") env.SetVar("customer_email", "jane.doe@example.com") env.SetVar("customer_tier", "premium") env.SetVar("message", `I've been charged twice for my subscription this month!This is the third time this has happened and I'm extremely frustrated.If this isn't resolved immediately, I'm switching to your competitor.`)
// Event handler for observability handler := func(event petalflow.Event) { switch event.Kind { case petalflow.EventNodeEnd: log.Printf("✓ %s completed in %v", event.NodeID, event.Duration) case petalflow.EventRouteDecision: log.Printf("→ Routed to: %s", event.Data["target"]) case petalflow.EventNodeError: log.Printf("✗ %s failed: %v", event.NodeID, event.Error) } }
// Execute ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel()
result, err := runtime.Run(ctx, graph, env, petalflow.RunOptions{ EventHandler: handler, }) if err != nil { log.Fatalf("Workflow failed: %v", err) }
// Output result finalResult := result.GetVar("final_result").(map[string]any) fmt.Printf("\n=== Ticket Processed ===\n") fmt.Printf("Ticket ID: %s\n", finalResult["ticket_id"]) fmt.Printf("Status: %s\n", finalResult["status"]) fmt.Printf("Routing: %+v\n", finalResult["routing"])}Example Output
Section titled “Example Output”✓ validate_input completed in 1ms→ Routed to: billing✓ classify_intent completed in 245ms✓ analyze_sentiment completed in 312ms✓ billing_handler completed in 1.2s→ Routed to: escalate_to_human✓ priority_router completed in 1ms✓ escalate_to_human completed in 1ms✓ format_response completed in 1ms
=== Ticket Processed ===Ticket ID: TKT-2024-001234Status: processedRouting: map[action:escalate queue:human_review reason:Customer sentiment requires human attention priority:high]Variations
Section titled “Variations”Adding SLA Tracking
Section titled “Adding SLA Tracking”// Add SLA metadata based on customer tierslaNode := petalflow.NewTransformNode("set_sla", petalflow.TransformNodeConfig{ Transform: func(inputs map[string]any) (any, error) { tier := inputs["customer_tier"].(string) slaHours := map[string]int{ "enterprise": 1, "premium": 4, "standard": 24, "free": 72, } return map[string]any{ "sla_hours": slaHours[tier], "sla_deadline": time.Now().Add(time.Duration(slaHours[tier]) * time.Hour), }, nil },})Multi-Language Support
Section titled “Multi-Language Support”// Detect language and translate if neededlanguageNode := petalflow.NewLLMNode("detect_language", client, petalflow.LLMNodeConfig{ Model: "gpt-4o-mini", PromptTemplate: `Detect the language of this text and respond with JSON:{"language": "en|es|fr|de|...", "confidence": 0.0-1.0}
Text: {{.Vars.message}}`, OutputKey: "language_detection", ResponseFormat: petalflow.ResponseFormatJSON,})
translateRouter := petalflow.NewRuleRouter("translate_check", petalflow.RuleRouterConfig{ Routes: []petalflow.RouteRule{ {When: petalflow.RouteCondition{Var: "language_detection.language", Op: petalflow.OpNotEquals, Value: "en"}, To: "translate"}, }, Default: "classify_intent",})