SWIRLS_
Cookbook

Triage Support Requests

Classify and route support tickets with an LLM and switch node.

Receive a support ticket, classify its complexity with an LLM, and route it to the right handler automatically.

webhook support_ticket {
  label: "Support Ticket"
  enabled: true
  schema: @json {
    {
      "type": "object",
      "required": ["ticket_id", "subject", "description", "customer_email"],
      "properties": {
        "ticket_id": { "type": "string" },
        "subject": { "type": "string" },
        "description": { "type": "string" },
        "customer_email": { "type": "string" },
        "priority": { "type": "string" }
      },
      "additionalProperties": false
    }
  }
}

graph triage_ticket {
  label: "Triage Ticket"
  description: "Classify complexity with an LLM, route to the right team"

  persistence {
    enabled: true
    condition: @ts {
      return true
    }
  }

  root {
    type: code
    label: "Parse ticket"
    inputSchema: @json {
      {
        "type": "object",
        "required": ["ticket_id", "subject", "description", "customer_email"],
        "properties": {
          "ticket_id": { "type": "string" },
          "subject": { "type": "string" },
          "description": { "type": "string" },
          "customer_email": { "type": "string" },
          "priority": { "type": "string" }
        },
        "additionalProperties": false
      }
    }
    outputSchema: @json {
      {
        "type": "object",
        "required": ["ticket_id", "subject", "description", "customer_email", "priority"],
        "properties": {
          "ticket_id": { "type": "string" },
          "subject": { "type": "string" },
          "description": { "type": "string" },
          "customer_email": { "type": "string" },
          "priority": { "type": "string" }
        },
        "additionalProperties": false
      }
    }
    code: @ts {
      const { ticket_id, subject, description, customer_email, priority } = context.nodes.root.input
      return {
        ticket_id,
        subject: subject.trim(),
        description: description.trim(),
        customer_email: customer_email.toLowerCase().trim(),
        priority: (priority ?? "normal").toLowerCase(),
      }
    }
  }

  node classify {
    type: ai
    label: "Classify ticket"
    kind: object
    model: "anthropic/claude-3.5-sonnet"
    temperature: 0.2
    prompt: @ts {
      const { subject, description, customer_email, priority } = context.nodes.root.output
      return `Classify this support ticket.

Subject: ${subject}
Description: ${description}
Customer: ${customer_email}
Priority: ${priority}

Return JSON with:
- "complexity": one of "simple", "medium", "complex"
- "category": short label (billing, technical, account, etc.)
- "suggested_response": draft reply if simple, null otherwise`
    }
    schema: @json {
      {
        "type": "object",
        "required": ["complexity", "category"],
        "properties": {
          "complexity": { "type": "string", "enum": ["simple", "medium", "complex"] },
          "category": { "type": "string" },
          "suggested_response": { "type": "string" }
        }
      }
    }
  }

  node route {
    type: switch
    label: "Route by complexity"
    cases: ["simple", "medium", "complex"]
    router: @ts {
      return context.nodes.classify.output.complexity
    }
  }

  node auto_respond {
    type: email
    label: "Auto-respond"
    from: @ts { return "[email protected]" }
    to: @ts { return context.nodes.root.output.customer_email }
    subject: @ts { return `Re: ${context.nodes.root.output.subject}` }
    text: @ts {
      return context.nodes.classify.output.suggested_response ?? "We've received your request and will follow up shortly."
    }
  }

  node assign_agent {
    type: http
    label: "Assign to agent"
    method: "POST"
    url: @ts { return "https://api.example.com/tickets/assign" }
    headers: { "Authorization": @ts { return `Bearer ${context.secrets.SUPPORT_API_KEY}` } }
    body: @ts {
      return JSON.stringify({
        ticket_id: context.nodes.root.output.ticket_id,
        category: context.nodes.classify.output.category,
        priority: "normal"
      })
    }
  }

  node escalate {
    type: http
    label: "Escalate"
    method: "POST"
    url: @ts { return "https://api.example.com/tickets/escalate" }
    headers: { "Authorization": @ts { return `Bearer ${context.secrets.SUPPORT_API_KEY}` } }
    body: @ts {
      return JSON.stringify({
        ticket_id: context.nodes.root.output.ticket_id,
        category: context.nodes.classify.output.category,
        priority: "urgent"
      })
    }
  }

  flow {
    root -> classify
    classify -> route
    route -["simple"]-> auto_respond
    route -["medium"]-> assign_agent
    route -["complex"]-> escalate
  }
}

trigger on_support_ticket {
  webhook:support_ticket -> triage_ticket
  enabled: true
}

How it works

  • The webhook receives ticket data from your support system and validates it against the schema.
  • The root code node normalizes the payload: trimming whitespace, lowercasing the email, and defaulting priority to normal.
  • The AI node classifies complexity (simple, medium, or complex), assigns a category, and drafts a reply for simple tickets.
  • The switch node reads complexity from the AI output and branches accordingly.
  • Simple tickets receive an automated email reply using the AI-drafted response.
  • Medium tickets are assigned to an agent via your support API with normal priority.
  • Complex tickets are escalated via your support API with urgent priority.
  • Persistence is enabled unconditionally, so every classification is stored for trend analysis.

Run it

swirls deploy

Send a test payload to the webhook URL:

curl -X POST https://your-project.swirls.dev/webhooks/support_ticket \
  -H "Content-Type: application/json" \
  -d '{
    "ticket_id": "TKT-001",
    "subject": "Cannot log in",
    "description": "I reset my password but the link expired.",
    "customer_email": "[email protected]",
    "priority": "normal"
  }'

Open the Executions view to inspect the run and verify the branch taken.

Customize

  • Add a review node on the simple branch to approve auto-responses before they send.
  • Add HTTP nodes before classify to fetch customer history and pass it into the prompt.
  • Query the persistence stream to analyze triage accuracy and tune the prompt over time.
  • Swap the webhook for a form trigger to support manual ticket submission.

On this page