pythonprincess commited on
Commit
9cd554e
·
verified ·
1 Parent(s): 576ec9d

Upload frontend_pam.py

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