Customer Support Agent

Build a customer support voice agent with FAQ handling, ticket creation, and human handoff.

Customer Support Agent

A complete example of a customer support voice agent that handles FAQs, creates tickets, and transfers to human agents when needed.

Configuration

{
  "agent": {
    "name": "Acme Support",
    "language": "en-US",

    "llmProvider": "gemini-2.5",
    "llmModel": "gemini-2.5-flash-lite",
    "llmTemperature": 0.7,

    "sttProvider": "deepgram",
    "sttModel": "nova-3",
    "sttConfig": {
      "endpointing": 300,
      "interimResults": true,
      "keywords": ["Acme:2", "order:2", "refund:2"]
    },

    "ttsProvider": "cartesia",
    "ttsVoice": "95856005-0332-41b0-935f-352e296aa0df",

    "greetingMessage": "Hello! Thank you for calling Acme Support. My name is Alex. How can I help you today?",

    "allowInterruptions": true,

    "prompt": "...",

    "tools": [],

    "webhooks": {
      "url": "https://your-server.com/webhooks/voice",
      "events": ["call.ended", "transcript.updated"]
    }
  }
}

System Prompt

You are Alex, a friendly and helpful customer support agent for Acme Corporation.

## Your Role
- Help customers with product questions, order issues, and account problems
- Look up orders and account information using the available tools
- Create support tickets for issues you can't resolve
- Transfer to a human agent for complex or sensitive issues

## Guidelines

### Response Style
- Keep responses concise (1-2 sentences max)
- Be warm, empathetic, and professional
- Use the customer's name when you know it
- Speak naturally, like a real person would

### Information Gathering
- Ask for order numbers to look up orders
- Verify identity by asking for email or phone on file
- Don't ask for more than one piece of info at a time

### Issue Resolution
1. Listen to the customer's issue completely
2. Acknowledge their concern with empathy
3. Look up relevant information
4. Provide a clear solution or next steps
5. Confirm they're satisfied before ending

### Escalation Criteria
Transfer to a human agent if:
- Customer explicitly requests a human
- Issue involves refunds over $100
- Customer is very upset or frustrated
- Issue requires manager approval
- Technical issues you can't resolve

### What NOT to do
- Never make up information
- Don't promise specific resolution times
- Don't provide legal or financial advice
- Don't share other customers' information

## Available Tools
- get_order_status: Look up order by ID
- get_account_info: Get customer account details
- create_ticket: Create a support ticket
- transfer_to_human: Connect to human agent

## Sample Conversations

Customer: "Where's my order?"
You: "I'd be happy to help you track your order. Could you give me your order number?"

Customer: "I want a refund"
You: "I understand you'd like a refund. Let me look into that for you. What's the order number?"

Customer: "I want to speak to a manager"
You: "Of course, I'll transfer you to a human agent right away. Please hold."

Tools

Get Order Status

{
  "type": "function",
  "function": {
    "name": "get_order_status",
    "description": "Look up the status of a customer order by order ID",
    "parameters": {
      "type": "object",
      "properties": {
        "order_id": {
          "type": "string",
          "description": "The order ID (e.g., ORD-12345)"
        }
      },
      "required": ["order_id"]
    }
  }
}

Tool Handler:

func handleGetOrderStatus(params map[string]any) (string, error) {
    orderID := params["order_id"].(string)

    order, err := orderService.GetOrder(orderID)
    if err != nil {
        return "I couldn't find that order. Could you double-check the order number?", nil
    }

    return fmt.Sprintf("Order %s was placed on %s. Status: %s. %s",
        order.ID,
        order.CreatedAt.Format("January 2"),
        order.Status,
        getStatusDetails(order),
    ), nil
}

func getStatusDetails(order *Order) string {
    switch order.Status {
    case "processing":
        return "It's being prepared and should ship within 1-2 business days."
    case "shipped":
        return fmt.Sprintf("It shipped on %s via %s. Tracking: %s. Expected delivery: %s.",
            order.ShippedAt.Format("January 2"),
            order.Carrier,
            order.TrackingNumber,
            order.EstimatedDelivery.Format("January 2"))
    case "delivered":
        return fmt.Sprintf("It was delivered on %s.", order.DeliveredAt.Format("January 2"))
    default:
        return ""
    }
}

Create Ticket

{
  "type": "function",
  "function": {
    "name": "create_ticket",
    "description": "Create a support ticket for follow-up",
    "parameters": {
      "type": "object",
      "properties": {
        "category": {
          "type": "string",
          "enum": ["order", "refund", "technical", "billing", "other"],
          "description": "Ticket category"
        },
        "summary": {
          "type": "string",
          "description": "Brief summary of the issue"
        },
        "priority": {
          "type": "string",
          "enum": ["low", "medium", "high"],
          "description": "Issue priority"
        }
      },
      "required": ["category", "summary"]
    }
  }
}

Transfer to Human

{
  "type": "function",
  "function": {
    "name": "transfer_to_human",
    "description": "Transfer the call to a human agent",
    "parameters": {
      "type": "object",
      "properties": {
        "reason": {
          "type": "string",
          "description": "Reason for transfer"
        },
        "department": {
          "type": "string",
          "enum": ["general", "billing", "technical", "manager"],
          "description": "Department to transfer to"
        }
      },
      "required": ["reason"]
    }
  }
}

Webhook Integration

Call Ended Webhook

type CallEndedPayload struct {
    CallID     string `json:"call_id"`
    AgentID    string `json:"agent_id"`
    Duration   int    `json:"duration"`
    Transcript struct {
        Summary string `json:"summary"`
        Turns   []Turn `json:"turns"`
    } `json:"transcript"`
    Outcome string `json:"outcome"` // resolved, transferred, dropped
}

func handleCallEnded(payload CallEndedPayload) {
    // Log to CRM
    crmClient.LogCall(CRMCallLog{
        CustomerPhone: payload.CallerID,
        Duration:      payload.Duration,
        Summary:       payload.Transcript.Summary,
        Outcome:       payload.Outcome,
    })

    // Update analytics
    analytics.TrackCall(payload)
}

Analytics

Track key metrics:

type SupportMetrics struct {
    TotalCalls          int
    AverageHandleTime   time.Duration
    FirstCallResolution float64
    TransferRate        float64
    CustomerSatisfaction float64
}

func trackMetrics(call *Call) {
    metrics.Increment("support.calls.total")
    metrics.Gauge("support.calls.duration", call.Duration.Seconds())

    if call.Outcome == "resolved" {
        metrics.Increment("support.calls.resolved")
    } else if call.Outcome == "transferred" {
        metrics.Increment("support.calls.transferred")
    }
}

Best Practices

1. Warm Handoff

When transferring to a human:

func transferToHuman(session *Session, reason string) {
    // Brief the human agent
    briefing := fmt.Sprintf(
        "Customer %s calling about: %s. Context: %s",
        session.CustomerName,
        reason,
        session.ConversationSummary,
    )

    session.TTS.Speak("Let me transfer you to a specialist who can help. One moment please.")

    // Transfer with context
    telephony.Transfer(session.CallID, humanQueue, briefing)
}

2. Handle Common Intents

var commonIntents = map[string]string{
    "hours": "We're open Monday through Friday, 9 AM to 6 PM Eastern time.",
    "location": "We're located at 123 Main Street. Would you like directions?",
    "website": "You can visit us at acme.com. Is there something specific I can help you find?",
}

3. Empathy Phrases

var empathyPhrases = []string{
    "I understand how frustrating that must be.",
    "I'm sorry to hear you're having trouble with that.",
    "I completely understand your concern.",
    "That's definitely not the experience we want you to have.",
}

Testing

Test Scenarios

func TestOrderStatusFlow(t *testing.T) {
    agent := setupTestAgent()

    // Customer asks about order
    response := agent.Process("Where is my order ORD-12345?")
    assert.Contains(t, response, "order")

    // Verify tool was called
    assert.True(t, agent.ToolCalled("get_order_status"))
}

func TestTransferFlow(t *testing.T) {
    agent := setupTestAgent()

    response := agent.Process("I want to speak to a manager")
    assert.Contains(t, response, "transfer")
    assert.True(t, agent.ToolCalled("transfer_to_human"))
}

Next Steps