Spaces:
Running
Running
Commit
·
3214945
1
Parent(s):
fec2827
Some updates
Browse files
main.py
CHANGED
@@ -73,17 +73,17 @@ class HealthcareChatbot:
|
|
73 |
self.user_id = 'e2fe5655-d6ac-447d-8237-850413288561'
|
74 |
self.max_retries = 3
|
75 |
self.retry_delay = 2
|
76 |
-
|
77 |
# Store conversation history
|
78 |
self.conversation_history = []
|
79 |
self.max_history_length = 10 # Keep last 10 exchanges
|
80 |
-
|
81 |
# Initialize components
|
82 |
self._initialize_language_tools()
|
83 |
self._initialize_llm()
|
84 |
self._initialize_parsers_and_chains()
|
85 |
self._initialize_date_parser()
|
86 |
-
|
87 |
print("Healthcare Chatbot initialized successfully!")
|
88 |
self._print_welcome_message()
|
89 |
|
@@ -111,7 +111,7 @@ class HealthcareChatbot:
|
|
111 |
try:
|
112 |
self.embeddings = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-large")
|
113 |
self.language_classifier = pipeline(
|
114 |
-
"text-classification",
|
115 |
model="papluca/xlm-roberta-base-language-detection",
|
116 |
top_k=1
|
117 |
)
|
@@ -156,172 +156,172 @@ class HealthcareChatbot:
|
|
156 |
|
157 |
# Intent classification prompt
|
158 |
self.intent_classifier_template = PromptTemplate(
|
159 |
-
|
160 |
-
|
161 |
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
Think step by step:
|
167 |
-
|
168 |
-
1. What does the user want from this message?
|
169 |
-
Read the user's message carefully. What is the user trying to say or accomplish? What would a human understand from this message?
|
170 |
-
|
171 |
-
2. Can any API endpoint fulfill what the user wants?
|
172 |
-
Look at each API endpoint. Does any endpoint do what the user is asking for? Be very precise - only say yes if there's a clear match.
|
173 |
-
|
174 |
-
Important rules:
|
175 |
-
- Focus ONLY on the current message, ignore conversation history for classification
|
176 |
-
- If the user is just talking, being social, or saying something casual, that's CONVERSATION
|
177 |
-
- Only choose API_ACTION if the user is clearly asking for something an API endpoint can do
|
178 |
-
- When you're not sure, choose CONVERSATION
|
179 |
-
|
180 |
-
Answer in this format:
|
181 |
-
{{
|
182 |
-
"intent": "API_ACTION" or "CONVERSATION",
|
183 |
-
"confidence": [0.0 to 1.0],
|
184 |
-
"reasoning": "What does the user want? Can any API do this?",
|
185 |
-
"requires_backend": true or false
|
186 |
-
}}
|
187 |
-
""",
|
188 |
-
input_variables=["user_query", "detected_language", "conversation_history", "endpoints_documentation"]
|
189 |
-
)
|
190 |
|
|
|
191 |
|
|
|
|
|
192 |
|
|
|
|
|
193 |
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
|
|
198 |
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
203 |
|
204 |
-
=== AVAILABLE ENDPOINTS ===
|
205 |
-
{endpoints_documentation}
|
206 |
|
207 |
-
=== USER REQUEST DETAILS ===
|
208 |
-
User Query: {user_query}
|
209 |
-
Detected Language: {detected_language}
|
210 |
-
Extracted Keywords: {extracted_keywords}
|
211 |
-
Sentiment Analysis: {sentiment_analysis}
|
212 |
-
Conversation History: {conversation_history}
|
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 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
325 |
# self.router_prompt_template = PromptTemplate(
|
326 |
# template="""
|
327 |
# You are a precise API routing assistant. Your job is to analyze user queries and select the correct API endpoint with proper parameters.
|
@@ -448,162 +448,140 @@ class HealthcareChatbot:
|
|
448 |
# API response formatting prompt (reuse existing user_response_template)
|
449 |
# self.user_response_template = PromptTemplate(
|
450 |
# template="""
|
451 |
-
# You are a professional healthcare assistant.
|
452 |
-
|
453 |
-
# === STRICT REQUIREMENTS ===
|
454 |
-
# - Respond ONLY in {detected_language}
|
455 |
-
# - Use EXACT information from api_response - NO modifications
|
456 |
-
# - Keep responses SHORT, SIMPLE, and DIRECT
|
457 |
-
# - Use professional healthcare tone
|
458 |
-
# - NEVER mix languages or make up information
|
459 |
|
460 |
-
# === ORIGINAL REQUEST ===
|
461 |
# User Query: {user_query}
|
462 |
# User Sentiment: {sentiment_analysis}
|
|
|
463 |
|
464 |
-
#
|
465 |
# {api_response}
|
466 |
|
467 |
-
# ===
|
468 |
-
|
469 |
-
#
|
470 |
-
#
|
471 |
-
#
|
472 |
-
#
|
473 |
-
#
|
474 |
-
#
|
475 |
-
|
476 |
-
#
|
477 |
-
|
478 |
-
|
479 |
-
#
|
480 |
-
# -
|
481 |
-
|
482 |
-
#
|
483 |
-
|
484 |
-
#
|
485 |
-
# "
|
486 |
-
|
487 |
-
|
488 |
-
#
|
489 |
-
#
|
490 |
-
#
|
491 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
492 |
|
493 |
# === CRITICAL RULES ===
|
494 |
-
# - Extract
|
495 |
-
# -
|
496 |
-
# -
|
497 |
-
# -
|
498 |
-
# -
|
499 |
-
|
500 |
-
#
|
501 |
-
# - Convert technical data into human-readable format
|
502 |
-
# - NEVER add translations or explanations in other languages
|
503 |
-
# - NEVER include "Translated response" or similar phrases
|
504 |
-
# - END your response immediately after providing the requested information
|
505 |
-
# - Do NOT add any English translation when responding in Arabic
|
506 |
-
# - Do NOT add any Arabic translation when responding in English
|
507 |
-
|
508 |
-
# === HUMAN-LIKE FORMATTING RULES ===
|
509 |
-
# FOR ARABIC:
|
510 |
-
# - Instead of "رابط الحجز: [URL]" → say "تم حجز موعدك بنجاح"
|
511 |
-
# - Instead of "الأزمة: غير متوفرة" → omit or say "بدون أعراض محددة"
|
512 |
-
# - Use natural sentences like "موعدك مع الدكتور [Name] يوم [Date] في تمام الساعة [Time]"
|
513 |
-
# - Avoid technical terms and system language
|
514 |
-
|
515 |
-
# FOR ENGLISH:
|
516 |
-
# - Instead of "Booking URL: [link]" → say "Your appointment has been scheduled"
|
517 |
-
# - Use natural sentences like "You have an appointment with Dr. [Name] on [Date] at [Time]"
|
518 |
-
# - Avoid showing raw URLs, IDs, or technical data
|
519 |
-
|
520 |
-
# === QUALITY CHECKS ===
|
521 |
-
# Before responding, verify:
|
522 |
-
# ✓ Response sounds natural and conversational
|
523 |
-
# ✓ No technical URLs, IDs, or system codes are shown
|
524 |
-
# ✓ Information is presented in human-friendly language
|
525 |
-
# ✓ Grammar is correct in the target language
|
526 |
-
# ✓ Response directly answers the user's question
|
527 |
-
# ✓ No bullet points or technical formatting
|
528 |
-
# ✓ Sounds like a helpful human assistant, not a system
|
529 |
-
|
530 |
-
# Generate a response that is accurate, helpful, and professionally formatted.
|
531 |
-
|
532 |
-
# === FINAL INSTRUCTION ===
|
533 |
-
# Respond ONLY in the requested language. Do NOT provide translations, explanations, or additional text in any other language. Stop immediately after answering the user's question.
|
534 |
# """,
|
535 |
# input_variables=["user_query", "api_response", "detected_language", "sentiment_analysis"]
|
536 |
# )
|
537 |
self.user_response_template = PromptTemplate(
|
538 |
-
|
539 |
-
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
|
557 |
-
|
558 |
-
|
559 |
-
|
560 |
-
|
561 |
-
|
562 |
-
|
563 |
-
|
564 |
-
|
565 |
-
|
566 |
-
|
567 |
-
|
568 |
-
|
569 |
-
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
|
574 |
-
|
575 |
-
|
576 |
-
|
577 |
-
|
578 |
-
|
579 |
-
|
580 |
-
|
581 |
-
|
582 |
-
|
583 |
-
|
584 |
-
|
585 |
-
|
586 |
-
|
587 |
-
|
588 |
-
|
589 |
-
|
590 |
-
|
591 |
-
|
592 |
-
|
593 |
-
|
594 |
-
|
595 |
-
|
596 |
-
|
597 |
-
|
598 |
-
|
599 |
-
|
600 |
-
|
|
|
601 |
|
602 |
# Create chains
|
603 |
self.intent_chain = LLMChain(llm=self.llm, prompt=self.intent_classifier_template)
|
604 |
self.router_chain = LLMChain(llm=self.llm, prompt=self.router_prompt_template)
|
605 |
self.conversation_chain = LLMChain(llm=self.llm, prompt=self.conversation_template)
|
606 |
self.api_response_chain = LLMChain(llm=self.llm, prompt=self.user_response_template)
|
|
|
607 |
def detect_language(self, text):
|
608 |
"""Detect language of the input text"""
|
609 |
if self.language_classifier and len(text.strip()) > 3:
|
@@ -611,7 +589,7 @@ class HealthcareChatbot:
|
|
611 |
result = self.language_classifier(text)
|
612 |
detected_lang = result[0][0]['label']
|
613 |
confidence = result[0][0]['score']
|
614 |
-
|
615 |
if detected_lang in ['ar', 'arabic']:
|
616 |
return "arabic"
|
617 |
elif detected_lang in ['en', 'english']:
|
@@ -620,12 +598,12 @@ class HealthcareChatbot:
|
|
620 |
return "english" # Default to English for unsupported languages
|
621 |
except:
|
622 |
pass
|
623 |
-
|
624 |
# Fallback: Basic Arabic detection
|
625 |
arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]+')
|
626 |
if arabic_pattern.search(text):
|
627 |
return "arabic"
|
628 |
-
|
629 |
return "english"
|
630 |
|
631 |
def analyze_sentiment(self, text):
|
@@ -639,7 +617,7 @@ class HealthcareChatbot:
|
|
639 |
}
|
640 |
except:
|
641 |
pass
|
642 |
-
|
643 |
return {"sentiment": "NEUTRAL", "score": 0.5}
|
644 |
|
645 |
def extract_keywords(self, text):
|
@@ -655,12 +633,12 @@ class HealthcareChatbot:
|
|
655 |
"""Get recent conversation history as context"""
|
656 |
if not self.conversation_history:
|
657 |
return "No previous conversation"
|
658 |
-
|
659 |
context = []
|
660 |
for item in self.conversation_history[-3:]: # Last 3 exchanges
|
661 |
context.append(f"User: {item['user_message']}")
|
662 |
context.append(f"Bot: {item['bot_response'][:100]}...") # Truncate long responses
|
663 |
-
|
664 |
return " | ".join(context)
|
665 |
|
666 |
def add_to_history(self, user_message, bot_response, response_type):
|
@@ -671,7 +649,7 @@ class HealthcareChatbot:
|
|
671 |
'bot_response': bot_response,
|
672 |
'response_type': response_type
|
673 |
})
|
674 |
-
|
675 |
# Keep only recent history
|
676 |
if len(self.conversation_history) > self.max_history_length:
|
677 |
self.conversation_history = self.conversation_history[-self.max_history_length:]
|
@@ -685,14 +663,14 @@ class HealthcareChatbot:
|
|
685 |
"conversation_history": self.get_conversation_context(),
|
686 |
"endpoints_documentation": json.dumps(self.endpoints_documentation, indent=2)
|
687 |
})
|
688 |
-
|
689 |
# Parse the JSON response
|
690 |
intent_text = result["text"]
|
691 |
# Clean and parse JSON
|
692 |
cleaned_response = re.sub(r'//.*?$', '', intent_text, flags=re.MULTILINE)
|
693 |
cleaned_response = re.sub(r'/\*.*?\*/', '', cleaned_response, flags=re.DOTALL)
|
694 |
cleaned_response = re.sub(r',(\s*[}\]])', r'\1', cleaned_response)
|
695 |
-
|
696 |
try:
|
697 |
intent_data = json.loads(cleaned_response)
|
698 |
return intent_data
|
@@ -728,9 +706,9 @@ class HealthcareChatbot:
|
|
728 |
"sentiment_analysis": json.dumps(sentiment_result),
|
729 |
"conversation_history": self.get_conversation_context()
|
730 |
})
|
731 |
-
|
732 |
return result["text"].strip()
|
733 |
-
|
734 |
except Exception as e:
|
735 |
# Fallback response
|
736 |
if detected_language == "arabic":
|
@@ -746,12 +724,13 @@ class HealthcareChatbot:
|
|
746 |
|
747 |
print('Sending the api request')
|
748 |
print(f"🔗 Making API call to {endpoint_method} {self.BASE_URL + endpoint_url} with params: {endpoint_params}")
|
749 |
-
|
750 |
# Inject patient_id if needed
|
751 |
if 'patient_id' in endpoint_params:
|
752 |
endpoint_params['patient_id'] = self.user_id
|
753 |
-
|
754 |
retries = 0
|
|
|
755 |
while retries < self.max_retries:
|
756 |
try:
|
757 |
if endpoint_method.upper() == 'GET':
|
@@ -769,10 +748,11 @@ class HealthcareChatbot:
|
|
769 |
headers=self.headers,
|
770 |
timeout=10
|
771 |
)
|
772 |
-
|
773 |
response.raise_for_status()
|
|
|
774 |
return response.json()
|
775 |
-
|
776 |
except requests.exceptions.RequestException as e:
|
777 |
retries += 1
|
778 |
if retries >= self.max_retries:
|
@@ -781,35 +761,35 @@ class HealthcareChatbot:
|
|
781 |
"details": str(e),
|
782 |
"status_code": getattr(e.response, 'status_code', None) if hasattr(e, 'response') else None
|
783 |
}
|
784 |
-
|
785 |
time.sleep(self.retry_delay)
|
786 |
def parse_relative_date(self, text, detected_language):
|
787 |
"""
|
788 |
Parse relative dates from text using a combination of methods
|
789 |
"""
|
790 |
today = datetime.now()
|
791 |
-
|
792 |
# Handle common relative date patterns in English and Arabic
|
793 |
tomorrow_patterns = {
|
794 |
'english': [r'\btomorrow\b', r'\bnext day\b'],
|
795 |
'arabic': [r'\bغدا\b', r'\bبكرة\b', r'\bغدًا\b', r'\bالغد\b']
|
796 |
}
|
797 |
-
|
798 |
next_week_patterns = {
|
799 |
'english': [r'\bnext week\b'],
|
800 |
'arabic': [r'\bالأسبوع القادم\b', r'\bالأسبوع المقبل\b', r'\bالاسبوع الجاي\b']
|
801 |
}
|
802 |
-
|
803 |
# Check for "tomorrow" patterns
|
804 |
for pattern in tomorrow_patterns.get(detected_language, []) + tomorrow_patterns.get('english', []):
|
805 |
if re.search(pattern, text, re.IGNORECASE):
|
806 |
return (today + timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S')
|
807 |
-
|
808 |
# Check for "next week" patterns
|
809 |
for pattern in next_week_patterns.get(detected_language, []) + next_week_patterns.get('english', []):
|
810 |
if re.search(pattern, text, re.IGNORECASE):
|
811 |
return (today + timedelta(days=7)).strftime('%Y-%m-%dT%H:%M:%S')
|
812 |
-
|
813 |
# If NER model is available, use it to extract date entities
|
814 |
if self.date_parser and detected_language == 'english':
|
815 |
try:
|
@@ -823,7 +803,7 @@ class HealthcareChatbot:
|
|
823 |
return (today + timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S')
|
824 |
except Exception as e:
|
825 |
print(f"Error in date parsing: {e}")
|
826 |
-
|
827 |
# Default return None if no date pattern is recognized
|
828 |
return None
|
829 |
|
@@ -831,11 +811,11 @@ class HealthcareChatbot:
|
|
831 |
def handle_api_action(self, user_query, detected_language, sentiment_result, keywords):
|
832 |
"""Handle API-based actions"""
|
833 |
try:
|
834 |
-
|
835 |
-
parsed_date = self.parse_relative_date(user_query, detected_language)
|
836 |
-
if parsed_date:
|
837 |
-
|
838 |
-
|
839 |
# Route the query to determine API endpoint
|
840 |
router_result = self.router_chain.invoke({
|
841 |
"endpoints_documentation": json.dumps(self.endpoints_documentation, indent=2),
|
@@ -843,15 +823,18 @@ class HealthcareChatbot:
|
|
843 |
"detected_language": detected_language,
|
844 |
"extracted_keywords": ", ".join(keywords),
|
845 |
"sentiment_analysis": json.dumps(sentiment_result),
|
846 |
-
"conversation_history": self.get_conversation_context()
|
|
|
|
|
|
|
847 |
})
|
848 |
-
|
849 |
# Parse router response
|
850 |
route_text = router_result["text"]
|
851 |
# cleaned_response = re.sub(r'//.*?$', '', route_text, flags=re.MULTILINE)
|
852 |
# cleaned_response = re.sub(r'/\*.*?\*/', '', cleaned_response, flags=re.DOTALL)
|
853 |
# cleaned_response = re.sub(r',(\s*[}\]])', r'\1', cleaned_response)
|
854 |
-
|
855 |
# try:
|
856 |
# parsed_route = json.loads(cleaned_response)
|
857 |
# except json.JSONDecodeError:
|
@@ -860,17 +843,17 @@ class HealthcareChatbot:
|
|
860 |
# parsed_route = json.loads(json_match.group(0))
|
861 |
# else:
|
862 |
# raise ValueError("Could not parse routing response")
|
863 |
-
|
864 |
# print(f"🔍 Parsed route: {parsed_route}")
|
865 |
cleaned_response = route_text
|
866 |
-
|
867 |
# Remove any comments (both single-line and multi-line)
|
868 |
cleaned_response = re.sub(r'//.*?$', '', cleaned_response, flags=re.MULTILINE)
|
869 |
cleaned_response = re.sub(r'/\*.*?\*/', '', cleaned_response, flags=re.DOTALL)
|
870 |
-
|
871 |
# Remove any trailing commas
|
872 |
cleaned_response = re.sub(r',(\s*[}\]])', r'\1', cleaned_response)
|
873 |
-
|
874 |
# Try different methods to parse the JSON response
|
875 |
try:
|
876 |
# First attempt: direct JSON parsing of cleaned response
|
@@ -896,27 +879,27 @@ class HealthcareChatbot:
|
|
896 |
print(f"Failed to parse JSON. Raw response: {route_text}")
|
897 |
print(f"Cleaned response: {cleaned_response}")
|
898 |
raise ValueError("Could not extract valid JSON from LLM response")
|
899 |
-
|
900 |
if not parsed_route:
|
901 |
raise ValueError("Failed to parse LLM response into valid JSON")
|
902 |
-
|
903 |
# Replace any placeholder values and inject parsed dates if available
|
904 |
if 'params' in parsed_route:
|
905 |
if 'patient_id' in parsed_route['params']:
|
906 |
parsed_route['params']['patient_id'] = self.user_id
|
907 |
-
|
908 |
# Inject parsed date if available and a date parameter exists
|
909 |
-
date_params = ['appointment_date', 'date', 'schedule_date', 'date_time', 'new_date_time']
|
910 |
-
if parsed_date:
|
911 |
-
|
912 |
-
|
913 |
-
|
914 |
-
|
915 |
print('Parsed route: ', parsed_route)
|
916 |
|
917 |
# Make backend API call
|
918 |
api_response = self.backend_call(parsed_route)
|
919 |
-
|
920 |
print("🔗 API response received:", api_response)
|
921 |
# Generate user-friendly response
|
922 |
user_response_result = self.api_response_chain.invoke({
|
@@ -927,20 +910,20 @@ class HealthcareChatbot:
|
|
927 |
})
|
928 |
|
929 |
print("🔗 API response:", user_response_result["text"].strip())
|
930 |
-
|
931 |
return {
|
932 |
"response": user_response_result["text"].strip(),
|
933 |
"api_data": api_response,
|
934 |
"routing_info": parsed_route
|
935 |
}
|
936 |
-
|
937 |
except Exception as e:
|
938 |
# Fallback error response
|
939 |
if detected_language == "arabic":
|
940 |
error_msg = "أعتذر، لم أتمكن من معالجة طلبك. يرجى المحاولة مرة أخرى أو صياغة السؤال بطريقة مختلفة."
|
941 |
else:
|
942 |
error_msg = "I apologize, I couldn't process your request. Please try again or rephrase your question."
|
943 |
-
|
944 |
return {
|
945 |
"response": error_msg,
|
946 |
"api_data": {"error": str(e)},
|
@@ -950,7 +933,7 @@ class HealthcareChatbot:
|
|
950 |
def chat(self, user_message: str) -> ChatResponse:
|
951 |
"""Main chat method that handles user messages"""
|
952 |
start_time = time.time()
|
953 |
-
|
954 |
# Check for exit commands
|
955 |
exit_commands = ['quit', 'exit', 'bye', 'خروج', 'وداعا', 'مع السلامة']
|
956 |
if user_message.lower().strip() in exit_commands:
|
@@ -960,19 +943,19 @@ class HealthcareChatbot:
|
|
960 |
message="Goodbye! Take care of your health! / وداعاً! اعتن بصحتك!",
|
961 |
language="bilingual"
|
962 |
)
|
963 |
-
|
964 |
try:
|
965 |
# Language detection and analysis
|
966 |
detected_language = self.detect_language(user_message)
|
967 |
sentiment_result = self.analyze_sentiment(user_message)
|
968 |
keywords = self.extract_keywords(user_message)
|
969 |
-
|
970 |
print(f"🔍 Language: {detected_language} | Sentiment: {sentiment_result['sentiment']} | Keywords: {keywords}")
|
971 |
-
|
972 |
# Classify intent
|
973 |
intent_data = self.classify_intent(user_message, detected_language)
|
974 |
print(f"🎯 Intent: {intent_data['intent']} (confidence: {intent_data.get('confidence', 'N/A')})")
|
975 |
-
|
976 |
# Handle based on intent
|
977 |
if intent_data["intent"] == "API_ACTION" and intent_data.get("requires_backend", False):
|
978 |
# Handle API-based actions
|
@@ -980,7 +963,7 @@ class HealthcareChatbot:
|
|
980 |
action_result = self.handle_api_action(user_message, detected_language, sentiment_result, keywords)
|
981 |
|
982 |
# print(action_result)
|
983 |
-
|
984 |
response = ChatResponse(
|
985 |
response_id=f"resp_{int(time.time())}",
|
986 |
response_type="api_action",
|
@@ -989,12 +972,12 @@ class HealthcareChatbot:
|
|
989 |
api_data=json.dumps(action_result["api_data"]) if 'action_result' in action_result else None,
|
990 |
language=detected_language
|
991 |
)
|
992 |
-
|
993 |
else:
|
994 |
# Handle conversational responses
|
995 |
print("💬 Processing conversational response...")
|
996 |
conv_response = self.handle_conversation(user_message, detected_language, sentiment_result)
|
997 |
-
|
998 |
response = ChatResponse(
|
999 |
response_id=f"resp_{int(time.time())}",
|
1000 |
response_type="conversation",
|
@@ -1002,17 +985,17 @@ class HealthcareChatbot:
|
|
1002 |
api_call_made=False,
|
1003 |
language=detected_language
|
1004 |
)
|
1005 |
-
|
1006 |
# Add to conversation history
|
1007 |
self.add_to_history(user_message, response.message, response.response_type)
|
1008 |
-
|
1009 |
print(f"⏱️ Processing time: {time.time() - start_time:.2f}s")
|
1010 |
return response
|
1011 |
-
|
1012 |
except Exception as e:
|
1013 |
print(f"❌ Error in chat processing: {e}")
|
1014 |
error_msg = "I apologize for the technical issue. Please try again. / أعتذر عن المشكلة التقنية. يرجى المحاولة مرة أخرى."
|
1015 |
-
|
1016 |
return ChatResponse(
|
1017 |
response_id=f"resp_{int(time.time())}",
|
1018 |
response_type="conversation",
|
@@ -1024,41 +1007,39 @@ class HealthcareChatbot:
|
|
1024 |
def start_interactive_chat(self):
|
1025 |
"""Start an interactive chat session"""
|
1026 |
print("🚀 Starting interactive chat session...")
|
1027 |
-
|
1028 |
while True:
|
1029 |
try:
|
1030 |
# Get user input
|
1031 |
user_input = input("\n👤 You: ").strip()
|
1032 |
-
|
1033 |
if not user_input:
|
1034 |
continue
|
1035 |
-
|
1036 |
# Process the message
|
1037 |
print("🤖 Processing...")
|
1038 |
response = self.chat(user_input)
|
1039 |
-
|
1040 |
# Display response
|
1041 |
print(f"\n🏥 Healthcare Bot: {response.message}")
|
1042 |
-
|
1043 |
# Show additional info if API call was made
|
1044 |
if response.api_call_made and response.api_data:
|
1045 |
if "error" not in response.api_data:
|
1046 |
print("✅ Successfully retrieved information from healthcare system")
|
1047 |
else:
|
1048 |
print("⚠️ There was an issue accessing the healthcare system")
|
1049 |
-
|
1050 |
# Check for exit
|
1051 |
if "Goodbye" in response.message or "وداعاً" in response.message:
|
1052 |
break
|
1053 |
-
|
1054 |
except KeyboardInterrupt:
|
1055 |
print("\n\n👋 Chat session ended. Goodbye!")
|
1056 |
break
|
1057 |
except Exception as e:
|
1058 |
print(f"\n❌ Unexpected error: {e}")
|
1059 |
print("The chat session will continue...")
|
1060 |
-
|
1061 |
-
|
1062 |
# Create a simple function to start the chatbot
|
1063 |
# def start_healthcare_chatbot():
|
1064 |
# """Initialize and start the healthcare chatbot"""
|
|
|
73 |
self.user_id = 'e2fe5655-d6ac-447d-8237-850413288561'
|
74 |
self.max_retries = 3
|
75 |
self.retry_delay = 2
|
76 |
+
|
77 |
# Store conversation history
|
78 |
self.conversation_history = []
|
79 |
self.max_history_length = 10 # Keep last 10 exchanges
|
80 |
+
|
81 |
# Initialize components
|
82 |
self._initialize_language_tools()
|
83 |
self._initialize_llm()
|
84 |
self._initialize_parsers_and_chains()
|
85 |
self._initialize_date_parser()
|
86 |
+
|
87 |
print("Healthcare Chatbot initialized successfully!")
|
88 |
self._print_welcome_message()
|
89 |
|
|
|
111 |
try:
|
112 |
self.embeddings = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-large")
|
113 |
self.language_classifier = pipeline(
|
114 |
+
"text-classification",
|
115 |
model="papluca/xlm-roberta-base-language-detection",
|
116 |
top_k=1
|
117 |
)
|
|
|
156 |
|
157 |
# Intent classification prompt
|
158 |
self.intent_classifier_template = PromptTemplate(
|
159 |
+
template="""
|
160 |
+
You are an intent classifier. Your job is simple: understand what the user wants and check if any API endpoint can do that.
|
161 |
|
162 |
+
User Message: {user_query}
|
163 |
+
Language: {detected_language}
|
164 |
+
API Endpoints: {endpoints_documentation}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
165 |
|
166 |
+
Think step by step:
|
167 |
|
168 |
+
1. What does the user want from this message?
|
169 |
+
Read the user's message carefully. What is the user trying to say or accomplish? What would a human understand from this message?
|
170 |
|
171 |
+
2. Can any API endpoint fulfill what the user wants?
|
172 |
+
Look at each API endpoint. Does any endpoint do what the user is asking for? Be very precise - only say yes if there's a clear match.
|
173 |
|
174 |
+
Important rules:
|
175 |
+
- Focus ONLY on the current message, ignore conversation history for classification
|
176 |
+
- If the user is just talking, being social, or saying something casual, that's CONVERSATION
|
177 |
+
- Only choose API_ACTION if the user is clearly asking for something an API endpoint can do
|
178 |
+
- When you're not sure, choose CONVERSATION
|
179 |
|
180 |
+
Answer in this format:
|
181 |
+
{{
|
182 |
+
"intent": "API_ACTION" or "CONVERSATION",
|
183 |
+
"confidence": [0.0 to 1.0],
|
184 |
+
"reasoning": "What does the user want? Can any API do this?",
|
185 |
+
"requires_backend": true or false
|
186 |
+
}}
|
187 |
+
""",
|
188 |
+
input_variables=["user_query", "detected_language", "conversation_history", "endpoints_documentation"]
|
189 |
+
)
|
190 |
|
|
|
|
|
191 |
|
|
|
|
|
|
|
|
|
|
|
|
|
192 |
|
193 |
+
|
194 |
+
# API routing prompt (reuse existing router_prompt_template)
|
195 |
+
self.router_prompt_template = PromptTemplate(
|
196 |
+
template="""
|
197 |
+
You are an expert API routing assistant with deep analytical capabilities. Your role is to thoroughly understand user intentions and precisely match them with the most appropriate API endpoint after comprehensive analysis.
|
198 |
+
|
199 |
+
=== CURRENT CONTEXT ===
|
200 |
+
Current Date/Time: {current_datetime}
|
201 |
+
Current Timezone: {timezone}
|
202 |
+
User Location/Locale: {user_locale}
|
203 |
+
|
204 |
+
=== AVAILABLE ENDPOINTS ===
|
205 |
+
{endpoints_documentation}
|
206 |
+
|
207 |
+
=== USER REQUEST DETAILS ===
|
208 |
+
User Query: {user_query}
|
209 |
+
Detected Language: {detected_language}
|
210 |
+
Extracted Keywords: {extracted_keywords}
|
211 |
+
Sentiment Analysis: {sentiment_analysis}
|
212 |
+
Conversation History: {conversation_history}
|
213 |
+
|
214 |
+
=== COMPREHENSIVE ANALYSIS PROCESS ===
|
215 |
+
|
216 |
+
PHASE 1: DEEP QUERY UNDERSTANDING
|
217 |
+
Thoroughly analyze the user's request by examining:
|
218 |
+
- PRIMARY INTENT: What is the core action the user wants to perform?
|
219 |
+
- SECONDARY INTENTS: Are there multiple actions or follow-up implications?
|
220 |
+
- ENTITY IDENTIFICATION: What specific resources/objects are mentioned?
|
221 |
+
- OPERATION TYPE: Create, Read, Update, Delete, Search, List, Filter, Aggregate?
|
222 |
+
- IMPLICIT CONTEXT: What assumptions or unstated requirements exist?
|
223 |
+
- URGENCY/PRIORITY: Is this time-sensitive or routine?
|
224 |
+
- SCOPE: Single item, multiple items, or system-wide operation?
|
225 |
+
|
226 |
+
PHASE 2: ENDPOINTS DOCUMENTATION COMPREHENSION
|
227 |
+
For EACH endpoint in the documentation, analyze:
|
228 |
+
- PURPOSE AND FUNCTIONALITY: What business problem does this endpoint solve?
|
229 |
+
- REQUIRED VS OPTIONAL PARAMETERS: What data is absolutely necessary?
|
230 |
+
- INPUT DATA TYPES AND FORMATS: How should data be structured?
|
231 |
+
- EXPECTED OUTPUT: What will this endpoint return?
|
232 |
+
- USE CASES: When would this endpoint be the best choice?
|
233 |
+
- CONSTRAINTS AND LIMITATIONS: What are the boundaries of this endpoint?
|
234 |
+
- RELATIONSHIP TO OTHER ENDPOINTS: How does it fit in the overall API ecosystem?
|
235 |
+
|
236 |
+
PHASE 3: SEMANTIC MATCHING AND SCORING
|
237 |
+
For each potential endpoint match:
|
238 |
+
- INTENT ALIGNMENT SCORE (0-100): How well does the endpoint's purpose match user intent?
|
239 |
+
- PARAMETER AVAILABILITY SCORE (0-100): How much required data can be extracted/inferred?
|
240 |
+
- CONTEXT RELEVANCE SCORE (0-100): How well does this fit the conversation context?
|
241 |
+
- OPERATIONAL COMPLEXITY SCORE (0-100): How straightforward is this operation?
|
242 |
+
- TOTAL MATCH SCORE: Weighted average of above scores
|
243 |
+
|
244 |
+
PHASE 4: TEMPORAL AND CONTEXTUAL PROCESSING
|
245 |
+
Handle time-related expressions with precision:
|
246 |
+
- RELATIVE TIME CONVERSION:
|
247 |
+
* Arabic: اليوم/today, غدا/tomorrow, أمس/yesterday, بعد/after, قبل/before
|
248 |
+
* English: today, tomorrow, yesterday, next week, in 2 hours, etc.
|
249 |
+
* Context-aware: "next appointment", "usual time", "same day"
|
250 |
+
- ABSOLUTE TIME PARSING:
|
251 |
+
* Date formats: DD/MM/YYYY, MM-DD-YYYY, YYYY-MM-DD
|
252 |
+
* Time formats: 12-hour (AM/PM), 24-hour, relative (morning/afternoon)
|
253 |
+
- TIMEZONE CONSIDERATIONS:
|
254 |
+
* Convert all times to user's timezone
|
255 |
+
* Account for daylight saving time if applicable
|
256 |
+
* Provide ISO 8601 formatted output
|
257 |
+
|
258 |
+
PHASE 5: PARAMETER EXTRACTION AND VALIDATION
|
259 |
+
Extract and validate ALL parameters:
|
260 |
+
- DIRECT EXTRACTION: Explicitly mentioned values in user query
|
261 |
+
- CONTEXTUAL INFERENCE: Values derivable from conversation history
|
262 |
+
- TYPE CONVERSION: Ensure proper data types (string, int, float, datetime, boolean)
|
263 |
+
- FORMAT VALIDATION: Check against endpoint requirements
|
264 |
+
- DEFAULT VALUES: Apply sensible defaults for optional parameters
|
265 |
+
- MISSING PARAMETER IDENTIFICATION: Clearly identify what's missing and why it's needed
|
266 |
+
|
267 |
+
PHASE 6: DECISION VALIDATION AND CONFIDENCE ASSESSMENT
|
268 |
+
Before finalizing the decision:
|
269 |
+
- SANITY CHECK: Does this endpoint actually solve the user's problem?
|
270 |
+
- ALTERNATIVE CONSIDERATION: Are there better endpoint options?
|
271 |
+
- COMPLETENESS VERIFICATION: Are all required parameters available or obtainable?
|
272 |
+
- EDGE CASE EVALUATION: What could go wrong with this choice?
|
273 |
+
- CONFIDENCE CALCULATION: How certain are you about this routing decision?
|
274 |
+
|
275 |
+
=== RESPONSE FORMAT ===
|
276 |
+
Provide your comprehensive analysis and decision in this exact JSON structure:
|
277 |
+
|
278 |
+
{{
|
279 |
+
"reasoning": {{
|
280 |
+
"user_intent": "Detailed description of what the user wants to accomplish, including primary and secondary goals",
|
281 |
+
"temporal_analysis": "How dates/times were interpreted and converted with step-by-step calculations",
|
282 |
+
"context_used": "Any context from conversation history that was applied, including entity persistence and implicit references",
|
283 |
+
"endpoint_analysis": "Thorough evaluation of why each potential endpoint was considered or rejected, with scoring rationale",
|
284 |
+
"selected_endpoint": "Detailed explanation of why this specific endpoint was chosen over all others",
|
285 |
+
"parameter_mapping": "Comprehensive explanation of how user query maps to endpoint parameters, including extraction methods and confidence levels"
|
286 |
+
}},
|
287 |
+
"endpoint": "/exact_endpoint_path_from_documentation",
|
288 |
+
"method": "HTTP_METHOD",
|
289 |
+
"params": {{
|
290 |
+
"required_param_1": "extracted_or_converted_value",
|
291 |
+
"required_param_2": "extracted_or_converted_value",
|
292 |
+
"optional_param": "value_if_applicable"
|
293 |
+
}},
|
294 |
+
"missing_required": ["list", "of", "missing", "required", "parameters"],
|
295 |
+
"confidence": 0.95
|
296 |
+
}}
|
297 |
+
|
298 |
+
=== CRITICAL EXCELLENCE STANDARDS ===
|
299 |
+
1. **ACCURACY FIRST**: Only route to endpoints that exist in the documentation
|
300 |
+
2. **NO FABRICATION**: Never assume parameters or capabilities not explicitly documented
|
301 |
+
3. **COMPLETE UNDERSTANDING**: Demonstrate deep comprehension of both query and endpoints
|
302 |
+
4. **TEMPORAL PRECISION**: Handle all time expressions with mathematical accuracy
|
303 |
+
5. **CONTEXT MASTERY**: Leverage conversation history intelligently and completely
|
304 |
+
6. **MULTILINGUAL COMPETENCY**: Handle Arabic, English, and mixed-language queries flawlessly
|
305 |
+
7. **PARAMETER COMPLETENESS**: Account for every required parameter or clearly identify gaps
|
306 |
+
8. **CONFIDENCE HONESTY**: Provide realistic confidence scores with clear reasoning
|
307 |
+
9. **EDGE CASE AWARENESS**: Consider what could go wrong and prepare for it
|
308 |
+
10. **DECISION TRANSPARENCY**: Make your reasoning process completely clear and auditable
|
309 |
+
|
310 |
+
=== ADVANCED CONTEXT HANDLING ===
|
311 |
+
- **Pronoun Resolution**: "Cancel it" → identify what "it" refers to from history
|
312 |
+
- **Implicit References**: "Same time tomorrow" → extract time from previous context
|
313 |
+
- **Entity Persistence**: Remember patient IDs, appointment IDs across conversation turns
|
314 |
+
- **Intent Continuation**: Understand when user is continuing a previous request
|
315 |
+
- **Correction Handling**: Detect when user is correcting previous information
|
316 |
+
|
317 |
+
Think methodically through each phase. Demonstrate deep understanding before making decisions. Be thorough in your analysis and transparent in your reasoning.
|
318 |
+
""",
|
319 |
+
input_variables=[
|
320 |
+
"endpoints_documentation", "user_query", "detected_language",
|
321 |
+
"extracted_keywords", "sentiment_analysis", "conversation_history",
|
322 |
+
"current_datetime", "timezone", "user_locale"
|
323 |
+
]
|
324 |
+
)
|
325 |
# self.router_prompt_template = PromptTemplate(
|
326 |
# template="""
|
327 |
# You are a precise API routing assistant. Your job is to analyze user queries and select the correct API endpoint with proper parameters.
|
|
|
448 |
# API response formatting prompt (reuse existing user_response_template)
|
449 |
# self.user_response_template = PromptTemplate(
|
450 |
# template="""
|
451 |
+
# You are a professional healthcare assistant. Answer the user's question using the provided API data.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
452 |
|
|
|
453 |
# User Query: {user_query}
|
454 |
# User Sentiment: {sentiment_analysis}
|
455 |
+
# Response Language: {detected_language}
|
456 |
|
457 |
+
# API Response Data:
|
458 |
# {api_response}
|
459 |
|
460 |
+
# === INSTRUCTIONS ===
|
461 |
+
|
462 |
+
# 1. Read and understand the API response data above
|
463 |
+
# 2. Use ONLY the actual data from the API response - never make up information
|
464 |
+
# 3. Respond in {detected_language} language only
|
465 |
+
# 4. Write like you're talking to a friend or family member - warm, friendly, and caring
|
466 |
+
# 5. Make it sound natural and conversational, not like a system message
|
467 |
+
# 6. Convert technical data to simple, everyday language
|
468 |
+
|
469 |
+
# === DATE AND TIME FORMATTING ===
|
470 |
+
|
471 |
+
# When you see date_time fields like '2025-05-30T10:28:10':
|
472 |
+
# - For English: Convert to "May 30, 2025 at 10:28 AM"
|
473 |
+
# - For Arabic: Convert to "٣٠ مايو ٢٠٢٥ في الساعة ١٠:٢٨ صباحاً"
|
474 |
+
|
475 |
+
# === RESPONSE EXAMPLES ===
|
476 |
+
|
477 |
+
# For appointment confirmations:
|
478 |
+
# - English: "Great! I've got your appointment set up for May 30, 2025 at 10:28 AM. Everything looks good!"
|
479 |
+
# - Arabic: "ممتاز! موعدك محجوز يوم ٣٠ مايو ٢٠٢٥ الساعة ١٠:٢٨ صباحاً. كل شيء جاهز!"
|
480 |
+
|
481 |
+
# For appointment info:
|
482 |
+
# - English: "Your next appointment is on May 30, 2025 at 10:28 AM. See you then!"
|
483 |
+
# - Arabic: "موعدك القادم يوم ٣٠ مايو ٢٠٢٥ الساعة ١٠:٢٨ صباحاً. نراك قريباً!"
|
484 |
+
|
485 |
+
# === TONE GUIDELINES ===
|
486 |
+
# - Use friendly words like: "Great!", "Perfect!", "All set!", "ممتاز!", "رائع!", "تمام!"
|
487 |
+
# - Add reassuring phrases: "Everything looks good", "You're all set", "كل شيء جاهز", "تم بنجاح"
|
488 |
+
# - Sound helpful and caring, not robotic or formal
|
489 |
+
|
490 |
+
# === LANGUAGE FORMATTING ===
|
491 |
+
|
492 |
+
# For Arabic responses:
|
493 |
+
# - Use Arabic numerals: ٠١٢٣٤٥٦٧٨٩
|
494 |
+
# - Use Arabic month names: يناير، فبراير، مارس، أبريل، مايو، يونيو، يوليو، أغسطس، سبتمبر، أكتوبر، نوفمبر، ديسمبر
|
495 |
+
# - Friendly, warm Arabic tone
|
496 |
+
|
497 |
+
# For English responses:
|
498 |
+
# - Use standard English numerals
|
499 |
+
# - 12-hour time format with AM/PM
|
500 |
+
# - Friendly, conversational English tone
|
501 |
|
502 |
# === CRITICAL RULES ===
|
503 |
+
# - Extract dates and times exactly as they appear in the API response
|
504 |
+
# - Never use example dates or placeholder information
|
505 |
+
# - Respond only in the specified language
|
506 |
+
# - Make your response sound like a helpful friend, not a computer
|
507 |
+
# - Focus on answering the user's specific question with warmth and care
|
508 |
+
|
509 |
+
# Generate a friendly, helpful response using the API data provided above.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
510 |
# """,
|
511 |
# input_variables=["user_query", "api_response", "detected_language", "sentiment_analysis"]
|
512 |
# )
|
513 |
self.user_response_template = PromptTemplate(
|
514 |
+
template="""
|
515 |
+
You are a professional healthcare assistant. Your task is to carefully analyze the API data and respond to the user's question accurately.
|
516 |
+
|
517 |
+
User Query: {user_query}
|
518 |
+
User Sentiment: {sentiment_analysis}
|
519 |
+
Response Language: {detected_language}
|
520 |
+
|
521 |
+
API Response Data:
|
522 |
+
{api_response}
|
523 |
+
|
524 |
+
=== CRITICAL INSTRUCTIONS ===
|
525 |
+
|
526 |
+
1. FIRST: Carefully read and analyze the API response data above
|
527 |
+
2. SECOND: Identify all date_time fields in the format 'YYYY-MM-DDTHH:MM:SS'
|
528 |
+
3. THIRD: Extract the EXACT dates and times from the API response - DO NOT use any example dates
|
529 |
+
4. FOURTH: Convert these extracted dates to the user-friendly format specified below
|
530 |
+
5. FIFTH: Respond ONLY in {detected_language} language
|
531 |
+
6. Use a warm, friendly, conversational tone like talking to a friend
|
532 |
+
|
533 |
+
=== DATE EXTRACTION AND CONVERSION ===
|
534 |
+
|
535 |
+
Step 1: Find date_time fields in the API response (format: 'YYYY-MM-DDTHH:MM:SS')
|
536 |
+
Step 2: Convert ONLY the actual extracted dates using these rules:
|
537 |
+
|
538 |
+
For English:
|
539 |
+
- Convert 'YYYY-MM-DDTHH:MM:SS' to readable format
|
540 |
+
- Example: '2025-06-01T08:00:00' becomes "June 1, 2025 at 8:00 AM"
|
541 |
+
- Use 12-hour format with AM/PM
|
542 |
+
|
543 |
+
For Arabic:
|
544 |
+
- Convert to Arabic numerals and month names
|
545 |
+
- Example: '2025-06-01T08:00:00' becomes "١ يونيو ٢٠٢٥ في الساعة ٨:٠٠ صباحاً"
|
546 |
+
- Arabic months: يناير، فبراير، مارس، أبريل، مايو، يونيو، يوليو، أغسطس، سبتمبر، أكتوبر، نوفمبر، ديسمبر
|
547 |
+
- Arabic numerals: ٠١٢٣٤٥٦٧٨٩
|
548 |
+
|
549 |
+
=== RESPONSE APPROACH ===
|
550 |
+
|
551 |
+
1. Analyze what the user is asking for
|
552 |
+
2. Find the relevant information in the API response
|
553 |
+
3. Extract actual dates/times from the API data
|
554 |
+
4. Convert technical information to simple language
|
555 |
+
5. Respond warmly and helpfully
|
556 |
+
|
557 |
+
=== TONE AND LANGUAGE ===
|
558 |
+
|
559 |
+
English responses:
|
560 |
+
- Use phrases like: "Great!", "Perfect!", "All set!", "Here's what I found:"
|
561 |
+
- Be conversational and reassuring
|
562 |
+
|
563 |
+
Arabic responses:
|
564 |
+
- Use phrases like: "ممتاز!", "رائع!", "تمام!", "إليك ما وجدته:"
|
565 |
+
- Be warm and helpful in Arabic style
|
566 |
+
|
567 |
+
=== IMPORTANT REMINDERS ===
|
568 |
+
- NEVER use example dates from this prompt
|
569 |
+
- ALWAYS extract dates from the actual API response data
|
570 |
+
- If no dates exist in API response, don't mention any dates
|
571 |
+
- Stay focused on answering the user's specific question
|
572 |
+
- Use only information that exists in the API response
|
573 |
+
|
574 |
+
Now, carefully analyze the API response above and generate a helpful response to the user's query using ONLY the actual data provided.
|
575 |
+
""",
|
576 |
+
input_variables=["user_query", "api_response", "detected_language", "sentiment_analysis"]
|
577 |
+
)
|
578 |
|
579 |
# Create chains
|
580 |
self.intent_chain = LLMChain(llm=self.llm, prompt=self.intent_classifier_template)
|
581 |
self.router_chain = LLMChain(llm=self.llm, prompt=self.router_prompt_template)
|
582 |
self.conversation_chain = LLMChain(llm=self.llm, prompt=self.conversation_template)
|
583 |
self.api_response_chain = LLMChain(llm=self.llm, prompt=self.user_response_template)
|
584 |
+
|
585 |
def detect_language(self, text):
|
586 |
"""Detect language of the input text"""
|
587 |
if self.language_classifier and len(text.strip()) > 3:
|
|
|
589 |
result = self.language_classifier(text)
|
590 |
detected_lang = result[0][0]['label']
|
591 |
confidence = result[0][0]['score']
|
592 |
+
|
593 |
if detected_lang in ['ar', 'arabic']:
|
594 |
return "arabic"
|
595 |
elif detected_lang in ['en', 'english']:
|
|
|
598 |
return "english" # Default to English for unsupported languages
|
599 |
except:
|
600 |
pass
|
601 |
+
|
602 |
# Fallback: Basic Arabic detection
|
603 |
arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]+')
|
604 |
if arabic_pattern.search(text):
|
605 |
return "arabic"
|
606 |
+
|
607 |
return "english"
|
608 |
|
609 |
def analyze_sentiment(self, text):
|
|
|
617 |
}
|
618 |
except:
|
619 |
pass
|
620 |
+
|
621 |
return {"sentiment": "NEUTRAL", "score": 0.5}
|
622 |
|
623 |
def extract_keywords(self, text):
|
|
|
633 |
"""Get recent conversation history as context"""
|
634 |
if not self.conversation_history:
|
635 |
return "No previous conversation"
|
636 |
+
|
637 |
context = []
|
638 |
for item in self.conversation_history[-3:]: # Last 3 exchanges
|
639 |
context.append(f"User: {item['user_message']}")
|
640 |
context.append(f"Bot: {item['bot_response'][:100]}...") # Truncate long responses
|
641 |
+
|
642 |
return " | ".join(context)
|
643 |
|
644 |
def add_to_history(self, user_message, bot_response, response_type):
|
|
|
649 |
'bot_response': bot_response,
|
650 |
'response_type': response_type
|
651 |
})
|
652 |
+
|
653 |
# Keep only recent history
|
654 |
if len(self.conversation_history) > self.max_history_length:
|
655 |
self.conversation_history = self.conversation_history[-self.max_history_length:]
|
|
|
663 |
"conversation_history": self.get_conversation_context(),
|
664 |
"endpoints_documentation": json.dumps(self.endpoints_documentation, indent=2)
|
665 |
})
|
666 |
+
|
667 |
# Parse the JSON response
|
668 |
intent_text = result["text"]
|
669 |
# Clean and parse JSON
|
670 |
cleaned_response = re.sub(r'//.*?$', '', intent_text, flags=re.MULTILINE)
|
671 |
cleaned_response = re.sub(r'/\*.*?\*/', '', cleaned_response, flags=re.DOTALL)
|
672 |
cleaned_response = re.sub(r',(\s*[}\]])', r'\1', cleaned_response)
|
673 |
+
|
674 |
try:
|
675 |
intent_data = json.loads(cleaned_response)
|
676 |
return intent_data
|
|
|
706 |
"sentiment_analysis": json.dumps(sentiment_result),
|
707 |
"conversation_history": self.get_conversation_context()
|
708 |
})
|
709 |
+
|
710 |
return result["text"].strip()
|
711 |
+
|
712 |
except Exception as e:
|
713 |
# Fallback response
|
714 |
if detected_language == "arabic":
|
|
|
724 |
|
725 |
print('Sending the api request')
|
726 |
print(f"🔗 Making API call to {endpoint_method} {self.BASE_URL + endpoint_url} with params: {endpoint_params}")
|
727 |
+
|
728 |
# Inject patient_id if needed
|
729 |
if 'patient_id' in endpoint_params:
|
730 |
endpoint_params['patient_id'] = self.user_id
|
731 |
+
|
732 |
retries = 0
|
733 |
+
response = None
|
734 |
while retries < self.max_retries:
|
735 |
try:
|
736 |
if endpoint_method.upper() == 'GET':
|
|
|
748 |
headers=self.headers,
|
749 |
timeout=10
|
750 |
)
|
751 |
+
|
752 |
response.raise_for_status()
|
753 |
+
print('Backend Response : ', response.json())
|
754 |
return response.json()
|
755 |
+
|
756 |
except requests.exceptions.RequestException as e:
|
757 |
retries += 1
|
758 |
if retries >= self.max_retries:
|
|
|
761 |
"details": str(e),
|
762 |
"status_code": getattr(e.response, 'status_code', None) if hasattr(e, 'response') else None
|
763 |
}
|
764 |
+
|
765 |
time.sleep(self.retry_delay)
|
766 |
def parse_relative_date(self, text, detected_language):
|
767 |
"""
|
768 |
Parse relative dates from text using a combination of methods
|
769 |
"""
|
770 |
today = datetime.now()
|
771 |
+
|
772 |
# Handle common relative date patterns in English and Arabic
|
773 |
tomorrow_patterns = {
|
774 |
'english': [r'\btomorrow\b', r'\bnext day\b'],
|
775 |
'arabic': [r'\bغدا\b', r'\bبكرة\b', r'\bغدًا\b', r'\bالغد\b']
|
776 |
}
|
777 |
+
|
778 |
next_week_patterns = {
|
779 |
'english': [r'\bnext week\b'],
|
780 |
'arabic': [r'\bالأسبوع القادم\b', r'\bالأسبوع المقبل\b', r'\bالاسبوع الجاي\b']
|
781 |
}
|
782 |
+
|
783 |
# Check for "tomorrow" patterns
|
784 |
for pattern in tomorrow_patterns.get(detected_language, []) + tomorrow_patterns.get('english', []):
|
785 |
if re.search(pattern, text, re.IGNORECASE):
|
786 |
return (today + timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S')
|
787 |
+
|
788 |
# Check for "next week" patterns
|
789 |
for pattern in next_week_patterns.get(detected_language, []) + next_week_patterns.get('english', []):
|
790 |
if re.search(pattern, text, re.IGNORECASE):
|
791 |
return (today + timedelta(days=7)).strftime('%Y-%m-%dT%H:%M:%S')
|
792 |
+
|
793 |
# If NER model is available, use it to extract date entities
|
794 |
if self.date_parser and detected_language == 'english':
|
795 |
try:
|
|
|
803 |
return (today + timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S')
|
804 |
except Exception as e:
|
805 |
print(f"Error in date parsing: {e}")
|
806 |
+
|
807 |
# Default return None if no date pattern is recognized
|
808 |
return None
|
809 |
|
|
|
811 |
def handle_api_action(self, user_query, detected_language, sentiment_result, keywords):
|
812 |
"""Handle API-based actions"""
|
813 |
try:
|
814 |
+
|
815 |
+
# parsed_date = self.parse_relative_date(user_query, detected_language)
|
816 |
+
# if parsed_date:
|
817 |
+
# print(f"Parsed relative date: {parsed_date}")
|
818 |
+
|
819 |
# Route the query to determine API endpoint
|
820 |
router_result = self.router_chain.invoke({
|
821 |
"endpoints_documentation": json.dumps(self.endpoints_documentation, indent=2),
|
|
|
823 |
"detected_language": detected_language,
|
824 |
"extracted_keywords": ", ".join(keywords),
|
825 |
"sentiment_analysis": json.dumps(sentiment_result),
|
826 |
+
"conversation_history": self.get_conversation_context(),
|
827 |
+
"current_datetime": datetime.now().strftime('%Y-%m-%dT%H:%M:%S'),
|
828 |
+
"timezone": "UTC",
|
829 |
+
"user_locale": "en-US"
|
830 |
})
|
831 |
+
|
832 |
# Parse router response
|
833 |
route_text = router_result["text"]
|
834 |
# cleaned_response = re.sub(r'//.*?$', '', route_text, flags=re.MULTILINE)
|
835 |
# cleaned_response = re.sub(r'/\*.*?\*/', '', cleaned_response, flags=re.DOTALL)
|
836 |
# cleaned_response = re.sub(r',(\s*[}\]])', r'\1', cleaned_response)
|
837 |
+
|
838 |
# try:
|
839 |
# parsed_route = json.loads(cleaned_response)
|
840 |
# except json.JSONDecodeError:
|
|
|
843 |
# parsed_route = json.loads(json_match.group(0))
|
844 |
# else:
|
845 |
# raise ValueError("Could not parse routing response")
|
846 |
+
|
847 |
# print(f"🔍 Parsed route: {parsed_route}")
|
848 |
cleaned_response = route_text
|
849 |
+
|
850 |
# Remove any comments (both single-line and multi-line)
|
851 |
cleaned_response = re.sub(r'//.*?$', '', cleaned_response, flags=re.MULTILINE)
|
852 |
cleaned_response = re.sub(r'/\*.*?\*/', '', cleaned_response, flags=re.DOTALL)
|
853 |
+
|
854 |
# Remove any trailing commas
|
855 |
cleaned_response = re.sub(r',(\s*[}\]])', r'\1', cleaned_response)
|
856 |
+
|
857 |
# Try different methods to parse the JSON response
|
858 |
try:
|
859 |
# First attempt: direct JSON parsing of cleaned response
|
|
|
879 |
print(f"Failed to parse JSON. Raw response: {route_text}")
|
880 |
print(f"Cleaned response: {cleaned_response}")
|
881 |
raise ValueError("Could not extract valid JSON from LLM response")
|
882 |
+
|
883 |
if not parsed_route:
|
884 |
raise ValueError("Failed to parse LLM response into valid JSON")
|
885 |
+
|
886 |
# Replace any placeholder values and inject parsed dates if available
|
887 |
if 'params' in parsed_route:
|
888 |
if 'patient_id' in parsed_route['params']:
|
889 |
parsed_route['params']['patient_id'] = self.user_id
|
890 |
+
|
891 |
# Inject parsed date if available and a date parameter exists
|
892 |
+
# date_params = ['appointment_date', 'date', 'schedule_date', 'date_time', 'new_date_time']
|
893 |
+
# if parsed_date:
|
894 |
+
# for param in date_params:
|
895 |
+
# if param in parsed_route['params']:
|
896 |
+
# parsed_route['params'][param] = parsed_date
|
897 |
+
|
898 |
print('Parsed route: ', parsed_route)
|
899 |
|
900 |
# Make backend API call
|
901 |
api_response = self.backend_call(parsed_route)
|
902 |
+
|
903 |
print("🔗 API response received:", api_response)
|
904 |
# Generate user-friendly response
|
905 |
user_response_result = self.api_response_chain.invoke({
|
|
|
910 |
})
|
911 |
|
912 |
print("🔗 API response:", user_response_result["text"].strip())
|
913 |
+
|
914 |
return {
|
915 |
"response": user_response_result["text"].strip(),
|
916 |
"api_data": api_response,
|
917 |
"routing_info": parsed_route
|
918 |
}
|
919 |
+
|
920 |
except Exception as e:
|
921 |
# Fallback error response
|
922 |
if detected_language == "arabic":
|
923 |
error_msg = "أعتذر، لم أتمكن من معالجة طلبك. يرجى المحاولة مرة أخرى أو صياغة السؤال بطريقة مختلفة."
|
924 |
else:
|
925 |
error_msg = "I apologize, I couldn't process your request. Please try again or rephrase your question."
|
926 |
+
|
927 |
return {
|
928 |
"response": error_msg,
|
929 |
"api_data": {"error": str(e)},
|
|
|
933 |
def chat(self, user_message: str) -> ChatResponse:
|
934 |
"""Main chat method that handles user messages"""
|
935 |
start_time = time.time()
|
936 |
+
|
937 |
# Check for exit commands
|
938 |
exit_commands = ['quit', 'exit', 'bye', 'خروج', 'وداعا', 'مع السلامة']
|
939 |
if user_message.lower().strip() in exit_commands:
|
|
|
943 |
message="Goodbye! Take care of your health! / وداعاً! اعتن بصحتك!",
|
944 |
language="bilingual"
|
945 |
)
|
946 |
+
|
947 |
try:
|
948 |
# Language detection and analysis
|
949 |
detected_language = self.detect_language(user_message)
|
950 |
sentiment_result = self.analyze_sentiment(user_message)
|
951 |
keywords = self.extract_keywords(user_message)
|
952 |
+
|
953 |
print(f"🔍 Language: {detected_language} | Sentiment: {sentiment_result['sentiment']} | Keywords: {keywords}")
|
954 |
+
|
955 |
# Classify intent
|
956 |
intent_data = self.classify_intent(user_message, detected_language)
|
957 |
print(f"🎯 Intent: {intent_data['intent']} (confidence: {intent_data.get('confidence', 'N/A')})")
|
958 |
+
|
959 |
# Handle based on intent
|
960 |
if intent_data["intent"] == "API_ACTION" and intent_data.get("requires_backend", False):
|
961 |
# Handle API-based actions
|
|
|
963 |
action_result = self.handle_api_action(user_message, detected_language, sentiment_result, keywords)
|
964 |
|
965 |
# print(action_result)
|
966 |
+
|
967 |
response = ChatResponse(
|
968 |
response_id=f"resp_{int(time.time())}",
|
969 |
response_type="api_action",
|
|
|
972 |
api_data=json.dumps(action_result["api_data"]) if 'action_result' in action_result else None,
|
973 |
language=detected_language
|
974 |
)
|
975 |
+
|
976 |
else:
|
977 |
# Handle conversational responses
|
978 |
print("💬 Processing conversational response...")
|
979 |
conv_response = self.handle_conversation(user_message, detected_language, sentiment_result)
|
980 |
+
|
981 |
response = ChatResponse(
|
982 |
response_id=f"resp_{int(time.time())}",
|
983 |
response_type="conversation",
|
|
|
985 |
api_call_made=False,
|
986 |
language=detected_language
|
987 |
)
|
988 |
+
|
989 |
# Add to conversation history
|
990 |
self.add_to_history(user_message, response.message, response.response_type)
|
991 |
+
|
992 |
print(f"⏱️ Processing time: {time.time() - start_time:.2f}s")
|
993 |
return response
|
994 |
+
|
995 |
except Exception as e:
|
996 |
print(f"❌ Error in chat processing: {e}")
|
997 |
error_msg = "I apologize for the technical issue. Please try again. / أعتذر عن المشكلة التقنية. يرجى المحاولة مرة أخرى."
|
998 |
+
|
999 |
return ChatResponse(
|
1000 |
response_id=f"resp_{int(time.time())}",
|
1001 |
response_type="conversation",
|
|
|
1007 |
def start_interactive_chat(self):
|
1008 |
"""Start an interactive chat session"""
|
1009 |
print("🚀 Starting interactive chat session...")
|
1010 |
+
|
1011 |
while True:
|
1012 |
try:
|
1013 |
# Get user input
|
1014 |
user_input = input("\n👤 You: ").strip()
|
1015 |
+
|
1016 |
if not user_input:
|
1017 |
continue
|
1018 |
+
|
1019 |
# Process the message
|
1020 |
print("🤖 Processing...")
|
1021 |
response = self.chat(user_input)
|
1022 |
+
|
1023 |
# Display response
|
1024 |
print(f"\n🏥 Healthcare Bot: {response.message}")
|
1025 |
+
|
1026 |
# Show additional info if API call was made
|
1027 |
if response.api_call_made and response.api_data:
|
1028 |
if "error" not in response.api_data:
|
1029 |
print("✅ Successfully retrieved information from healthcare system")
|
1030 |
else:
|
1031 |
print("⚠️ There was an issue accessing the healthcare system")
|
1032 |
+
|
1033 |
# Check for exit
|
1034 |
if "Goodbye" in response.message or "وداعاً" in response.message:
|
1035 |
break
|
1036 |
+
|
1037 |
except KeyboardInterrupt:
|
1038 |
print("\n\n👋 Chat session ended. Goodbye!")
|
1039 |
break
|
1040 |
except Exception as e:
|
1041 |
print(f"\n❌ Unexpected error: {e}")
|
1042 |
print("The chat session will continue...")
|
|
|
|
|
1043 |
# Create a simple function to start the chatbot
|
1044 |
# def start_healthcare_chatbot():
|
1045 |
# """Initialize and start the healthcare chatbot"""
|