File size: 6,172 Bytes
8cadb90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
"""
agent.py  β€”  Groq API version
Uses Groq's free API with llama-3.3-70b-versatile model.
Groq is extremely fast (200+ tokens/sec) and free to use.

Setup:
  1. Go to console.groq.com β†’ sign up β†’ API Keys β†’ Create Key
  2. Set: export GROQ_API_KEY=gsk_your_key_here  (Mac/Linux)
          set GROQ_API_KEY=gsk_your_key_here     (Windows)
  3. pip install groq
"""

import os
import json
import re
from groq import Groq

client = Groq(api_key=os.environ.get("GROQ_API_KEY", ""))
MODEL  = "llama-3.3-70b-versatile"   # Free, fast, high quality


def _call(system: str, user: str, max_tokens: int = 800) -> str:
    """Call Groq API and return text response."""
    response = client.chat.completions.create(
        model=MODEL,
        max_tokens=max_tokens,
        temperature=0.2,
        messages=[
            {"role": "system", "content": system},
            {"role": "user",   "content": user},
        ],
    )
    return response.choices[0].message.content.strip()


# ── CLASSIFICATION ────────────────────────────────────────────────────────────

CLASSIFY_SYSTEM = """You are a support ticket classifier for a multi-domain helpdesk.
Classify the ticket into exactly these fields and respond ONLY with valid JSON.

Fields:
- domain: one of ["hackerrank", "claude", "visa", "unknown"]
  * hackerrank  β†’ coding assessments, candidates, recruiters, HackerRank platform, proctoring, plagiarism
  * claude      β†’ Claude AI assistant, claude.ai, Anthropic API, AI conversations, Claude subscription
  * visa        β†’ Visa card, ATM, transactions, payments, card dispute, merchant
  * unknown     β†’ unclear or does not match any domain
- request_type: one of ["faq", "billing", "bug_report", "account_access",
                         "assessment", "feature_request", "fraud", "complaint",
                         "how_to", "permissions", "other"]
- product_area: short phrase (e.g. "password reset", "card dispute", "API usage", "candidate results")
- confidence: "high" | "medium" | "low"

Respond ONLY with a JSON object. No markdown fences. No explanation. No extra text."""


def classify_ticket(ticket_text: str) -> dict:
    """Classify a support ticket. Returns dict."""
    raw = _call(CLASSIFY_SYSTEM, f"Classify this ticket:\n\n{ticket_text}", max_tokens=200)
    try:
        clean = raw.strip().strip("```json").strip("```").strip()
        return json.loads(clean)
    except json.JSONDecodeError:
        # Try to extract JSON object with regex
        match = re.search(r'\{.*?\}', clean, re.DOTALL)
        if match:
            try:
                return json.loads(match.group(0))
            except Exception:
                pass
        return _rule_based_classify(ticket_text)


def _rule_based_classify(text: str) -> dict:
    """Fast keyword fallback if LLM JSON parse fails."""
    t = text.lower()
    if any(w in t for w in ["hackerrank", "assessment", "candidate", "recruiter", "plagiar", "proctoring"]):
        domain = "hackerrank"
    elif any(w in t for w in ["claude", "anthropic", "claude.ai", "ai assistant"]):
        domain = "claude"
    elif any(w in t for w in ["visa", "card", "atm", "transaction", "merchant", "chargeback"]):
        domain = "visa"
    else:
        domain = "unknown"

    if any(w in t for w in ["fraud", "unauthorized", "stolen", "scam"]):
        rtype = "fraud"
    elif any(w in t for w in ["charge", "refund", "billing", "invoice"]):
        rtype = "billing"
    elif any(w in t for w in ["password", "login", "locked", "hacked", "access"]):
        rtype = "account_access"
    elif any(w in t for w in ["bug", "error", "broken", "crash", "not working"]):
        rtype = "bug_report"
    else:
        rtype = "faq"

    return {"domain": domain, "request_type": rtype,
            "product_area": f"{domain} support", "confidence": "medium"}


# ── RESPONSE GENERATION ───────────────────────────────────────────────────────

RESPONSE_SYSTEM = """You are a professional, friendly support agent.
STRICT RULE: Answer ONLY using the provided support documentation.
Do NOT invent any policies, prices, steps, or features not in the docs.
If docs don't cover something, say so honestly and suggest contacting support directly.
Keep responses concise: 3-5 sentences for simple FAQs, up to 8 for complex issues."""


def generate_response(ticket_text: str, docs: list[dict]) -> str:
    """Generate a grounded support response using retrieved docs."""
    if not docs:
        return (
            "Thank you for reaching out. I wasn't able to find relevant documentation "
            "for your specific query in our support corpus. Please contact our support "
            "team directly for personalized assistance."
        )

    context = "\n\n---\n\n".join(
        f"[Source {i}: {d['title']}]\n{d.get('snippet', d['text'][:800])}"
        for i, d in enumerate(docs[:4], 1)
    )

    user_prompt = f"""Support Documentation:
{context}

---
Customer Issue:
{ticket_text}

Write a helpful, accurate response using only the documentation above."""

    return _call(RESPONSE_SYSTEM, user_prompt, max_tokens=500)


# ── ESCALATION MESSAGE ────────────────────────────────────────────────────────

ESCALATE_SYSTEM = """You are a support agent writing an escalation acknowledgment.
Write 2-3 sentences: acknowledge the customer's concern, say a specialist will review it,
and set a reasonable expectation. Be warm and professional.
Do NOT attempt to resolve the issue. Do NOT reveal internal routing decisions."""


def generate_escalation_message(ticket_text: str, escalation_reason: str) -> str:
    """Generate a customer-facing escalation message."""
    return _call(
        ESCALATE_SYSTEM,
        f"Customer issue: {ticket_text}\n\n(Internal note - do not reveal: {escalation_reason})",
        max_tokens=180,
    )