Spaces:
Sleeping
Sleeping
File size: 8,770 Bytes
026df2c 9a013d0 026df2c 9a013d0 026df2c 9a013d0 026df2c 9a013d0 026df2c 9a013d0 026df2c 9a013d0 026df2c 9a013d0 026df2c 9a013d0 026df2c 9a013d0 026df2c 9a013d0 026df2c 9a013d0 026df2c | 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 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | import random
from typing import Dict, List, Set
GENERIC_DETAIL_KEYWORDS = {
"utr",
"amount",
"time",
"merchant",
"bank",
"transaction",
"issue",
"status",
"error",
}
UNSAFE_MARKERS = {"otp", "pin", "cvv", "password"}
CATEGORY_PROMPTS = {
"payment_failure": {
"vague": [
"The money left my account but the payment does not look right.",
"Something went wrong with the payment and I need help.",
],
"follow_up": [
"Can you tell me what I should check next?",
"What should I do from the app now?",
],
"success": [
"I checked again and it looks fixed now.",
"That helped and the payment status is clear now.",
],
"stuck": [
"I still do not see a proper final status.",
"It is still not fully clear on my side.",
],
},
"refund_delay": {
"vague": [
"I am still waiting for the money to come back.",
"The refund is taking too long and I am worried.",
],
"follow_up": [
"Can you tell me how long this usually takes?",
"What should I check before I wait more?",
],
"success": [
"I checked again and the refund is now visible.",
"Looks like the refund has finally come through.",
],
"stuck": [
"I still do not see the refund in my bank account.",
"The refund is still missing for me.",
],
},
"fraud_complaint": {
"vague": [
"This looks suspicious and I need urgent help.",
"I think something unauthorized happened on my account.",
],
"follow_up": [
"Please tell me what I should secure right now.",
"What should I do immediately from my side?",
],
"success": [
"I have secured the account and followed the steps.",
"I did the security steps and I understand the next action now.",
],
"stuck": [
"I am still worried because I do not know if the account is safe yet.",
"I followed some steps but I still need help with the fraud case.",
],
},
"kyc_account_restriction": {
"vague": [
"The app keeps saying my account is restricted.",
"I am blocked and not sure what KYC step is missing.",
],
"follow_up": [
"Can you explain what verification I should complete?",
"What is the next KYC step I should take?",
],
"success": [
"That makes sense and I know what verification to do now.",
"I understand the KYC steps now, thanks.",
],
"stuck": [
"I still do not understand why it is restricted.",
"It still looks blocked from my side.",
],
},
"upi_pin_or_bank_linking": {
"vague": [
"The setup keeps failing and I do not know why.",
"I cannot complete the bank or PIN setup.",
],
"follow_up": [
"Can you tell me what I should check on the phone first?",
"What should I try before I do it again?",
],
"success": [
"That helped and I was able to move forward.",
"I tried those checks and it is working better now.",
],
"stuck": [
"It is still failing after I checked that.",
"I still cannot complete the setup.",
],
},
}
class UserSimulator:
def __init__(self, ticket: Dict):
self.ticket_id = ticket.get("id", "")
self.initial_text = ticket.get("initial_text", "")
self.clarified_text = ticket.get("clarified_text", "")
self.trigger_phrases: List[str] = ticket.get("trigger_phrases", [])
self.gold_faq_id = ticket.get("gold_faq_id", "")
self.issue_category = ticket.get("issue_category", "")
self.state = "initial"
self.issue_resolved = False
self.clarification_given = False
self.turns = 0
self.repeat_questions = 0
self.guidance_attempts = 0
self.requested_details: Set[str] = set()
def _category_messages(self, kind: str) -> List[str]:
defaults = CATEGORY_PROMPTS.get(self.issue_category) or CATEGORY_PROMPTS[
"payment_failure"
]
return defaults[kind]
def _contains_unsafe_request(self, agent_message: str) -> bool:
lowered = agent_message.lower()
return any(marker in lowered for marker in UNSAFE_MARKERS)
def _extract_requested_details(self, agent_message: str) -> Set[str]:
lowered = agent_message.lower()
details = {phrase for phrase in self.trigger_phrases if phrase.lower() in lowered}
details.update(keyword for keyword in GENERIC_DETAIL_KEYWORDS if keyword in lowered)
return details
def _asked_for_useful_details(self, agent_message: str) -> bool:
details = self._extract_requested_details(agent_message)
new_details = details - self.requested_details
if details and not new_details:
self.repeat_questions += 1
self.requested_details.update(details)
return bool(details)
def _looks_like_guidance(self, agent_message: str) -> bool:
lowered = agent_message.lower()
generic_guidance = {
"check",
"retry",
"wait",
"follow",
"steps",
"secure",
"verify",
"complete",
"update",
"confirm",
"review",
"reversal",
"refund",
}
category_hints = {
"payment_failure": {"status", "merchant", "reversal"},
"refund_delay": {"refund", "merchant", "credited", "bank"},
"fraud_complaint": {"secure", "fraud", "recent activity", "report"},
"kyc_account_restriction": {"kyc", "verification", "documents", "review"},
"upi_pin_or_bank_linking": {"sim", "bank", "device", "permission"},
}
hints = category_hints.get(self.issue_category, set())
return any(token in lowered for token in generic_guidance | hints)
def respond(self, agent_message: str) -> str:
self.turns += 1
agent_message_lower = agent_message.lower()
if self._contains_unsafe_request(agent_message):
self.state = "concerned"
return "I am not comfortable sharing that. Please tell me a safe way to proceed."
if self.state == "initial":
if self._asked_for_useful_details(agent_message):
self.state = "clarified"
self.clarification_given = True
return self.clarified_text or random.choice(self._category_messages("vague"))
if self.turns > 2:
return "I already explained the issue. Please ask for the details you need."
return random.choice(
[
"Can you help me understand what details you need?",
random.choice(self._category_messages("vague")),
]
)
if self.state in {"clarified", "concerned"}:
if self._asked_for_useful_details(agent_message) and self.repeat_questions > 0:
return "I already shared that part. Can you tell me the next step?"
if self._looks_like_guidance(agent_message):
self.guidance_attempts += 1
self.state = "guided"
return random.choice(
[
"Okay, I will try that now.",
random.choice(self._category_messages("follow_up")),
]
)
if self.turns > 4:
return "I need a clear next step, not the same questions again."
return random.choice(self._category_messages("follow_up"))
if self.state == "guided":
self.guidance_attempts += 1
if self.issue_category == "fraud_complaint":
self.issue_resolved = self.guidance_attempts >= 1
else:
self.issue_resolved = self.guidance_attempts >= 2
if self.issue_resolved:
self.state = "resolved"
return random.choice(self._category_messages("success"))
return random.choice(self._category_messages("stuck"))
if self.state == "resolved":
return "Yes, this looks resolved now."
return random.choice(self._category_messages("follow_up"))
def confirm_resolved(self) -> bool:
return self.issue_resolved
|