# AI Voice Agents for Customer Support

> Deploy intelligent AI voice agents for 24/7 customer support with automated tier-1 handling, FAQ resolution, ticket creation, smart escalation, and helpdesk integration.

Source: https://edesy.in/docs/voice-agent/use-cases/customer-support

Transform your customer support operations with AI voice agents that handle tier-1 inquiries, resolve FAQs instantly, create tickets automatically, and escalate to human agents when needed.

## Overview

Customer support AI voice agents can reduce call center costs by 40-60% while maintaining high customer satisfaction through:

- **24/7 Availability**: Handle calls outside business hours
- **Instant Response**: Zero wait times for common inquiries
- **Consistent Quality**: Same professional service every call
- **Scalable Capacity**: Handle surge volumes without staffing

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                    Customer Support Voice Agent Flow                         │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   Inbound Call                                                               │
│       │                                                                      │
│       ▼                                                                      │
│   ┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐   │
│   │  Authentication  │────▶│  Intent Detection│────▶│  Resolution Path │   │
│   │  & Greeting      │     │  & Classification│     │  Selection       │   │
│   └──────────────────┘     └──────────────────┘     └──────────────────┘   │
│                                                              │               │
│              ┌───────────────────┬───────────────────────────┤               │
│              │                   │                           │               │
│              ▼                   ▼                           ▼               │
│   ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐           │
│   │  FAQ Resolution  │ │  Ticket Creation │ │  Human Escalation│           │
│   │  (Knowledge Base)│ │  (Helpdesk API)  │ │  (Warm Transfer) │           │
│   └──────────────────┘ └──────────────────┘ └──────────────────┘           │
│              │                   │                           │               │
│              └───────────────────┴───────────────────────────┘               │
│                                  │                                           │
│                                  ▼                                           │
│                       ┌──────────────────┐                                   │
│                       │  Post-Call       │                                   │
│                       │  Analytics & CRM │                                   │
│                       └──────────────────┘                                   │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

## Use Cases

### 1. Tier-1 Support Automation

Handle common support requests without human intervention:

| Request Type | Example Queries | Resolution Method |
|--------------|-----------------|-------------------|
| Order Status | "Where's my order?" | API lookup |
| Account Info | "What's my balance?" | Database query |
| Password Reset | "I forgot my password" | Trigger reset flow |
| Store Hours | "When are you open?" | Knowledge base |
| Refund Status | "Where's my refund?" | CRM lookup |

### 2. FAQ Handling

Resolve frequently asked questions instantly:

```go
type FAQHandler struct {
    knowledgeBase *KnowledgeBase
    vectorStore   *VectorStore
}

func (h *FAQHandler) HandleQuery(ctx context.Context, query string) (string, error) {
    // Semantic search for relevant FAQ entries
    results, err := h.vectorStore.Search(ctx, query, SearchOptions{
        TopK:       5,
        Threshold:  0.75,
        Collection: "faq",
    })
    if err != nil {
        return "", err
    }

    if len(results) == 0 || results[0].Score < 0.75 {
        return "", ErrNoMatchingFAQ
    }

    // Return the best matching answer
    return results[0].Answer, nil
}
```

### 3. Automated Ticket Creation

Create support tickets with full context:

```go
type TicketCreator struct {
    helpdeskClient *HelpdeskClient
}

func (t *TicketCreator) CreateTicket(ctx context.Context, params TicketParams) (*Ticket, error) {
    ticket := &Ticket{
        Subject:      params.Summary,
        Description:  buildDescription(params),
        Priority:     inferPriority(params.Sentiment, params.IssueType),
        Category:     params.Category,
        CustomerID:   params.CustomerID,
        CustomerPhone: params.CallerPhone,
        Source:       "voice_agent",
        Tags:         params.ExtractedTags,
        CustomFields: map[string]any{
            "call_id":       params.CallID,
            "transcript":    params.Transcript,
            "sentiment":     params.Sentiment,
            "call_duration": params.Duration,
        },
    }

    return t.helpdeskClient.CreateTicket(ctx, ticket)
}

func buildDescription(params TicketParams) string {
    return fmt.Sprintf(`
## Issue Summary
%s

## Customer Context
- Phone: %s
- Account ID: %s
- Call Duration: %s

## Conversation Transcript
%s

## AI Agent Analysis
- Sentiment: %s
- Category: %s
- Suggested Resolution: %s
`,
        params.Summary,
        params.CallerPhone,
        params.CustomerID,
        params.Duration,
        params.Transcript,
        params.Sentiment,
        params.Category,
        params.SuggestedResolution,
    )
}
```

### 4. Smart Escalation

Route complex issues to the right human agents:

```go
type EscalationRouter struct {
    queueManager *QueueManager
    skillMatrix  *SkillMatrix
}

type EscalationCriteria struct {
    ExplicitRequest    bool     // Customer asked for human
    HighValueCustomer  bool     // VIP or high LTV
    NegativeSentiment  bool     // Frustrated customer
    ComplexIssue       bool     // Multi-part problem
    PolicyException    bool     // Requires manager approval
    FailedAttempts     int      // AI couldn't resolve
    IssueCategories    []string // Technical, billing, etc.
}

func (r *EscalationRouter) ShouldEscalate(ctx context.Context, criteria EscalationCriteria) bool {
    // Immediate escalation triggers
    if criteria.ExplicitRequest {
        return true
    }
    if criteria.HighValueCustomer && criteria.NegativeSentiment {
        return true
    }
    if criteria.FailedAttempts >= 2 {
        return true
    }
    if criteria.PolicyException {
        return true
    }

    return false
}

func (r *EscalationRouter) Route(ctx context.Context, params EscalationParams) error {
    // Find best available agent based on skills
    agent, err := r.skillMatrix.FindBestAgent(ctx, SkillQuery{
        Categories:  params.IssueCategories,
        Language:    params.Language,
        Priority:    params.Priority,
    })
    if err != nil {
        // Fallback to general queue
        return r.queueManager.AddToQueue(ctx, "general", params)
    }

    // Warm transfer with context
    return r.transferWithBriefing(ctx, agent, params)
}

func (r *EscalationRouter) transferWithBriefing(ctx context.Context, agent *Agent, params EscalationParams) error {
    briefing := fmt.Sprintf(
        "Incoming transfer: %s calling about %s. Sentiment: %s. Summary: %s",
        params.CustomerName,
        params.IssueCategory,
        params.Sentiment,
        params.ConversationSummary,
    )

    return r.queueManager.TransferToAgent(ctx, agent.ID, params.CallID, briefing)
}
```

### 5. After-Hours Support

Handle calls when human agents are unavailable:

```go
type AfterHoursHandler struct {
    businessHours *BusinessHoursConfig
    callbackQueue *CallbackQueue
    voicemailBox  *VoicemailService
}

func (h *AfterHoursHandler) HandleCall(ctx context.Context, session *Session) error {
    isBusinessHours := h.businessHours.IsOpen(time.Now())

    if !isBusinessHours {
        // Play after-hours greeting
        session.TTS.Speak("Thank you for calling. Our office is currently closed. " +
            "I'm an AI assistant and can help with many requests.")

        // Try to resolve with AI
        resolved, err := h.attemptResolution(ctx, session)
        if resolved {
            return nil
        }

        // Offer callback or voicemail
        return h.offerAlternatives(ctx, session)
    }

    return nil
}

func (h *AfterHoursHandler) offerAlternatives(ctx context.Context, session *Session) error {
    session.TTS.Speak("I wasn't able to fully resolve your issue. " +
        "Would you like to schedule a callback during business hours, " +
        "or leave a voicemail?")

    // Wait for response and route accordingly
    response := session.WaitForResponse(ctx, 10*time.Second)

    if containsIntent(response, "callback") {
        return h.scheduleCallback(ctx, session)
    }

    return h.recordVoicemail(ctx, session)
}

func (h *AfterHoursHandler) scheduleCallback(ctx context.Context, session *Session) error {
    session.TTS.Speak("When would you like us to call you back?")
    preferredTime := session.WaitForResponse(ctx, 15*time.Second)

    callback := &CallbackRequest{
        CustomerPhone: session.CallerNumber,
        PreferredTime: parseTimePreference(preferredTime),
        Context:       session.GetConversationSummary(),
        Priority:      inferPriority(session.Sentiment),
    }

    err := h.callbackQueue.Schedule(ctx, callback)
    if err != nil {
        session.TTS.Speak("I'm sorry, I couldn't schedule the callback. Please try again later.")
        return err
    }

    session.TTS.Speak(fmt.Sprintf(
        "I've scheduled a callback for %s. You'll receive a call from our support team.",
        formatTimeForSpeech(callback.PreferredTime),
    ))

    return nil
}
```

## Agent Configuration

### Complete Configuration Example

```json
{
  "agent": {
    "name": "TechSupport AI",
    "description": "Tier-1 technical support voice agent",
    "language": "en-US",

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

    "sttProvider": "deepgram",
    "sttModel": "nova-3",
    "sttConfig": {
      "endpointing": 300,
      "interimResults": true,
      "punctuate": true,
      "profanityFilter": false,
      "keywords": [
        "order:2", "refund:2", "cancel:2",
        "password:2", "account:2", "billing:2"
      ]
    },

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

    "greetingMessage": "Thank you for calling TechCorp Support. My name is Alex, your virtual assistant. How can I help you today?",

    "endCallMessage": "Thank you for calling TechCorp. Have a great day!",

    "allowInterruptions": true,
    "interruptionThreshold": 150,

    "silenceTimeout": 10000,
    "maxCallDuration": 900000,

    "prompt": "...",

    "tools": [],

    "transferConfig": {
      "enabled": true,
      "warmTransfer": true,
      "departments": {
        "technical": "+14155551001",
        "billing": "+14155551002",
        "sales": "+14155551003",
        "manager": "+14155551004"
      },
      "queueIntegration": {
        "type": "zendesk",
        "queueId": "support_queue_1"
      }
    },

    "webhooks": {
      "url": "https://api.yourcompany.com/webhooks/voice",
      "events": [
        "call.started",
        "call.ended",
        "transcript.updated",
        "ticket.created",
        "transfer.initiated"
      ],
      "headers": {
        "Authorization": "Bearer {{WEBHOOK_SECRET}}"
      }
    },

    "analytics": {
      "trackSentiment": true,
      "trackTopics": true,
      "trackResolution": true,
      "customEvents": ["faq_used", "kb_search", "escalation_reason"]
    }
  }
}
```

## System Prompt

### Optimized Support Agent Prompt

```
You are Alex, a friendly and knowledgeable customer support agent for TechCorp.

## Your Mission
Help customers resolve their issues quickly and completely. Your goal is first-call resolution whenever possible.

## Communication Style
- Speak naturally and conversationally, like a real person
- Keep responses concise: 1-2 sentences for simple queries, 2-3 for complex ones
- Use the customer's name when you know it
- Be empathetic but efficient
- Avoid filler words and robotic phrases
- Never say "I'm an AI" unless directly asked

## Authentication Flow
Before accessing account information:
1. Greet the customer warmly
2. Ask: "May I have your account number or the phone number on your account?"
3. Verify: "And can you confirm the last 4 digits of your registered email?"
4. Only proceed after verification

## Issue Resolution Framework

### Step 1: Active Listening
- Let the customer explain fully before responding
- Acknowledge their concern: "I understand you're having trouble with..."
- Clarify if needed: "Just to make sure I understand..."

### Step 2: Information Gathering
- Ask ONE question at a time
- Use available tools to look up information
- Don't make the customer repeat themselves

### Step 3: Resolution
- Provide clear, actionable solutions
- For multi-step solutions, break them down clearly
- Confirm understanding: "Does that make sense?"

### Step 4: Verification
- Ask if the issue is resolved
- Check if they need help with anything else
- Thank them for their patience

## Tool Usage Guidelines

### get_order_status
- Use when customer asks about order, delivery, or shipment
- Always provide: current status, expected date, tracking if available

### search_knowledge_base
- Use for product questions, how-to queries, policy questions
- If no good match, acknowledge and offer alternatives

### create_ticket
- Use when you cannot fully resolve the issue
- Include: detailed summary, customer sentiment, attempted resolutions
- Inform customer: "I've created a support ticket..."

### transfer_to_human
- Use for: explicit requests, billing disputes over $100, angry customers,
  issues requiring system access you don't have
- Always brief the customer: "I'm connecting you to a specialist..."

## Escalation Rules

IMMEDIATELY transfer when:
- Customer says "speak to a human" or "talk to a person"
- Issue involves refunds over $100
- Customer mentions "lawyer", "sue", or "report to"
- After 2 failed resolution attempts
- Customer is clearly very upset or frustrated

## What You Can Handle
- Order status and tracking
- Account information lookups
- Password reset initiation
- FAQ and how-to questions
- Basic troubleshooting
- Appointment scheduling
- Ticket creation

## What Requires Human Agent
- Refunds over $100
- Account closures
- Billing disputes
- Technical escalations
- Complaints about staff
- Legal or compliance issues

## Prohibited Actions
- Never share other customers' information
- Never make promises about refunds without system confirmation
- Never provide legal, medical, or financial advice
- Never guess at information - look it up or ask
- Never argue with the customer

## Sample Responses

Customer: "My order hasn't arrived"
You: "I'm sorry to hear that. Let me look into it right away. Could you give me your order number?"

Customer: "This is ridiculous, I've been waiting for weeks!"
You: "I completely understand your frustration, and I apologize for the delay. Let me find out exactly where your order is and see what we can do to make this right."

Customer: "I want to cancel my subscription"
You: "I can help you with that. Before I process the cancellation, may I ask what prompted this decision? There might be something I can help with."

Customer: "Just connect me to a real person"
You: "Of course, I'll transfer you right away. One moment please."
```

## Tools for Customer Support

### 1. Get Order Status

```json
{
  "type": "function",
  "function": {
    "name": "get_order_status",
    "description": "Retrieve the current status of a customer order including shipping details and tracking information",
    "parameters": {
      "type": "object",
      "properties": {
        "order_id": {
          "type": "string",
          "description": "The order ID (e.g., ORD-12345)"
        },
        "customer_phone": {
          "type": "string",
          "description": "Customer phone number for verification"
        }
      },
      "required": ["order_id"]
    }
  }
}
```

### 2. Search Knowledge Base

```json
{
  "type": "function",
  "function": {
    "name": "search_knowledge_base",
    "description": "Search the knowledge base for answers to customer questions about products, policies, and procedures",
    "parameters": {
      "type": "object",
      "properties": {
        "query": {
          "type": "string",
          "description": "The customer's question or topic to search for"
        },
        "category": {
          "type": "string",
          "enum": ["products", "policies", "troubleshooting", "billing", "shipping", "returns"],
          "description": "Category to search within"
        }
      },
      "required": ["query"]
    }
  }
}
```

### 3. Create Support Ticket

```json
{
  "type": "function",
  "function": {
    "name": "create_ticket",
    "description": "Create a support ticket for issues that require follow-up or human intervention",
    "parameters": {
      "type": "object",
      "properties": {
        "subject": {
          "type": "string",
          "description": "Brief summary of the issue (max 100 chars)"
        },
        "description": {
          "type": "string",
          "description": "Detailed description of the issue and any attempted resolutions"
        },
        "category": {
          "type": "string",
          "enum": ["order_issue", "product_defect", "billing", "technical", "account", "other"],
          "description": "Issue category"
        },
        "priority": {
          "type": "string",
          "enum": ["low", "medium", "high", "urgent"],
          "description": "Issue priority based on impact and urgency"
        },
        "customer_id": {
          "type": "string",
          "description": "Customer account ID"
        }
      },
      "required": ["subject", "description", "category"]
    }
  }
}
```

### 4. Get Customer Account

```json
{
  "type": "function",
  "function": {
    "name": "get_customer_account",
    "description": "Retrieve customer account information for verification and support",
    "parameters": {
      "type": "object",
      "properties": {
        "phone_number": {
          "type": "string",
          "description": "Customer's registered phone number"
        },
        "account_id": {
          "type": "string",
          "description": "Customer's account ID"
        },
        "email": {
          "type": "string",
          "description": "Customer's email address"
        }
      }
    }
  }
}
```

### 5. Transfer to Human Agent

```json
{
  "type": "function",
  "function": {
    "name": "transfer_to_human",
    "description": "Transfer the call to a human agent. Use when customer requests it, issue is complex, or escalation criteria are met.",
    "parameters": {
      "type": "object",
      "properties": {
        "department": {
          "type": "string",
          "enum": ["general_support", "technical", "billing", "sales", "manager"],
          "description": "Department to transfer to"
        },
        "reason": {
          "type": "string",
          "description": "Brief reason for transfer (shared with receiving agent)"
        },
        "priority": {
          "type": "string",
          "enum": ["normal", "high", "urgent"],
          "description": "Transfer priority - urgent for very frustrated customers"
        },
        "context_summary": {
          "type": "string",
          "description": "Summary of conversation so far"
        }
      },
      "required": ["department", "reason"]
    }
  }
}
```

### 6. Schedule Callback

```json
{
  "type": "function",
  "function": {
    "name": "schedule_callback",
    "description": "Schedule a callback from a human agent when immediate transfer isn't possible",
    "parameters": {
      "type": "object",
      "properties": {
        "preferred_time": {
          "type": "string",
          "description": "Customer's preferred callback time (e.g., 'tomorrow morning', '2pm')"
        },
        "phone_number": {
          "type": "string",
          "description": "Phone number to call back"
        },
        "issue_summary": {
          "type": "string",
          "description": "Brief summary of the issue for the callback agent"
        },
        "department": {
          "type": "string",
          "enum": ["general_support", "technical", "billing"],
          "description": "Department for callback"
        }
      },
      "required": ["preferred_time", "issue_summary"]
    }
  }
}
```

## Tool Handlers Implementation

```go
package tools

import (
    "context"
    "encoding/json"
    "fmt"
    "time"
)

type SupportToolHandler struct {
    orderService   *OrderService
    knowledgeBase  *KnowledgeBaseClient
    helpdeskClient *HelpdeskClient
    crmClient      *CRMClient
    callbackQueue  *CallbackQueue
    transferHandler *TransferHandler
}

func (h *SupportToolHandler) Execute(ctx context.Context, call ToolCall) (any, error) {
    switch call.Name {
    case "get_order_status":
        return h.getOrderStatus(ctx, call.Arguments)
    case "search_knowledge_base":
        return h.searchKnowledgeBase(ctx, call.Arguments)
    case "create_ticket":
        return h.createTicket(ctx, call.Arguments)
    case "get_customer_account":
        return h.getCustomerAccount(ctx, call.Arguments)
    case "transfer_to_human":
        return h.transferToHuman(ctx, call.Arguments)
    case "schedule_callback":
        return h.scheduleCallback(ctx, call.Arguments)
    default:
        return nil, fmt.Errorf("unknown function: %s", call.Name)
    }
}

func (h *SupportToolHandler) getOrderStatus(ctx context.Context, args json.RawMessage) (any, error) {
    var params struct {
        OrderID       string `json:"order_id"`
        CustomerPhone string `json:"customer_phone"`
    }
    if err := json.Unmarshal(args, &params); err != nil {
        return nil, err
    }

    order, err := h.orderService.GetOrder(ctx, params.OrderID)
    if err != nil {
        return map[string]any{
            "found": false,
            "message": "I couldn't find that order. Please verify the order number.",
        }, nil
    }

    response := map[string]any{
        "found":       true,
        "order_id":    order.ID,
        "status":      order.Status,
        "created_at":  order.CreatedAt.Format("January 2"),
        "total":       fmt.Sprintf("$%.2f", order.Total),
    }

    switch order.Status {
    case "processing":
        response["message"] = fmt.Sprintf("Your order is being processed and should ship within %d business days.",
            order.EstimatedShipDays)
    case "shipped":
        response["shipped_at"] = order.ShippedAt.Format("January 2")
        response["carrier"] = order.Carrier
        response["tracking_number"] = order.TrackingNumber
        response["estimated_delivery"] = order.EstimatedDelivery.Format("January 2")
        response["message"] = fmt.Sprintf("Your order shipped on %s via %s. Expected delivery: %s.",
            order.ShippedAt.Format("January 2"),
            order.Carrier,
            order.EstimatedDelivery.Format("January 2"))
    case "delivered":
        response["delivered_at"] = order.DeliveredAt.Format("January 2")
        response["message"] = fmt.Sprintf("Your order was delivered on %s.", order.DeliveredAt.Format("January 2"))
    case "cancelled":
        response["message"] = "This order has been cancelled. A refund should appear within 5-7 business days."
    }

    return response, nil
}

func (h *SupportToolHandler) searchKnowledgeBase(ctx context.Context, args json.RawMessage) (any, error) {
    var params struct {
        Query    string `json:"query"`
        Category string `json:"category"`
    }
    if err := json.Unmarshal(args, &params); err != nil {
        return nil, err
    }

    results, err := h.knowledgeBase.Search(ctx, KBSearchParams{
        Query:     params.Query,
        Category:  params.Category,
        TopK:      3,
        Threshold: 0.7,
    })
    if err != nil {
        return map[string]any{
            "found": false,
            "message": "I couldn't search the knowledge base right now. Let me try to help another way.",
        }, nil
    }

    if len(results) == 0 {
        return map[string]any{
            "found": false,
            "message": "I couldn't find specific information about that. Would you like me to create a ticket for follow-up?",
        }, nil
    }

    return map[string]any{
        "found":   true,
        "answer":  results[0].Content,
        "source":  results[0].ArticleTitle,
        "url":     results[0].ArticleURL,
        "related": extractTitles(results[1:]),
    }, nil
}

func (h *SupportToolHandler) createTicket(ctx context.Context, args json.RawMessage) (any, error) {
    var params struct {
        Subject     string `json:"subject"`
        Description string `json:"description"`
        Category    string `json:"category"`
        Priority    string `json:"priority"`
        CustomerID  string `json:"customer_id"`
    }
    if err := json.Unmarshal(args, &params); err != nil {
        return nil, err
    }

    // Get session context
    session := getSessionFromContext(ctx)

    ticket, err := h.helpdeskClient.CreateTicket(ctx, &Ticket{
        Subject:     params.Subject,
        Description: params.Description,
        Category:    params.Category,
        Priority:    normalizePriority(params.Priority),
        CustomerID:  params.CustomerID,
        CustomerPhone: session.CallerNumber,
        Source:      "voice_agent",
        Tags:        []string{"ai_created", session.AgentID},
        CustomFields: map[string]any{
            "call_id":       session.CallID,
            "call_duration": time.Since(session.StartTime).Seconds(),
            "sentiment":     session.Sentiment,
            "transcript":    session.GetTranscript(),
        },
    })
    if err != nil {
        return map[string]any{
            "success": false,
            "message": "I wasn't able to create a ticket right now. Please try again later.",
        }, nil
    }

    return map[string]any{
        "success":   true,
        "ticket_id": ticket.ID,
        "message":   fmt.Sprintf("I've created ticket #%s. A support agent will follow up within %s.",
            ticket.ID, getResponseTime(ticket.Priority)),
    }, nil
}

func (h *SupportToolHandler) transferToHuman(ctx context.Context, args json.RawMessage) (any, error) {
    var params struct {
        Department    string `json:"department"`
        Reason        string `json:"reason"`
        Priority      string `json:"priority"`
        ContextSummary string `json:"context_summary"`
    }
    if err := json.Unmarshal(args, &params); err != nil {
        return nil, err
    }

    session := getSessionFromContext(ctx)

    // Create transfer context
    transferCtx := &TransferContext{
        CustomerPhone:  session.CallerNumber,
        CustomerName:   session.CustomerName,
        Summary:        params.ContextSummary,
        Reason:         params.Reason,
        Sentiment:      session.Sentiment,
        CallDuration:   time.Since(session.StartTime),
        PreviousTopics: session.Topics,
    }

    err := h.transferHandler.TransferCall(ctx, TransferParams{
        CallID:     session.CallID,
        Department: params.Department,
        Priority:   params.Priority,
        Context:    transferCtx,
        Message:    "I'm connecting you with a specialist who can better assist you. Please hold.",
    })

    if err != nil {
        return map[string]any{
            "success": false,
            "message": "I wasn't able to connect you right now. Would you like to schedule a callback instead?",
        }, nil
    }

    return map[string]any{
        "success": true,
        "message": "Transferring now.",
    }, nil
}
```

## Metrics and Analytics

### Key Performance Indicators

```go
type SupportMetrics struct {
    // Resolution Metrics
    FirstCallResolution    float64       // % resolved without transfer
    AverageHandleTime      time.Duration // Total call handling time
    AverageSpeedOfAnswer   time.Duration // Time to answer
    AbandonmentRate        float64       // % calls abandoned

    // Quality Metrics
    CustomerSatisfaction   float64       // CSAT score (1-5)
    SentimentScore         float64       // Average sentiment (-1 to 1)

    // Volume Metrics
    TotalCalls             int
    CallsHandledByAI       int
    CallsTransferred       int
    TicketsCreated         int

    // Efficiency Metrics
    ContainmentRate        float64       // % fully handled by AI
    TransferRate           float64       // % transferred to human
    CallbackRate           float64       // % resulted in callback
}

func (m *MetricsCollector) CalculateMetrics(period time.Duration) *SupportMetrics {
    calls := m.getCallsInPeriod(period)

    metrics := &SupportMetrics{
        TotalCalls:         len(calls),
        CallsHandledByAI:   countResolved(calls),
        CallsTransferred:   countTransferred(calls),
    }

    // First Call Resolution
    resolved := 0
    for _, call := range calls {
        if call.Outcome == "resolved" && !call.WasTransferred {
            resolved++
        }
    }
    metrics.FirstCallResolution = float64(resolved) / float64(len(calls)) * 100

    // Average Handle Time
    totalDuration := time.Duration(0)
    for _, call := range calls {
        totalDuration += call.Duration
    }
    metrics.AverageHandleTime = totalDuration / time.Duration(len(calls))

    // Containment Rate
    aiHandled := 0
    for _, call := range calls {
        if call.FullyHandledByAI {
            aiHandled++
        }
    }
    metrics.ContainmentRate = float64(aiHandled) / float64(len(calls)) * 100

    // CSAT (from post-call surveys)
    surveys := m.getSurveyResponses(calls)
    totalCSAT := 0.0
    for _, survey := range surveys {
        totalCSAT += survey.Score
    }
    if len(surveys) > 0 {
        metrics.CustomerSatisfaction = totalCSAT / float64(len(surveys))
    }

    return metrics
}
```

### Real-Time Dashboard Data

```go
type RealtimeDashboard struct {
    // Current Status
    ActiveCalls       int     `json:"active_calls"`
    AvgWaitTime       int     `json:"avg_wait_time_seconds"`
    AgentsAvailable   int     `json:"agents_available"`
    QueueDepth        int     `json:"queue_depth"`

    // Today's Performance
    CallsToday        int     `json:"calls_today"`
    FCRToday          float64 `json:"fcr_today"`
    AvgHandleTime     int     `json:"avg_handle_time_seconds"`
    CSATToday         float64 `json:"csat_today"`

    // Trends
    CallVolumeTrend   []int   `json:"call_volume_trend"`   // Last 24 hours by hour
    SentimentTrend    []float64 `json:"sentiment_trend"`   // Last 24 hours
}

func (d *Dashboard) StreamMetrics(ctx context.Context, ws *websocket.Conn) {
    ticker := time.NewTicker(5 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            metrics := d.calculateRealtimeMetrics()
            ws.WriteJSON(metrics)
        }
    }
}
```

### Metrics Tracking Integration

```go
func (h *SupportToolHandler) trackMetrics(ctx context.Context, session *Session, outcome string) {
    metrics.RecordCounter("support.calls.total", 1)
    metrics.RecordHistogram("support.calls.duration_ms", session.Duration.Milliseconds())
    metrics.RecordGauge("support.calls.sentiment", session.SentimentScore)

    switch outcome {
    case "resolved":
        metrics.RecordCounter("support.calls.resolved", 1)
        metrics.RecordCounter("support.fcr.success", 1)
    case "transferred":
        metrics.RecordCounter("support.calls.transferred", 1)
        metrics.RecordCounter(fmt.Sprintf("support.transfers.%s", session.TransferDepartment), 1)
    case "callback_scheduled":
        metrics.RecordCounter("support.callbacks.scheduled", 1)
    case "abandoned":
        metrics.RecordCounter("support.calls.abandoned", 1)
    }

    // Track by category
    metrics.RecordCounter(fmt.Sprintf("support.category.%s", session.IssueCategory), 1)

    // Track resolution time by category
    metrics.RecordHistogram(
        fmt.Sprintf("support.resolution_time.%s", session.IssueCategory),
        session.ResolutionTime.Milliseconds(),
    )
}
```

## Helpdesk Integrations

### Zendesk Integration

```go
package integrations

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
)

type ZendeskClient struct {
    subdomain string
    email     string
    apiToken  string
    client    *http.Client
}

func NewZendeskClient(subdomain, email, apiToken string) *ZendeskClient {
    return &ZendeskClient{
        subdomain: subdomain,
        email:     email,
        apiToken:  apiToken,
        client:    &http.Client{Timeout: 10 * time.Second},
    }
}

type ZendeskTicket struct {
    ID          int64             `json:"id,omitempty"`
    Subject     string            `json:"subject"`
    Description string            `json:"description"`
    Priority    string            `json:"priority"`
    Status      string            `json:"status"`
    Type        string            `json:"type"`
    Tags        []string          `json:"tags"`
    CustomFields []CustomField    `json:"custom_fields,omitempty"`
    Requester   *Requester        `json:"requester,omitempty"`
}

func (c *ZendeskClient) CreateTicket(ctx context.Context, ticket *ZendeskTicket) (*ZendeskTicket, error) {
    url := fmt.Sprintf("https://%s.zendesk.com/api/v2/tickets.json", c.subdomain)

    payload := map[string]any{
        "ticket": ticket,
    }

    req, err := c.newRequest(ctx, "POST", url, payload)
    if err != nil {
        return nil, err
    }

    var response struct {
        Ticket ZendeskTicket `json:"ticket"`
    }

    if err := c.do(req, &response); err != nil {
        return nil, err
    }

    return &response.Ticket, nil
}

func (c *ZendeskClient) SearchTickets(ctx context.Context, query string) ([]ZendeskTicket, error) {
    url := fmt.Sprintf("https://%s.zendesk.com/api/v2/search.json?query=%s",
        c.subdomain, url.QueryEscape(query))

    req, err := c.newRequest(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }

    var response struct {
        Results []ZendeskTicket `json:"results"`
    }

    if err := c.do(req, &response); err != nil {
        return nil, err
    }

    return response.Results, nil
}

func (c *ZendeskClient) GetUser(ctx context.Context, phone string) (*ZendeskUser, error) {
    query := fmt.Sprintf("type:user phone:%s", phone)
    url := fmt.Sprintf("https://%s.zendesk.com/api/v2/search.json?query=%s",
        c.subdomain, url.QueryEscape(query))

    req, err := c.newRequest(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }

    var response struct {
        Results []ZendeskUser `json:"results"`
    }

    if err := c.do(req, &response); err != nil {
        return nil, err
    }

    if len(response.Results) == 0 {
        return nil, ErrUserNotFound
    }

    return &response.Results[0], nil
}

// Tool handler integration
func (h *SupportToolHandler) createZendeskTicket(ctx context.Context, params CreateTicketParams) (*Ticket, error) {
    session := getSessionFromContext(ctx)

    // Find or create user
    user, err := h.zendesk.GetUser(ctx, session.CallerNumber)
    if err == ErrUserNotFound {
        user, err = h.zendesk.CreateUser(ctx, &ZendeskUser{
            Phone: session.CallerNumber,
            Name:  session.CustomerName,
        })
    }
    if err != nil {
        return nil, err
    }

    // Create ticket with voice context
    zdTicket := &ZendeskTicket{
        Subject:     params.Subject,
        Description: params.Description,
        Priority:    params.Priority,
        Status:      "new",
        Tags:        []string{"voice_agent", "ai_created"},
        Requester:   &Requester{ID: user.ID},
        CustomFields: []CustomField{
            {ID: 360012345678, Value: session.CallID},      // Call ID field
            {ID: 360012345679, Value: session.Sentiment},   // Sentiment field
            {ID: 360012345680, Value: session.GetTranscript()}, // Transcript
        },
    }

    return h.zendesk.CreateTicket(ctx, zdTicket)
}
```

### Freshdesk Integration

```go
package integrations

type FreshdeskClient struct {
    domain   string
    apiKey   string
    client   *http.Client
}

func NewFreshdeskClient(domain, apiKey string) *FreshdeskClient {
    return &FreshdeskClient{
        domain: domain,
        apiKey: apiKey,
        client: &http.Client{Timeout: 10 * time.Second},
    }
}

type FreshdeskTicket struct {
    ID            int64    `json:"id,omitempty"`
    Subject       string   `json:"subject"`
    Description   string   `json:"description"`
    DescriptionText string `json:"description_text,omitempty"`
    Status        int      `json:"status"`        // 2=Open, 3=Pending, 4=Resolved, 5=Closed
    Priority      int      `json:"priority"`      // 1=Low, 2=Medium, 3=High, 4=Urgent
    Source        int      `json:"source"`        // 1=Email, 2=Portal, 3=Phone, 7=Chat, 9=Feedback
    Type          string   `json:"type,omitempty"`
    Tags          []string `json:"tags,omitempty"`
    Phone         string   `json:"phone,omitempty"`
    Email         string   `json:"email,omitempty"`
    RequesterID   int64    `json:"requester_id,omitempty"`
    GroupID       int64    `json:"group_id,omitempty"`
    CustomFields  map[string]any `json:"custom_fields,omitempty"`
}

func (c *FreshdeskClient) CreateTicket(ctx context.Context, ticket *FreshdeskTicket) (*FreshdeskTicket, error) {
    url := fmt.Sprintf("https://%s.freshdesk.com/api/v2/tickets", c.domain)

    req, err := c.newRequest(ctx, "POST", url, ticket)
    if err != nil {
        return nil, err
    }

    var response FreshdeskTicket
    if err := c.do(req, &response); err != nil {
        return nil, err
    }

    return &response, nil
}

func (c *FreshdeskClient) SearchContacts(ctx context.Context, phone string) ([]FreshdeskContact, error) {
    url := fmt.Sprintf("https://%s.freshdesk.com/api/v2/contacts?phone=%s",
        c.domain, url.QueryEscape(phone))

    req, err := c.newRequest(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }

    var contacts []FreshdeskContact
    if err := c.do(req, &contacts); err != nil {
        return nil, err
    }

    return contacts, nil
}

// Voice agent integration
func (h *SupportToolHandler) createFreshdeskTicket(ctx context.Context, params CreateTicketParams) (*Ticket, error) {
    session := getSessionFromContext(ctx)

    // Map priority to Freshdesk values
    priorityMap := map[string]int{
        "low":    1,
        "medium": 2,
        "high":   3,
        "urgent": 4,
    }

    fdTicket := &FreshdeskTicket{
        Subject:     params.Subject,
        Description: formatHTMLDescription(params, session),
        Status:      2, // Open
        Priority:    priorityMap[params.Priority],
        Source:      3, // Phone
        Phone:       session.CallerNumber,
        Tags:        []string{"voice_agent", "ai_created"},
        CustomFields: map[string]any{
            "cf_call_id":       session.CallID,
            "cf_sentiment":     session.Sentiment,
            "cf_call_duration": session.Duration.Seconds(),
        },
    }

    return c.CreateTicket(ctx, fdTicket)
}

func formatHTMLDescription(params CreateTicketParams, session *Session) string {
    return fmt.Sprintf(`
<h3>Issue Summary</h3>
<p>%s</p>

<h3>Call Details</h3>
<ul>
    <li><strong>Call ID:</strong> %s</li>
    <li><strong>Duration:</strong> %s</li>
    <li><strong>Sentiment:</strong> %s</li>
</ul>

<h3>Conversation Transcript</h3>
<pre>%s</pre>
`,
        params.Description,
        session.CallID,
        session.Duration.Round(time.Second),
        session.Sentiment,
        session.GetTranscript(),
    )
}
```

### Salesforce Service Cloud Integration

```go
package integrations

type SalesforceClient struct {
    instanceURL string
    accessToken string
    client      *http.Client
}

type SalesforceCase struct {
    ID          string `json:"Id,omitempty"`
    Subject     string `json:"Subject"`
    Description string `json:"Description"`
    Status      string `json:"Status"`
    Priority    string `json:"Priority"`
    Origin      string `json:"Origin"`
    ContactID   string `json:"ContactId,omitempty"`
    AccountID   string `json:"AccountId,omitempty"`
    Type        string `json:"Type,omitempty"`

    // Custom fields
    VoiceCallID__c       string `json:"Voice_Call_ID__c,omitempty"`
    CallTranscript__c    string `json:"Call_Transcript__c,omitempty"`
    CustomerSentiment__c string `json:"Customer_Sentiment__c,omitempty"`
}

func (c *SalesforceClient) CreateCase(ctx context.Context, sfCase *SalesforceCase) (*SalesforceCase, error) {
    url := fmt.Sprintf("%s/services/data/v58.0/sobjects/Case", c.instanceURL)

    req, err := c.newRequest(ctx, "POST", url, sfCase)
    if err != nil {
        return nil, err
    }

    var response struct {
        ID      string `json:"id"`
        Success bool   `json:"success"`
    }

    if err := c.do(req, &response); err != nil {
        return nil, err
    }

    sfCase.ID = response.ID
    return sfCase, nil
}

func (c *SalesforceClient) FindContactByPhone(ctx context.Context, phone string) (*SalesforceContact, error) {
    query := fmt.Sprintf("SELECT Id, Name, AccountId, Phone, Email FROM Contact WHERE Phone = '%s'", phone)
    url := fmt.Sprintf("%s/services/data/v58.0/query?q=%s", c.instanceURL, url.QueryEscape(query))

    req, err := c.newRequest(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }

    var response struct {
        Records []SalesforceContact `json:"records"`
    }

    if err := c.do(req, &response); err != nil {
        return nil, err
    }

    if len(response.Records) == 0 {
        return nil, ErrContactNotFound
    }

    return &response.Records[0], nil
}
```

## Best Practices

### 1. Design for Resolution

Optimize for first-call resolution:

```go
// Good: Proactive resolution
func (h *Handler) handleOrderInquiry(ctx context.Context, session *Session, orderID string) error {
    order, err := h.orderService.GetOrder(ctx, orderID)
    if err != nil {
        return h.handleOrderNotFound(ctx, session)
    }

    // Proactively address common follow-up questions
    response := fmt.Sprintf("Your order %s is %s.", orderID, order.Status)

    switch order.Status {
    case "shipped":
        response += fmt.Sprintf(" It's being shipped via %s with tracking number %s. "+
            "Expected delivery is %s. Would you like me to text you the tracking link?",
            order.Carrier,
            order.TrackingNumber,
            order.EstimatedDelivery.Format("January 2"))
    case "delayed":
        response += fmt.Sprintf(" I see there's been a delay. The new expected date is %s. "+
            "I apologize for the inconvenience. Would you like me to apply a 10%% discount to your next order?",
            order.NewEstimatedDelivery.Format("January 2"))
    }

    return session.TTS.Speak(response)
}
```

### 2. Empathy-First Responses

Build empathy into agent responses:

```go
var empathyResponses = map[string][]string{
    "frustrated": {
        "I completely understand your frustration, and I'm sorry you're dealing with this.",
        "I hear you, and I want to make this right.",
        "That's definitely not the experience we want for you. Let me fix this.",
    },
    "confused": {
        "No worries, let me explain that more clearly.",
        "That can be confusing. Here's how it works.",
        "Great question. Let me walk you through it.",
    },
    "worried": {
        "I understand your concern. Let me check on that right away.",
        "Don't worry, I'll help you sort this out.",
        "I can see why that would be worrying. Let me take a look.",
    },
}

func selectEmpathyResponse(sentiment string) string {
    responses := empathyResponses[sentiment]
    return responses[rand.Intn(len(responses))]
}
```

### 3. Intelligent Escalation

Know when to escalate:

```go
type EscalationAnalyzer struct {
    sentimentThreshold   float64
    failedAttemptLimit   int
    highValueThreshold   float64
}

func (a *EscalationAnalyzer) Analyze(session *Session) EscalationDecision {
    // Score-based escalation
    score := 0.0

    // Customer frustration
    if session.SentimentScore < a.sentimentThreshold {
        score += 3.0
    }

    // Failed resolution attempts
    if session.FailedAttempts >= a.failedAttemptLimit {
        score += 2.0
    }

    // High-value customer
    if session.CustomerLTV > a.highValueThreshold {
        score += 1.5
    }

    // Explicit request
    if session.ExplicitHumanRequest {
        return EscalationDecision{
            ShouldEscalate: true,
            Reason:         "customer_requested",
            Priority:       "normal",
        }
    }

    // Policy-bound issues
    if containsPolicyIssue(session.Topics) {
        return EscalationDecision{
            ShouldEscalate: true,
            Reason:         "policy_exception",
            Priority:       "high",
        }
    }

    return EscalationDecision{
        ShouldEscalate: score >= 4.0,
        Reason:         "composite_score",
        Priority:       getPriority(score),
    }
}
```

### 4. Context Preservation

Maintain context across interactions:

```go
type ConversationContext struct {
    CustomerID       string
    CustomerName     string
    CustomerTier     string
    PreviousTickets  []string
    OpenOrders       []string
    LastInteraction  time.Time
    PreferredChannel string
    Notes            []string
}

func (h *Handler) loadCustomerContext(ctx context.Context, phone string) (*ConversationContext, error) {
    customer, err := h.crmClient.GetCustomerByPhone(ctx, phone)
    if err != nil {
        return &ConversationContext{}, nil // Continue without context
    }

    tickets, _ := h.helpdeskClient.GetRecentTickets(ctx, customer.ID, 5)
    orders, _ := h.orderService.GetOpenOrders(ctx, customer.ID)

    return &ConversationContext{
        CustomerID:       customer.ID,
        CustomerName:     customer.Name,
        CustomerTier:     customer.Tier,
        PreviousTickets:  extractTicketIDs(tickets),
        OpenOrders:       extractOrderIDs(orders),
        LastInteraction:  customer.LastContactDate,
        PreferredChannel: customer.PreferredChannel,
        Notes:            customer.SupportNotes,
    }, nil
}

// Use context in conversation
func (h *Handler) greetWithContext(ctx context.Context, session *Session) string {
    context := session.Context

    if context.CustomerName != "" {
        if time.Since(context.LastInteraction) < 24*time.Hour {
            return fmt.Sprintf("Welcome back, %s! I see you called earlier today. "+
                "Are you following up on the same issue?", context.CustomerName)
        }
        return fmt.Sprintf("Hello %s, thank you for calling. How can I help you today?",
            context.CustomerName)
    }

    return "Thank you for calling support. How can I help you today?"
}
```

### 5. Continuous Improvement

Track and improve based on data:

```go
type ImprovementTracker struct {
    transferReasons  map[string]int
    unresolvedTopics map[string]int
    lowCSATReasons   []string
}

func (t *ImprovementTracker) Analyze() []Recommendation {
    recommendations := []Recommendation{}

    // High transfer rate for specific reason
    for reason, count := range t.transferReasons {
        if count > t.getThreshold(reason) {
            recommendations = append(recommendations, Recommendation{
                Type:     "training_gap",
                Topic:    reason,
                Priority: "high",
                Action:   fmt.Sprintf("Add knowledge base articles for: %s", reason),
            })
        }
    }

    // Unresolved topics
    for topic, count := range t.unresolvedTopics {
        if count > 10 {
            recommendations = append(recommendations, Recommendation{
                Type:     "capability_gap",
                Topic:    topic,
                Priority: "medium",
                Action:   fmt.Sprintf("Add tool or integration for: %s", topic),
            })
        }
    }

    return recommendations
}
```

## Testing Support Agents

### Unit Tests

```go
func TestOrderStatusFlow(t *testing.T) {
    handler := setupTestHandler()
    session := createMockSession()

    // Test order lookup
    result, err := handler.getOrderStatus(context.Background(), json.RawMessage(`{
        "order_id": "ORD-12345"
    }`))

    assert.NoError(t, err)
    resultMap := result.(map[string]any)
    assert.True(t, resultMap["found"].(bool))
    assert.Contains(t, resultMap["message"].(string), "shipped")
}

func TestEscalationLogic(t *testing.T) {
    analyzer := &EscalationAnalyzer{
        sentimentThreshold: -0.3,
        failedAttemptLimit: 2,
    }

    tests := []struct {
        name     string
        session  *Session
        expected bool
    }{
        {
            name: "explicit request",
            session: &Session{
                ExplicitHumanRequest: true,
            },
            expected: true,
        },
        {
            name: "negative sentiment",
            session: &Session{
                SentimentScore: -0.5,
            },
            expected: true,
        },
        {
            name: "happy customer",
            session: &Session{
                SentimentScore: 0.8,
            },
            expected: false,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            decision := analyzer.Analyze(tt.session)
            assert.Equal(t, tt.expected, decision.ShouldEscalate)
        })
    }
}
```

### Integration Tests

```go
func TestZendeskIntegration(t *testing.T) {
    if testing.Short() {
        t.Skip("Skipping integration test")
    }

    client := NewZendeskClient(
        os.Getenv("ZENDESK_SUBDOMAIN"),
        os.Getenv("ZENDESK_EMAIL"),
        os.Getenv("ZENDESK_API_TOKEN"),
    )

    // Create test ticket
    ticket, err := client.CreateTicket(context.Background(), &ZendeskTicket{
        Subject:     "Test: Voice Agent Integration",
        Description: "Automated test ticket from voice agent integration tests",
        Priority:    "low",
        Tags:        []string{"test", "automated"},
    })

    assert.NoError(t, err)
    assert.NotEmpty(t, ticket.ID)

    // Cleanup
    defer client.DeleteTicket(context.Background(), ticket.ID)
}
```

## Next Steps

- [Function Calling](https://edesy.in/docs/voice-agent/features/function-calling) - Deep dive into tool integration
- [Call Transfer](https://edesy.in/docs/voice-agent/features/call-transfer) - Advanced escalation patterns
- [Webhooks](https://edesy.in/docs/voice-agent/features/webhooks) - Post-call integrations
- [Latency Optimization](https://edesy.in/docs/voice-agent/advanced/latency) - Sub-500ms responses
- [Customer Support Example](https://edesy.in/docs/voice-agent/examples/customer-support) - Basic implementation
