pythonprincess commited on
Commit
5faf0e7
·
verified ·
1 Parent(s): 18f003a

Upload frontend_pam.py

Browse files
Files changed (1) hide show
  1. frontend_pam.py +351 -0
frontend_pam.py ADDED
@@ -0,0 +1,351 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # filename: frontend_pam.py (ENHANCED FOR HF SPACES + PERSONALITY)
2
+
3
+ import os
4
+ import json
5
+ import random
6
+ import requests
7
+ from datetime import datetime
8
+ from typing import Dict, Any, Optional
9
+ import time
10
+
11
+ # --- Constants for Data Paths ---
12
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
13
+ DATA_DIR = os.path.join(BASE_DIR, "data")
14
+
15
+ APPOINTMENTS_FILE = os.path.join(DATA_DIR, "appointments.json")
16
+ RESOURCES_FILE = os.path.join(DATA_DIR, "resources.json")
17
+ FOLLOW_UP_FILE = os.path.join(DATA_DIR, "follow_up.json")
18
+ PERMISSIONS_FILE = os.path.join(DATA_DIR, "permissions.json")
19
+
20
+ # --- HuggingFace Inference API Setup ---
21
+ HF_API_TOKEN = os.getenv("HF_READ_TOKEN")
22
+ if not HF_API_TOKEN:
23
+ print("WARNING: HF_READ_TOKEN not found. Set it in Hugging Face Space settings.")
24
+
25
+ HF_HEADERS = {"Authorization": f"Bearer {HF_API_TOKEN}"} if HF_API_TOKEN else {}
26
+
27
+ # Updated model endpoints for better CPU performance
28
+ # Updated to use router.huggingface.co (api-inference.huggingface.co is deprecated)
29
+ HF_ENDPOINTS = {
30
+ "intent": "https://router.huggingface.co/models/facebook/bart-large-mnli",
31
+ "sentiment": "https://router.huggingface.co/models/distilbert-base-uncased-finetuned-sst-2-english",
32
+ "chat": "https://router.huggingface.co/models/mistralai/Mistral-7B-Instruct-v0.2"
33
+ }
34
+
35
+ # --- Load JSON Helper ---
36
+ def load_json(filepath: str) -> Dict[str, Any]:
37
+ """Safely load JSON data files"""
38
+ try:
39
+ with open(filepath, 'r', encoding='utf-8') as f:
40
+ return json.load(f)
41
+ except FileNotFoundError:
42
+ print(f"⚠️ Data file not found: {filepath}")
43
+ return {}
44
+ except json.JSONDecodeError as e:
45
+ print(f"⚠️ Failed to decode JSON from {filepath}: {e}")
46
+ return {}
47
+ except Exception as e:
48
+ print(f"⚠️ Unexpected error loading {filepath}: {e}")
49
+ return {}
50
+
51
+ # --- Inference API Call Helper with Retry Logic ---
52
+ def hf_infer(task: str, payload: Any, max_retries: int = 3) -> Any:
53
+ """Call HuggingFace Inference API with retry logic for model loading"""
54
+ url = HF_ENDPOINTS.get(task)
55
+ if not url:
56
+ return {"error": f"Invalid task: {task}"}
57
+
58
+ for attempt in range(max_retries):
59
+ try:
60
+ response = requests.post(url, headers=HF_HEADERS, json=payload, timeout=30)
61
+
62
+ # Handle deprecated endpoint (410) - should not happen with new router endpoint
63
+ if response.status_code == 410:
64
+ error_msg = response.text
65
+ print(f"❌ Deprecated endpoint error (410): {error_msg}")
66
+ # Try to extract the new endpoint suggestion if available
67
+ try:
68
+ error_data = response.json()
69
+ if "router.huggingface.co" in error_data.get("error", ""):
70
+ print(f"⚠️ Endpoint already updated but still getting 410. Check HF API token permissions.")
71
+ except:
72
+ pass
73
+ return {"error": "API endpoint deprecated. Please verify the router endpoint is correctly configured."}
74
+
75
+ # Handle model loading state
76
+ if response.status_code == 503:
77
+ result = response.json()
78
+ if "loading" in result.get("error", "").lower():
79
+ wait_time = result.get("estimated_time", 20)
80
+ print(f"⏳ Model loading... waiting {wait_time}s (attempt {attempt + 1}/{max_retries})")
81
+ time.sleep(wait_time)
82
+ continue
83
+
84
+ if response.status_code == 200:
85
+ return response.json()
86
+ else:
87
+ # Improved error logging
88
+ error_text = response.text[:500] # Limit error text length
89
+ print(f"⚠️ HF API Error ({response.status_code}): {error_text}")
90
+
91
+ # Try to parse error details for better user feedback
92
+ try:
93
+ error_data = response.json()
94
+ if "error" in error_data:
95
+ return {"error": f"API Error {response.status_code}: {error_data['error']}"}
96
+ except:
97
+ pass
98
+
99
+ return {"error": f"API Error {response.status_code}: {error_text[:100]}"}
100
+
101
+ except requests.exceptions.Timeout:
102
+ print(f"⏱️ Request timeout (attempt {attempt + 1}/{max_retries})")
103
+ if attempt < max_retries - 1:
104
+ time.sleep(5)
105
+ except requests.exceptions.RequestException as e:
106
+ print(f"⚠️ Request exception: {e}")
107
+ if attempt < max_retries - 1:
108
+ time.sleep(2)
109
+ except Exception as e:
110
+ print(f"⚠️ Unexpected error: {e}")
111
+ return {"error": str(e)}
112
+
113
+ return {"error": "Max retries reached. Please check your connection and try again."}
114
+
115
+ # --- Agent Initialization ---
116
+ def load_frontend_agent() -> 'FrontendPAM':
117
+ """Initialize Frontend PAM with data files"""
118
+ print("💕 Initializing Frontend PAM (Sweet Southern Receptionist)...")
119
+ data = {
120
+ "APPOINTMENTS": load_json(APPOINTMENTS_FILE),
121
+ "RESOURCES": load_json(RESOURCES_FILE),
122
+ "FOLLOW_UP": load_json(FOLLOW_UP_FILE),
123
+ "PERMISSIONS": load_json(PERMISSIONS_FILE)
124
+ }
125
+ return FrontendPAM(data)
126
+
127
+ # --- PAM's Sweet Southern Personality ---
128
+ PAM_TONE = """You are PAM, a sweet southern receptionist at a women's health clinic.
129
+ You're warm, comforting, and encouraging - like everyone's favorite caring front desk person.
130
+ You use words of endearment naturally (honey, dear, boo, sugar, sweetheart).
131
+ You make people feel welcome, safe, and taken care of.
132
+ You're professional but personal - you genuinely care about each person who walks through the door.
133
+ Keep responses conversational, warm, and under 3 sentences unless more detail is needed."""
134
+
135
+ # Words of endearment - Southern style
136
+ ENDEARMENTS = [
137
+ "honey", "dear", "boo", "sugar", "sweetheart",
138
+ "love", "darling", "hun", "sweetpea", "angel"
139
+ ]
140
+
141
+ # Warm greetings
142
+ GREETINGS = [
143
+ "Well hey there", "Hi there", "Hello",
144
+ "Hey", "Well hello", "Hi"
145
+ ]
146
+
147
+ # Comforting phrases
148
+ COMFORT_PHRASES = [
149
+ "I'm here to help you with that",
150
+ "Let me take care of that for you",
151
+ "We'll get that sorted out together",
152
+ "I've got you covered",
153
+ "Don't you worry about a thing"
154
+ ]
155
+
156
+ # --- Agent Class ---
157
+ class FrontendPAM:
158
+ """Frontend PAM - Sweet Southern Receptionist"""
159
+
160
+ def __init__(self, data: Dict[str, Dict]):
161
+ self.APPOINTMENTS = data.get("APPOINTMENTS", {})
162
+ self.PERMISSIONS = data.get("PERMISSIONS", {})
163
+ self.RESOURCES = data.get("RESOURCES", {})
164
+ self.FOLLOW_UP = data.get("FOLLOW_UP", {})
165
+ self.user_id = "user_001" # Default user, can be dynamic
166
+
167
+ def _get_endearment(self) -> str:
168
+ """Get a random term of endearment"""
169
+ return random.choice(ENDEARMENTS)
170
+
171
+ def _get_greeting(self) -> str:
172
+ """Get a random warm greeting"""
173
+ return random.choice(GREETINGS)
174
+
175
+ def _get_comfort_phrase(self) -> str:
176
+ """Get a random comforting phrase"""
177
+ return random.choice(COMFORT_PHRASES)
178
+
179
+ def _detect_intent(self, text: str) -> str:
180
+ """Detect user intent using zero-shot classification"""
181
+ candidate_labels = [
182
+ "appointment scheduling",
183
+ "health symptoms inquiry",
184
+ "resource request",
185
+ "general question",
186
+ "emergency concern"
187
+ ]
188
+
189
+ payload = {
190
+ "inputs": text,
191
+ "parameters": {"candidate_labels": candidate_labels}
192
+ }
193
+
194
+ result = hf_infer("intent", payload)
195
+ if isinstance(result, dict) and "error" in result:
196
+ return "general_question"
197
+
198
+ # BART-MNLI returns labels array
199
+ if isinstance(result, dict) and "labels" in result:
200
+ return result["labels"][0].replace(" ", "_")
201
+
202
+ return "general_question"
203
+
204
+ def _detect_sentiment(self, text: str) -> Dict[str, Any]:
205
+ """Detect sentiment to gauge emotional state"""
206
+ result = hf_infer("sentiment", {"inputs": text})
207
+ if isinstance(result, list) and len(result) > 0:
208
+ return result[0][0] if isinstance(result[0], list) else result[0]
209
+ return {"label": "NEUTRAL", "score": 0.5}
210
+
211
+ def _generate_response(self, text: str, context: str = "") -> str:
212
+ """Generate conversational response using LLM"""
213
+ endearment = self._get_endearment()
214
+
215
+ prompt = f"""<s>[INST] {PAM_TONE}
216
+
217
+ User said: "{text}"
218
+ {f'Context: {context}' if context else ''}
219
+
220
+ Respond warmly as PAM, using natural southern charm. Address the user as "{endearment}". [/INST]"""
221
+
222
+ payload = {
223
+ "inputs": prompt,
224
+ "parameters": {
225
+ "max_new_tokens": 150,
226
+ "temperature": 0.7,
227
+ "top_p": 0.9,
228
+ "return_full_text": False
229
+ }
230
+ }
231
+
232
+ result = hf_infer("chat", payload)
233
+
234
+ if isinstance(result, dict) and "error" in result:
235
+ return f"Sorry {endearment}, I'm having a little technical hiccup. Could you try that again for me?"
236
+
237
+ if isinstance(result, list) and len(result) > 0:
238
+ generated = result[0].get("generated_text", "")
239
+ # Clean up the response
240
+ reply = generated.strip()
241
+ # Ensure endearment is included if not already
242
+ if endearment not in reply.lower():
243
+ reply = f"{reply.rstrip('.')} {endearment}."
244
+ return reply
245
+
246
+ return f"Sorry {endearment}, I didn't quite catch that. Could you say that again?"
247
+
248
+ def respond(self, user_text: str, backend_brief: Optional[str] = None) -> Dict[str, Any]:
249
+ """Main response handler with sweet southern personality"""
250
+
251
+ # Get personalized elements
252
+ endearment = self._get_endearment()
253
+ greeting = self._get_greeting()
254
+ comfort = self._get_comfort_phrase()
255
+
256
+ # Check for PAM greeting (flexible)
257
+ if not any(trigger in user_text.lower() for trigger in ["hey pam", "hi pam", "hello pam", "pam,"]):
258
+ return {
259
+ "reply": f"{greeting} {endearment}! Just a quick note - I respond best when you start with 'Hey PAM' or 'Hi PAM'. It helps me know you're talking to me. 💕"
260
+ }
261
+
262
+ # Clean text for processing
263
+ text = user_text.lower().replace("pam", "you").strip()
264
+
265
+ # Detect intent and sentiment
266
+ detected_intent = self._detect_intent(text)
267
+ sentiment_result = self._detect_sentiment(text)
268
+
269
+ # Check if user seems distressed
270
+ is_distressed = sentiment_result.get("label") == "NEGATIVE" and sentiment_result.get("score", 0) > 0.7
271
+
272
+ # Permission check (sensitive topics)
273
+ for term, allowed in self.PERMISSIONS.items():
274
+ if term.lower() in text and not allowed:
275
+ return {
276
+ "intent": detected_intent,
277
+ "sentiment": sentiment_result,
278
+ "reply": f"{greeting} {endearment}, that's something I need to connect you with a provider for directly. {comfort}, and I can get you to the right person. Would that be okay?"
279
+ }
280
+
281
+ # Handle appointments
282
+ if any(word in text for word in ["appointment", "scheduled", "booking", "schedule"]):
283
+ appt = self.APPOINTMENTS.get(self.user_id)
284
+ if appt:
285
+ appt_date = appt.get('date', 'soon')
286
+ appt_type = appt.get('type', 'appointment')
287
+ return {
288
+ "intent": "appointment_scheduling",
289
+ "sentiment": sentiment_result,
290
+ "reply": f"{greeting} {endearment}! You've got a {appt_type} scheduled for {appt_date}. Do you need to reschedule or have any questions about it?"
291
+ }
292
+ else:
293
+ return {
294
+ "intent": "appointment_scheduling",
295
+ "sentiment": sentiment_result,
296
+ "reply": f"{greeting} {endearment}! I don't see any appointments on file for you yet. Would you like me to help you get one set up?"
297
+ }
298
+
299
+ # Handle health symptoms/concerns
300
+ symptom_keywords = ["cramp", "pain", "discharge", "bleed", "smell", "spotting",
301
+ "fatigue", "mood", "missed period", "nausea", "concern"]
302
+ if any(keyword in text for keyword in symptom_keywords):
303
+ concern_prefix = f"{greeting} {endearment}, I hear you" if is_distressed else f"{greeting} {endearment}"
304
+ return {
305
+ "intent": "health_symptoms_inquiry",
306
+ "sentiment": sentiment_result,
307
+ "reply": f"{concern_prefix}. I've pulled together some helpful resources about what you're experiencing. Would you like me to also connect you with a nurse for a quick chat?"
308
+ }
309
+
310
+ # Handle resource requests
311
+ if any(word in text for word in ["resource", "information", "help", "guide", "link"]):
312
+ return {
313
+ "intent": "resource_request",
314
+ "sentiment": sentiment_result,
315
+ "reply": f"{greeting} {endearment}! {comfort}. What type of resources are you looking for? I've got information on just about everything."
316
+ }
317
+
318
+ # Handle emergency indicators
319
+ emergency_keywords = ["emergency", "urgent", "severe pain", "heavy bleeding", "can't breathe"]
320
+ if any(keyword in text for keyword in emergency_keywords):
321
+ return {
322
+ "intent": "emergency_concern",
323
+ "sentiment": sentiment_result,
324
+ "reply": f"{endearment}, if this is a medical emergency, please call 911 or go to your nearest emergency room right away. I'm here for you, but your safety comes first. ❤️"
325
+ }
326
+
327
+ # General conversational response
328
+ context = f"Backend summary: {backend_brief}" if backend_brief else ""
329
+ reply = self._generate_response(user_text, context)
330
+
331
+ return {
332
+ "intent": detected_intent,
333
+ "sentiment": sentiment_result,
334
+ "backend_summary": backend_brief or "No backend data",
335
+ "reply": reply
336
+ }
337
+
338
+ # --- Quick Test ---
339
+ if __name__ == "__main__":
340
+ pam = load_frontend_agent()
341
+ test_queries = [
342
+ "Hey PAM, I have a question about my appointment",
343
+ "Hi PAM, I'm experiencing some cramping",
344
+ "Hey PAM, can you help me find resources?"
345
+ ]
346
+
347
+ print("\n💕 Testing Frontend PAM...\n")
348
+ for query in test_queries:
349
+ print(f"USER: {query}")
350
+ response = pam.respond(query)
351
+ print(f"PAM: {response['reply']}\n")