02 Case Studies 7 min read 1,550 words

Case Study: Customer Support Conversational Agent

This case study walks through designing a production customer support agent for a B2B SaaS company.

case-studyagentsreliabilityapplied
01statement

Problem Statement

Company: B2B SaaS platform with 50K enterprise customers

Current state:

  • 500K support tickets per month
  • Average response time: 4 hours
  • Customer satisfaction (CSAT): 72%
  • Support team: 100 agents

Goal:

  • Reduce response time to < 5 minutes for common queries
  • Improve CSAT to > 85%
  • Handle 60% of tickets without human intervention
  • Maintain quality for escalated tickets
02analysis

Requirements Analysis

2.1

Functional Requirements

RequirementDescriptionPriority
Query understandingClassify intent, extract entitiesP0
Knowledge retrievalSearch product docs, FAQs, past ticketsP0
Account contextAccess user's subscription, historyP0
Response generationNatural, accurate, helpful responsesP0
Conversation memoryMulti-turn contextP0
Action executionCreate tickets, trigger workflowsP1
Human escalationSeamless handoff when neededP0
Billing inquiriesHandle sensitive financial dataP1
2.2

Non-Functional Requirements

RequirementTargetRationale
Latency (TTFT)< 1sUser expectation for chat
Latency (full)< 5sMaintain engagement
Availability99.9%Business-critical
Accuracy> 95%Customer trust
Escalation rate< 40%Cost efficiency
CSAT> 85%Business goal
2.3

Security Requirements

  • No PII in logs
  • Tenant isolation (customers only see their data)
  • Audit trail for all actions
  • SOC 2 compliance
03design

Architecture Design

3.1

High-Level Architecture

3.2

Conversation Flow

04deep dives

Component Deep Dives

4.1

Intent Classification (Dec 2025)

Pythonpython · 9 lines
123456789
class IntentClassifier:
    async def classify(self, message: str, history: list[dict]) -> dict:
        # Using GPT-5.2-mini for <100ms classification latency
        result = await client.chat.completions.create(
            model="gpt-5.2-mini",
            messages=[{"role": "user", "content": message}],
            response_format={"type": "json_object"}
        )
        return json.loads(result.choices[0].message.content)
4.2

Knowledge Base (Gemini 3 Flash RAG)

Pythonpython · 6 lines
123456
class SupportKnowledgeBase:
    async def retrieve(self, query: str, context_window: int = 1_000_000) -> list[dict]:
        # Using Gemini 3 Flash for massive context retrieval
        # No more 'reranking' needed for many standard support tasks
        results = await self.sources.search(query, limit=50) 
        return results
4.3

Response Generation (Claude Sonnet 4.5)

Pythonpython · 12 lines
123456789101112
class ResponseGenerator:
    async def generate(self, query: str, context: list[dict]) -> dict:
        # Claude Sonnet 4.5 for 'Hybrid Reasoning'
        # Toggle 'Thinking' mode for complex billing issues
        is_complex = self.detect_complexity(query)
        
        response = await self.anthropic.messages.create(
            model="claude-3-7-sonnet-20250219",
            thinking={"enabled": is_complex, "budget_tokens": 2048},
            messages=[{"role": "user", "content": f"Context: {context}\nQuery: {query}"}]
        )
        return {"response": response.content[0].text}
05patterns

Reliability Patterns

5.1

Confidence-Based Escalation

Pythonpython · 40 lines
12345678910111213141516171819202122232425262728293031323334353637383940
class EscalationHandler:
    def __init__(self, confidence_threshold: float = 0.7):
        self.threshold = confidence_threshold
    
    async def check_escalation(
        self,
        response: dict,
        intent: str,
        user_request: str
    ) -> dict:
        should_escalate = False
        reason = None
        
        # Low confidence
        if response["confidence"] < self.threshold:
            should_escalate = True
            reason = "low_confidence"
        
        # Explicit escalation request
        if intent == "escalation_request":
            should_escalate = True
            reason = "user_requested"
        
        # Sensitive topics
        if await self.is_sensitive(user_request):
            should_escalate = True
            reason = "sensitive_topic"
        
        if should_escalate:
            return await self.create_escalation(response, reason)
        
        return {"escalate": False, "response": response}
    
    async def is_sensitive(self, message: str) -> bool:
        sensitive_keywords = [
            "legal", "lawsuit", "lawyer",
            "refund", "cancel subscription",
            "competitor", "data breach"
        ]
        return any(kw in message.lower() for kw in sensitive_keywords)
5.2

Multi-Turn Memory

Pythonpython · 32 lines
1234567891011121314151617181920212223242526272829303132
class ConversationMemory:
    def __init__(self, max_turns: int = 10):
        self.max_turns = max_turns
        self.redis = Redis()
    
    async def get_history(self, session_id: str) -> list[dict]:
        key = f"conversation:{session_id}"
        history = await self.redis.get(key)
        if history:
            return json.loads(history)
        return []
    
    async def add_turn(
        self,
        session_id: str,
        user_message: str,
        assistant_message: str
    ):
        history = await self.get_history(session_id)
        
        history.append({"role": "user", "content": user_message})
        history.append({"role": "assistant", "content": assistant_message})
        
        # Trim to max turns
        if len(history) > self.max_turns * 2:
            history = history[-(self.max_turns * 2):]
        
        await self.redis.setex(
            f"conversation:{session_id}",
            3600,  # 1 hour TTL
            json.dumps(history)
        )
06monitoring

Evaluation and Monitoring

6.1

Quality Metrics

Pythonpython · 24 lines
123456789101112131415161718192021222324
class QualityMonitor:
    def __init__(self, sample_rate: float = 0.05):
        self.sample_rate = sample_rate
        self.judge = LLMJudge()
    
    async def evaluate(self, conversation: dict):
        if random.random() > self.sample_rate:
            return
        
        scores = await self.judge.evaluate(
            query=conversation["user_message"],
            response=conversation["assistant_message"],
            context=conversation["context"],
            criteria={
                "relevance": "Does the response address the user's question?",
                "accuracy": "Is the information correct based on the context?",
                "helpfulness": "Would this response help the user?",
                "tone": "Is the tone professional and empathetic?"
            }
        )
        
        # Record metrics
        for criterion, score in scores.items():
            metrics.record(f"quality_{criterion}", score)
6.2

Dashboard Metrics

MetricTargetActual
Latency (TTFT)< 1s0.8s
Latency (full)< 5s3.2s
Accuracy> 95%94.3%
Escalation rate< 40%38%
CSAT> 85%87%
Resolution rate> 60%62%
07analysis

Cost Analysis

7.1

Per-Conversation Cost Breakdown (Dec 2025)

ComponentCostNotes
Intent classification$0.0001GPT-5.2-mini ($0.10/1M)
RAG retrieval$0.0001Gemini 3 Flash ($0.05/1M)
Thinking mode$0.0050Claude Sonnet 4.5 Thinking (avg 250 tokens)
Response generation$0.0030Claude Sonnet 4.5 ($3/1M in)
Quality sampling$0.00015% sample rate on GPT-5.2
Total~$0.0083Per conversation (62% reduction vs 2024)
7.2

Monthly Cost Projection

ItemCalculationCost
Conversations500K × $0.022$11,000
InfrastructureFixed$2,000
Human escalations190K × $5 (human cost)$950,000
Total$963,000
Savings vs all-human500K × $5 - $963K$1.5M/year
08learned

Lessons Learned

8.1

What Worked

  1. Intent-based routing reduced latency by focusing retrieval on relevant sources
  2. Confidence-based escalation maintained quality while reducing human load
  3. Account context made responses more personalized and accurate
  4. Lower temperature (0.3) improved consistency for support responses
8.2

What Did Not Work Initially

  1. Single model for everything - routing to different models for different tasks improved quality
  2. Too high escalation threshold - started at 0.9 confidence, causing too many escalations
  3. Full conversation history - exceeded context limits, switched to summarization
8.3

Recommendations

  1. Start with high escalation rate and lower gradually as confidence improves
  2. Monitor CSAT by escalation reason to identify weak areas
  3. Retrain embeddings on support-specific vocabulary
  4. Build feedback loop: agents tag escalated conversations for training data
09walkthrough

Interview Walkthrough

Interviewer: "Design an AI customer support system for a SaaS company."

Strong response pattern:

  1. Clarify requirements (2 min)

    • "What's the ticket volume? What channels? What's the current CSAT?"
  2. State constraints explicitly

    • "Key constraints: accuracy over speed, seamless escalation, tenant isolation"
  3. High-level architecture (3 min)

    • Draw the flow: intent → routing → RAG → generation → safety → response/escalation
  4. Deep dive on critical component (5 min)

    • "Let me detail the confidence-based escalation..."
  5. Address reliability (3 min)

    • "For reliability, I would use self-consistency for billing queries, multi-provider fallback"
  6. Metrics and monitoring (2 min)

    • "Key metrics: CSAT, resolution rate, escalation rate, accuracy sampling"
  7. Cost consideration (1 min)

    • "At 500K conversations/month, cost per conversation matters. Model routing helps."
10references

References


Next: Code Assistant Case Study

summary · added by this rebuild

Key takeaways

01

Escalation is a gate, not a score

Three independent triggers force a handoff: confidence below 0.7, an explicit user request, or sensitive keywords such as lawsuit, refund, cancel subscription or data breach.

02

Start escalating too much, then relax

The team began at a 0.9 confidence threshold, found it escalated far too often, and now advises starting high and lowering the bar only as measured confidence earns it.

03

One model per task won

A cheap intent classifier, a long-context retriever, and a generator with thinking toggled on only for complex billing questions replaced a single-model design that underperformed.

04

Human handling still dominates the bill

The monthly projection puts conversations at $11,000 and infrastructure at $2,000 against roughly $950,000 for 190K escalated tickets at $5 of human cost each.