abdibrahem commited on
Commit
4d047b2
·
1 Parent(s): 24135cd

Add new logic

Browse files
Files changed (2) hide show
  1. main.py +452 -658
  2. old.py +1146 -0
main.py CHANGED
@@ -27,10 +27,6 @@ import numpy as np
27
  from endpoints_documentation import endpoints_documentation
28
 
29
  # Set environment variables for HuggingFace
30
- # if os.name == 'posix' and os.uname().sysname == 'Darwin': # Check if running on macOS
31
- # os.environ["HF_HOME"] = os.path.expanduser("~/Library/Caches/huggingface")
32
- # os.environ["TRANSFORMERS_CACHE"] = os.path.expanduser("~/Library/Caches/huggingface/transformers")
33
- # else:
34
  os.environ["HF_HOME"] = "/tmp/huggingface"
35
  os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1"
36
 
@@ -55,12 +51,15 @@ class ChatResponse(BaseModel):
55
  timestamp: datetime = Field(default_factory=datetime.now, description="When the response was generated")
56
 
57
 
58
- class EndpointRequest(BaseModel):
59
- """Data model for API endpoint requests"""
60
- endpoint: str = Field(..., description="The API endpoint path to call")
61
- method: str = Field(..., description="The HTTP method to use (GET or POST)")
62
- params: Dict[str, Any] = Field(default_factory=dict, description="Parameters for the API call")
63
- missing_required: List[str] = Field(default_factory=list, description="Any required parameters that are missing")
 
 
 
64
 
65
 
66
  class HealthcareChatbot:
@@ -68,22 +67,22 @@ class HealthcareChatbot:
68
  self.endpoints_documentation = endpoints_documentation
69
  self.ollama_base_url = "http://localhost:11434"
70
  self.model_name = "gemma3"
71
- self.BASE_URL = 'https://f376-197-54-54-66.ngrok-free.app'
72
  self.headers = {'Content-type': 'application/json'}
73
- self.user_id = '86639f4c-5dfc-441d-b229-084f0fcdd748'
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 +110,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
  )
@@ -151,277 +150,132 @@ class HealthcareChatbot:
151
  )
152
 
153
  def _initialize_parsers_and_chains(self):
154
- """Initialize all prompt templates and chains"""
155
- self.json_parser = JsonOutputParser(pydantic_object=EndpointRequest)
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
- self.intent_classifier_template = PromptTemplate(
192
  template="""
193
- You are a strict intent classification system. Your only task is to determine if the user message requires an API action or is general conversation.
 
 
194
 
195
- === ABSOLUTE RULES ===
196
- 1. OUTPUT FORMAT MUST BE EXACTLY:
197
- {{
198
- "intent": "API_ACTION" or "CONVERSATION",
199
- "confidence": 0.0-1.0,
200
- "reasoning": "clear justification",
201
- "requires_backend": true or false
202
- }}
203
- 2. Never invent custom intent types
204
- 3. Never output endpoint names in the intent field
205
- 4. "requires_backend" must match the intent (true for API_ACTION)
206
-
207
- === CLASSIFICATION CRITERIA ===
208
- API_ACTION must meet ALL of:
209
- - The message contains a clear, actionable request
210
- - The request matches a documented API endpoint's purpose
211
- - The request requires specific backend functionality
212
-
213
- CONVERSATION applies when:
214
- - The message is social/greeting/smalltalk
215
- - The request is too vague for API action
216
- - No API endpoint matches the request
217
-
218
- === INPUT DATA ===
219
- User Message: {user_query}
220
- Detected Language: {detected_language}
221
- API Endpoints: {endpoints_documentation}
222
 
223
  === DECISION PROCESS ===
224
- 1. Analyze the message literally - what is the explicit request?
225
- 2. Check endpoints documentation - is there an exact functional match?
226
- 3. If uncertain, default to CONVERSATION
227
- 4. Validate against rules before responding
228
-
229
- === OUTPUT VALIDATION ===
230
- Before responding, verify:
231
- - Intent is ONLY "API_ACTION" or "CONVERSATION"
232
- - Confidence reflects certainty (1.0 = perfect match)
233
- - Reasoning explains the endpoint match (for API_ACTION)
234
- - requires_backend aligns with intent
235
-
236
- Respond ONLY in the exact specified format.
237
- """,
238
- input_variables=["user_query", "detected_language", "conversation_history", "endpoints_documentation"]
239
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
 
242
 
243
- # API routing prompt (reuse existing router_prompt_template)
244
- self.router_prompt_template = PromptTemplate(
245
- template="""
246
- You are a precise API routing assistant. Your job is to analyze user queries and select the correct API endpoint with proper parameters.
247
-
248
- === ENDPOINT DOCUMENTATION ===
249
- {endpoints_documentation}
250
-
251
- === USER REQUEST ANALYSIS ===
252
- User Query: {user_query}
253
- Language: {detected_language}
254
- Keywords: {extracted_keywords}
255
- Sentiment: {sentiment_analysis}
256
- Current Context:
257
- - DateTime: {current_datetime}
258
- - Timezone: {timezone}
259
- - User Locale: {user_locale}
260
-
261
- === ROUTING PROCESS ===
262
- Follow these steps in order:
263
-
264
- STEP 1: INTENT ANALYSIS
265
- - What is the user trying to accomplish?
266
- - What type of operation are they requesting? (create, read, update, delete, search, etc.)
267
- - What entity/resource are they working with?
268
-
269
- STEP 2: DATE/TIME PROCESSING
270
- - Identify any temporal expressions in the user query
271
- - Convert relative dates/times using the current context:
272
- * "اليوم" (today) = current date
273
- * "غدا" (tomorrow) = current date + 1 day
274
- * "أمس" (yesterday) = current date - 1 day
275
- * "الأسبوع القادم" (next week) = current date + 7 days
276
- * "بعد ساعتين" (in 2 hours) = current time + 2 hours
277
- * "صباحًا" (morning/AM), "مساءً" (evening/PM)
278
- - Handle different date formats and languages
279
- - Account for timezone differences
280
- - Convert to ISO 8601 format: YYYY-MM-DDTHH:MM:SS
281
-
282
- STEP 3: ENDPOINT MATCHING
283
- - Review each endpoint in the documentation
284
- - Match the user's intent to the endpoint's PURPOSE/DESCRIPTION
285
- - Consider the HTTP method (GET for retrieval, POST for creation, etc.)
286
- - Verify the endpoint can handle the user's specific request
287
-
288
- STEP 4: PARAMETER EXTRACTION
289
- - Identify ALL required parameters from the endpoint documentation
290
- - Extract parameter values from the user query
291
- - Convert data types as needed:
292
- - Dates/times to ISO 8601 format (YYYY-MM-DDTHH:mm:ss)
293
- - Numbers to integers
294
- - Set appropriate defaults for optional parameters if beneficial
295
 
296
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
 
298
- STEP 5: VALIDATION
299
- - Ensure ALL required parameters are provided or identified as missing
300
- - Verify parameter formats match documentation requirements
301
- - Check that the selected endpoint actually solves the user's problem
302
-
303
- === RESPONSE FORMAT ===
304
- Provide your analysis and decision in this exact JSON structure:
305
-
306
- {{
307
- "reasoning": {{
308
- "user_intent": "Brief description of what the user wants to accomplish",
309
- "selected_endpoint": "Why this endpoint was chosen over others",
310
- "parameter_mapping": "How user query maps to endpoint parameters"
311
- }},
312
- "endpoint": "/exact_endpoint_path_from_documentation",
313
- "method": "HTTP_METHOD",
314
- "params": {{
315
- "required_param_1": "extracted_or_converted_value",
316
- "required_param_2": "extracted_or_converted_value",
317
- "optional_param": "value_if_applicable"
318
- }},
319
- "missing_required": ["list", "of", "missing", "required", "parameters"],
320
- "confidence": 0.95
321
- }}
322
-
323
- === CRITICAL RULES ===
324
- 1. ONLY select endpoints that exist in the provided documentation
325
- 2. NEVER fabricate or assume endpoint parameters not in documentation
326
- 3. ALL required parameters MUST be included or listed as missing
327
- 4. Convert dates/times to ISO 8601 format (YYYY-MM-DDTHH:mm:ss)
328
- 5. If patient_id is required and not provided, add it to missing_required
329
- 6. Match endpoints by PURPOSE, not just keywords in the path
330
- 7. If multiple endpoints could work, choose the most specific one
331
- 8. If no endpoint matches, set endpoint to null and explain in reasoning
332
-
333
- === EXAMPLES OF GOOD MATCHING ===
334
- - User wants "patient records" → Use patient retrieval endpoint, not general search
335
- - User wants to "schedule appointment" → Use appointment creation endpoint
336
- - User asks "what appointments today" → Use appointment listing with date filter
337
- - User wants to "update medication" → Use medication update endpoint with patient_id
338
-
339
- Think step by step and be precise with your endpoint selection and parameter extraction.:""",
340
- input_variables=["endpoints_documentation", "user_query", "detected_language",
341
- "extracted_keywords", "sentiment_analysis", "conversation_history",
342
- "current_datetime", "timezone", "user_locale"]
343
  )
344
- # old one
345
- # self.router_prompt_template = PromptTemplate(
346
- # template="""
347
- # You are a precise API routing assistant. Your job is to analyze user queries and select the correct API endpoint with proper parameters.
348
-
349
- # === ENDPOINT DOCUMENTATION ===
350
- # {endpoints_documentation}
351
-
352
- # === USER REQUEST ANALYSIS ===
353
- # User Query: {user_query}
354
- # Language: {detected_language}
355
- # Keywords: {extracted_keywords}
356
- # Sentiment: {sentiment_analysis}
357
-
358
- # === ROUTING PROCESS ===
359
- # Follow these steps in order:
360
-
361
- # STEP 1: INTENT ANALYSIS
362
- # - What is the user trying to accomplish?
363
- # - What type of operation are they requesting? (create, read, update, delete, search, etc.)
364
- # - What entity/resource are they working with?
365
-
366
- # STEP 2: ENDPOINT MATCHING
367
- # - Review each endpoint in the documentation
368
- # - Match the user's intent to the endpoint's PURPOSE/DESCRIPTION
369
- # - Consider the HTTP method (GET for retrieval, POST for creation, etc.)
370
- # - Verify the endpoint can handle the user's specific request
371
-
372
- # STEP 3: PARAMETER EXTRACTION
373
- # - Identify ALL required parameters from the endpoint documentation
374
- # - Extract parameter values from the user query
375
- # - Convert data types as needed (dates to ISO 8601, numbers to integers, etc.)
376
- # - Set appropriate defaults for optional parameters if beneficial
377
-
378
- # STEP 4: VALIDATION
379
- # - Ensure ALL required parameters are provided or identified as missing
380
- # - Verify parameter formats match documentation requirements
381
- # - Check that the selected endpoint actually solves the user's problem
382
-
383
- # === RESPONSE FORMAT ===
384
- # Provide your analysis and decision in this exact JSON structure:
385
-
386
- # {{
387
- # "reasoning": {{
388
- # "user_intent": "Brief description of what the user wants to accomplish",
389
- # "selected_endpoint": "Why this endpoint was chosen over others",
390
- # "parameter_mapping": "How user query maps to endpoint parameters"
391
- # }},
392
- # "endpoint": "/exact_endpoint_path_from_documentation",
393
- # "method": "HTTP_METHOD",
394
- # "params": {{
395
- # "required_param_1": "extracted_or_converted_value",
396
- # "required_param_2": "extracted_or_converted_value",
397
- # "optional_param": "value_if_applicable"
398
- # }},
399
- # "missing_required": ["list", "of", "missing", "required", "parameters"],
400
- # "confidence": 0.95
401
- # }}
402
-
403
- # === CRITICAL RULES ===
404
- # 1. ONLY select endpoints that exist in the provided documentation
405
- # 2. NEVER fabricate or assume endpoint parameters not in documentation
406
- # 3. ALL required parameters MUST be included or listed as missing
407
- # 4. Convert dates/times to ISO 8601 format (YYYY-MM-DDTHH:MM:SS)
408
- # 5. If patient_id is required and not provided, add it to missing_required
409
- # 6. Match endpoints by PURPOSE, not just keywords in the path
410
- # 7. If multiple endpoints could work, choose the most specific one
411
- # 8. If no endpoint matches, set endpoint to null and explain in reasoning
412
-
413
- # === EXAMPLES OF GOOD MATCHING ===
414
- # - User wants "patient records" → Use patient retrieval endpoint, not general search
415
- # - User wants to "schedule appointment" → Use appointment creation endpoint
416
- # - User asks "what appointments today" → Use appointment listing with date filter
417
- # - User wants to "update medication" → Use medication update endpoint with patient_id
418
-
419
- # Think step by step and be precise with your endpoint selection and parameter extraction.:""",
420
- # input_variables=["endpoints_documentation", "user_query", "detected_language",
421
- # "extracted_keywords", "sentiment_analysis", "conversation_history"]
422
- # )
423
-
424
- # Conversational response prompt
425
  self.conversation_template = PromptTemplate(
426
  template="""
427
  You are a friendly and professional healthcare chatbot assistant.
@@ -465,8 +319,8 @@ class HealthcareChatbot:
465
  input_variables=["user_query", "detected_language", "sentiment_analysis", "conversation_history"]
466
  )
467
 
468
- # API response formatting prompt (reuse existing user_response_template)
469
- self.user_response_template = PromptTemplate(
470
  template="""
471
  You are a professional healthcare assistant. Answer the user's question using the provided API data.
472
 
@@ -478,7 +332,7 @@ class HealthcareChatbot:
478
  {api_response}
479
 
480
  === INSTRUCTIONS ===
481
-
482
  1. Read and understand the API response data above
483
  2. Use ONLY the actual data from the API response - never make up information
484
  3. Respond in {detected_language} language only
@@ -487,13 +341,13 @@ class HealthcareChatbot:
487
  6. Convert technical data to simple, everyday language
488
 
489
  === DATE AND TIME FORMATTING ===
490
-
491
  When you see date_time fields like '2025-05-30T10:28:10':
492
- - For English: Convert to "May 30, 2025 at 10:28 AM"
493
  - For Arabic: Convert to "٣٠ مايو ٢٠٢٥ في الساعة ١٠:٢٨ صباحاً"
494
 
495
  === RESPONSE EXAMPLES ===
496
-
497
  For appointment confirmations:
498
  - English: "Great! I've got your appointment set up for May 30, 2025 at 10:28 AM. Everything looks good!"
499
  - Arabic: "ممتاز! موعدك محجوز يوم ٣٠ مايو ٢٠٢٥ الساعة ١٠:٢٨ صباحاً. كل شيء جاهز!"
@@ -508,13 +362,13 @@ class HealthcareChatbot:
508
  - Sound helpful and caring, not robotic or formal
509
 
510
  === LANGUAGE FORMATTING ===
511
-
512
  For Arabic responses:
513
  - Use Arabic numerals: ٠١٢٣٤٥٦٧٨٩
514
  - Use Arabic month names: يناير، فبراير، مارس، أبريل، مايو، يونيو، يوليو، أغسطس، سبتمبر، أكتوبر، نوفمبر، ديسمبر
515
  - Friendly, warm Arabic tone
516
 
517
- For English responses:
518
  - Use standard English numerals
519
  - 12-hour time format with AM/PM
520
  - Friendly, conversational English tone
@@ -530,77 +384,11 @@ class HealthcareChatbot:
530
  """,
531
  input_variables=["user_query", "api_response", "detected_language", "sentiment_analysis"]
532
  )
533
- # self.user_response_template = PromptTemplate(
534
- # template="""
535
- # You are a professional healthcare assistant. Your task is to carefully analyze the API data and respond to the user's question accurately.
536
-
537
- # User Query: {user_query}
538
- # User Sentiment: {sentiment_analysis}
539
- # Response Language: {detected_language}
540
-
541
- # API Response Data:
542
- # {api_response}
543
-
544
- # === CRITICAL INSTRUCTIONS ===
545
-
546
- # 1. FIRST: Carefully read and analyze the API response data above
547
- # 2. SECOND: Identify all date_time fields in the format 'YYYY-MM-DDTHH:MM:SS'
548
- # 3. THIRD: Extract the EXACT dates and times from the API response - DO NOT use any example dates
549
- # 4. FOURTH: Convert these extracted dates to the user-friendly format specified below
550
- # 5. FIFTH: Respond ONLY in {detected_language} language
551
- # 6. Use a warm, friendly, conversational tone like talking to a friend
552
-
553
- # === DATE EXTRACTION AND CONVERSION ===
554
-
555
- # Step 1: Find date_time fields in the API response (format: 'YYYY-MM-DDTHH:MM:SS')
556
- # Step 2: Convert ONLY the actual extracted dates using these rules:
557
-
558
- # For English:
559
- # - Convert 'YYYY-MM-DDTHH:MM:SS' to readable format
560
- # - Example: '2025-06-01T08:00:00' becomes "June 1, 2025 at 8:00 AM"
561
- # - Use 12-hour format with AM/PM
562
-
563
- # For Arabic:
564
- # - Convert to Arabic numerals and month names
565
- # - Example: '2025-06-01T08:00:00' becomes "١ يونيو ٢٠٢٥ في الساعة ٨:٠٠ صباحاً"
566
- # - Arabic months: يناير، فبراير، مارس، أبريل، مايو، يونيو، يوليو، أغسطس، سبتمبر، أكتوبر، نوفمبر، ديسمبر
567
- # - Arabic numerals: ٠١٢٣٤٥٦٧٨٩
568
-
569
- # === RESPONSE APPROACH ===
570
-
571
- # 1. Analyze what the user is asking for
572
- # 2. Find the relevant information in the API response
573
- # 3. Extract actual dates/times from the API data
574
- # 4. Convert technical information to simple language
575
- # 5. Respond warmly and helpfully
576
-
577
- # === TONE AND LANGUAGE ===
578
-
579
- # English responses:
580
- # - Use phrases like: "Great!", "Perfect!", "All set!", "Here's what I found:"
581
- # - Be conversational and reassuring
582
-
583
- # Arabic responses:
584
- # - Use phrases like: "ممتاز!", "رائع!", "تمام!", "إليك ما وجدته:"
585
- # - Be warm and helpful in Arabic style
586
-
587
- # === IMPORTANT REMINDERS ===
588
- # - NEVER use example dates from this prompt
589
- # - ALWAYS extract dates from the actual API response data
590
- # - If no dates exist in API response, don't mention any dates
591
- # - Stay focused on answering the user's specific question
592
- # - Use only information that exists in the API response
593
-
594
- # Now, carefully analyze the API response above and generate a helpful response to the user's query using ONLY the actual data provided.
595
- # """,
596
- # input_variables=["user_query", "api_response", "detected_language", "sentiment_analysis"]
597
- # )
598
-
599
- # Create chains
600
- self.intent_chain = LLMChain(llm=self.llm, prompt=self.intent_classifier_template)
601
  self.router_chain = LLMChain(llm=self.llm, prompt=self.router_prompt_template)
602
  self.conversation_chain = LLMChain(llm=self.llm, prompt=self.conversation_template)
603
- self.api_response_chain = LLMChain(llm=self.llm, prompt=self.user_response_template)
604
 
605
  def detect_language(self, text):
606
  """Detect language of the input text"""
@@ -609,7 +397,7 @@ class HealthcareChatbot:
609
  result = self.language_classifier(text)
610
  detected_lang = result[0][0]['label']
611
  confidence = result[0][0]['score']
612
-
613
  if detected_lang in ['ar', 'arabic']:
614
  return "arabic"
615
  elif detected_lang in ['en', 'english']:
@@ -618,12 +406,12 @@ class HealthcareChatbot:
618
  return "english" # Default to English for unsupported languages
619
  except:
620
  pass
621
-
622
  # Fallback: Basic Arabic detection
623
  arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]+')
624
  if arabic_pattern.search(text):
625
  return "arabic"
626
-
627
  return "english"
628
 
629
  def analyze_sentiment(self, text):
@@ -637,7 +425,7 @@ class HealthcareChatbot:
637
  }
638
  except:
639
  pass
640
-
641
  return {"sentiment": "NEUTRAL", "score": 0.5}
642
 
643
  def extract_keywords(self, text):
@@ -653,12 +441,12 @@ class HealthcareChatbot:
653
  """Get recent conversation history as context"""
654
  if not self.conversation_history:
655
  return "No previous conversation"
656
-
657
  context = []
658
  for item in self.conversation_history[-3:]: # Last 3 exchanges
659
  context.append(f"User: {item['user_message']}")
660
  context.append(f"Bot: {item['bot_response'][:100]}...") # Truncate long responses
661
-
662
  return " | ".join(context)
663
 
664
  def add_to_history(self, user_message, bot_response, response_type):
@@ -669,52 +457,126 @@ class HealthcareChatbot:
669
  'bot_response': bot_response,
670
  'response_type': response_type
671
  })
672
-
673
  # Keep only recent history
674
  if len(self.conversation_history) > self.max_history_length:
675
  self.conversation_history = self.conversation_history[-self.max_history_length:]
676
 
677
- def classify_intent(self, user_query, detected_language):
678
- """Classify if the user query requires API action or is conversational"""
679
- try:
680
- result = self.intent_chain.invoke({
681
- "user_query": user_query,
682
- "detected_language": detected_language,
683
- "conversation_history": self.get_conversation_context(),
684
- "endpoints_documentation": json.dumps(self.endpoints_documentation, indent=2)
685
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
686
 
687
- # Parse the JSON response
688
- intent_text = result["text"]
689
- # Clean and parse JSON
690
- cleaned_response = re.sub(r'//.*?$', '', intent_text, flags=re.MULTILINE)
 
 
 
 
691
  cleaned_response = re.sub(r'/\*.*?\*/', '', cleaned_response, flags=re.DOTALL)
 
 
692
  cleaned_response = re.sub(r',(\s*[}\]])', r'\1', cleaned_response)
693
-
 
694
  try:
695
- intent_data = json.loads(cleaned_response)
696
- return intent_data
697
  except json.JSONDecodeError:
698
- # Try to extract JSON from the response
699
- json_match = re.search(r'\{.*?\}', cleaned_response, re.DOTALL)
700
- if json_match:
701
- intent_data = json.loads(json_match.group(0))
702
- return intent_data
703
- else:
704
- # Default classification if parsing fails
705
- return {
706
- "intent": "CONVERSATION",
707
- "confidence": 0.5,
708
- "reasoning": "Failed to parse LLM response",
709
- "requires_backend": False
710
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
711
  except Exception as e:
712
- print(f"Error in intent classification: {e}")
713
  return {
714
  "intent": "CONVERSATION",
715
  "confidence": 0.5,
716
- "reasoning": f"Error in classification: {str(e)}",
717
- "requires_backend": False
 
 
 
718
  }
719
 
720
  def handle_conversation(self, user_query, detected_language, sentiment_result):
@@ -726,9 +588,9 @@ class HealthcareChatbot:
726
  "sentiment_analysis": json.dumps(sentiment_result),
727
  "conversation_history": self.get_conversation_context()
728
  })
729
-
730
  return result["text"].strip()
731
-
732
  except Exception as e:
733
  # Fallback response
734
  if detected_language == "arabic":
@@ -742,13 +604,12 @@ class HealthcareChatbot:
742
  endpoint_method = data.get('method')
743
  endpoint_params = data.get('params', {}).copy()
744
 
745
- print('Sending the api request')
746
  print(f"🔗 Making API call to {endpoint_method} {self.BASE_URL + endpoint_url} with params: {endpoint_params}")
747
-
748
  # Inject patient_id if needed
749
  if 'patient_id' in endpoint_params:
750
  endpoint_params['patient_id'] = self.user_id
751
-
752
  retries = 0
753
  response = None
754
  while retries < self.max_retries:
@@ -768,11 +629,11 @@ class HealthcareChatbot:
768
  headers=self.headers,
769
  timeout=10
770
  )
771
-
772
  response.raise_for_status()
773
- print('Backend Response : ', response.json())
774
  return response.json()
775
-
776
  except requests.exceptions.RequestException as e:
777
  retries += 1
778
  if retries >= self.max_retries:
@@ -781,148 +642,33 @@ 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:
816
- date_entities = self.date_parser(text)
817
- for entity in date_entities:
818
- if entity['entity_group'] == 'DATE':
819
- # Here you would need more complex date parsing logic
820
- # This is just a placeholder
821
- print(f"Found date entity: {entity['word']}")
822
- # For now, just default to tomorrow if we detect any date
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
-
830
 
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
- # print(f"Parsed relative date: {parsed_date}")
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),
842
- "user_query": user_query,
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
- "current_datetime": datetime.now().strftime('%Y-%m-%dT%H:%M:%S'),
848
- "timezone": "UTC",
849
- "user_locale": "en-US"
850
- })
851
-
852
- # Parse router response
853
- route_text = router_result["text"]
854
- # cleaned_response = re.sub(r'//.*?$', '', route_text, flags=re.MULTILINE)
855
- # cleaned_response = re.sub(r'/\*.*?\*/', '', cleaned_response, flags=re.DOTALL)
856
- # cleaned_response = re.sub(r',(\s*[}\]])', r'\1', cleaned_response)
857
-
858
- # try:
859
- # parsed_route = json.loads(cleaned_response)
860
- # except json.JSONDecodeError:
861
- # json_match = re.search(r'\{.*?\}', cleaned_response, re.DOTALL)
862
- # if json_match:
863
- # parsed_route = json.loads(json_match.group(0))
864
- # else:
865
- # raise ValueError("Could not parse routing response")
866
-
867
- # print(f"🔍 Parsed route: {parsed_route}")
868
- cleaned_response = route_text
869
-
870
- # Remove any comments (both single-line and multi-line)
871
- cleaned_response = re.sub(r'//.*?$', '', cleaned_response, flags=re.MULTILINE)
872
- cleaned_response = re.sub(r'/\*.*?\*/', '', cleaned_response, flags=re.DOTALL)
873
-
874
- # Remove any trailing commas
875
- cleaned_response = re.sub(r',(\s*[}\]])', r'\1', cleaned_response)
876
-
877
- # Try different methods to parse the JSON response
878
- try:
879
- # First attempt: direct JSON parsing of cleaned response
880
- parsed_route = json.loads(cleaned_response)
881
- except json.JSONDecodeError:
882
- try:
883
- # Second attempt: extract JSON from markdown code block
884
- json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', cleaned_response, re.DOTALL)
885
- if json_match:
886
- parsed_route = json.loads(json_match.group(1))
887
- except (json.JSONDecodeError, AttributeError):
888
- try:
889
- # Third attempt: find JSON-like content using regex
890
- json_pattern = r'\{\s*"endpoint"\s*:.*?\}'
891
- json_match = re.search(json_pattern, cleaned_response, re.DOTALL)
892
- if json_match:
893
- json_str = json_match.group(0)
894
- # Additional cleaning for the extracted JSON
895
- json_str = re.sub(r'//.*?$', '', json_str, flags=re.MULTILINE)
896
- json_str = re.sub(r',(\s*[}\]])', r'\1', json_str)
897
- parsed_route = json.loads(json_str)
898
- except (json.JSONDecodeError, AttributeError):
899
- print(f"Failed to parse JSON. Raw response: {route_text}")
900
- print(f"Cleaned response: {cleaned_response}")
901
- raise ValueError("Could not extract valid JSON from LLM response")
902
-
903
- if not parsed_route:
904
- raise ValueError("Failed to parse LLM response into valid JSON")
905
-
906
- # Replace any placeholder values and inject parsed dates if available
907
- if 'params' in parsed_route:
908
- if 'patient_id' in parsed_route['params']:
909
- parsed_route['params']['patient_id'] = self.user_id
910
- else:
911
- parsed_route['params']['patient_id'] = self.user_id
912
-
913
  # Inject parsed date if available and a date parameter exists
914
- # date_params = ['appointment_date', 'date', 'schedule_date', 'date_time', 'new_date_time']
915
- # if parsed_date:
916
- # for param in date_params:
917
- # if param in parsed_route['params']:
918
- # parsed_route['params'][param] = parsed_date
919
-
920
- print('Parsed route: ', parsed_route)
 
 
 
921
 
922
  # Make backend API call
923
- api_response = self.backend_call(parsed_route)
924
-
925
  print("🔗 API response received:", api_response)
 
926
  # Generate user-friendly response
927
  user_response_result = self.api_response_chain.invoke({
928
  "user_query": user_query,
@@ -931,21 +677,21 @@ class HealthcareChatbot:
931
  "sentiment_analysis": json.dumps(sentiment_result),
932
  })
933
 
934
- print("🔗 API response:", user_response_result["text"].strip())
935
-
936
  return {
937
  "response": user_response_result["text"].strip(),
938
  "api_data": api_response,
939
- "routing_info": parsed_route
940
  }
941
-
942
  except Exception as e:
943
  # Fallback error response
944
  if detected_language == "arabic":
945
  error_msg = "أعتذر، لم أتمكن من معالجة طلبك. يرجى المحاولة مرة أخرى أو صياغة السؤال بطريقة مختلفة."
946
  else:
947
  error_msg = "I apologize, I couldn't process your request. Please try again or rephrase your question."
948
-
949
  return {
950
  "response": error_msg,
951
  "api_data": {"error": str(e)},
@@ -953,148 +699,202 @@ class HealthcareChatbot:
953
  }
954
 
955
  def chat(self, user_message: str) -> ChatResponse:
956
- """Main chat method that handles user messages"""
957
  start_time = time.time()
958
-
959
  # Check for exit commands
960
- exit_commands = ['quit', 'exit', 'bye', 'خروج', 'وداعا', 'مع السلامة']
961
- if user_message.lower().strip() in exit_commands:
962
- return ChatResponse(
963
- response_id=f"resp_{int(time.time())}",
964
- response_type="conversation",
965
- message="Goodbye! Take care of your health! / وداعاً! اعتن بصحتك!",
966
- language="bilingual"
967
- )
968
-
 
 
 
 
 
 
 
969
  try:
970
- # Language detection and analysis
 
 
 
 
971
  detected_language = self.detect_language(user_message)
972
  sentiment_result = self.analyze_sentiment(user_message)
973
  keywords = self.extract_keywords(user_message)
974
-
975
- print(f"🔍 Language: {detected_language} | Sentiment: {sentiment_result['sentiment']} | Keywords: {keywords}")
976
-
977
- # Classify intent
978
- intent_data = self.classify_intent(user_message, detected_language)
979
- print(f"🎯 Intent: {intent_data['intent']} (confidence: {intent_data.get('confidence', 'N/A')})")
980
-
981
- # Handle based on intent
982
- if intent_data["intent"] == "API_ACTION" and intent_data.get("requires_backend", False):
983
- # Handle API-based actions
984
- print("🔗 Processing API action...")
985
- action_result = self.handle_api_action(user_message, detected_language, sentiment_result, keywords)
986
-
987
- # print(action_result)
988
-
989
- response = ChatResponse(
990
- response_id=f"resp_{int(time.time())}",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
991
  response_type="api_action",
992
- message=action_result["response"],
993
  api_call_made=True,
994
- api_data=json.dumps(action_result["api_data"]) if 'action_result' in action_result else None,
995
  language=detected_language
996
  )
997
-
998
  else:
999
- # Handle conversational responses
1000
- print("💬 Processing conversational response...")
1001
- conv_response = self.handle_conversation(user_message, detected_language, sentiment_result)
1002
-
1003
- response = ChatResponse(
1004
- response_id=f"resp_{int(time.time())}",
1005
  response_type="conversation",
1006
- message=conv_response,
1007
  api_call_made=False,
1008
  language=detected_language
1009
  )
1010
-
1011
- # Add to conversation history
1012
- self.add_to_history(user_message, response.message, response.response_type)
1013
-
1014
- print(f"⏱️ Processing time: {time.time() - start_time:.2f}s")
1015
- return response
1016
-
1017
  except Exception as e:
1018
- print(f"❌ Error in chat processing: {e}")
1019
- error_msg = "I apologize for the technical issue. Please try again. / أعتذر عن المشكلة التقنية. يرجى المحاولة مرة أخرى."
1020
-
 
 
 
 
 
 
1021
  return ChatResponse(
1022
- response_id=f"resp_{int(time.time())}",
1023
  response_type="conversation",
1024
- message=error_msg,
1025
  api_call_made=False,
1026
- language="bilingual"
1027
  )
 
 
 
 
1028
 
1029
- def start_interactive_chat(self):
1030
- """Start an interactive chat session"""
1031
- print("🚀 Starting interactive chat session...")
1032
-
1033
- while True:
1034
- try:
1035
- # Get user input
1036
- user_input = input("\n👤 You: ").strip()
1037
-
1038
- if not user_input:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1039
  continue
 
 
 
1040
 
1041
- # Process the message
1042
- print("🤖 Processing...")
1043
- response = self.chat(user_input)
1044
-
1045
- # Display response
1046
- print(f"\n🏥 Healthcare Bot: {response.message}")
1047
 
1048
- # Show additional info if API call was made
1049
- if response.api_call_made and response.api_data:
1050
- if "error" not in response.api_data:
1051
- print("✅ Successfully retrieved information from healthcare system")
1052
- else:
1053
- print("⚠️ There was an issue accessing the healthcare system")
1054
 
1055
- # Check for exit
1056
- if "Goodbye" in response.message or "وداعاً" in response.message:
1057
- break
1058
 
1059
- except KeyboardInterrupt:
1060
- print("\n\n👋 Chat session ended. Goodbye!")
1061
- break
1062
- except Exception as e:
1063
- print(f"\n❌ Unexpected error: {e}")
1064
- print("The chat session will continue...")
1065
- # Create a simple function to start the chatbot
1066
- # def start_healthcare_chatbot():
1067
- # """Initialize and start the healthcare chatbot"""
1068
  # try:
 
1069
  # chatbot = HealthcareChatbot()
1070
- # chatbot.start_interactive_chat()
 
 
 
1071
  # except Exception as e:
1072
- # print(f"Failed to start chatbot: {e}")
1073
- # print("Please check your Ollama installation and endpoint documentation.")
1074
 
1075
 
1076
- # Test the chatbot
1077
  # if __name__ == "__main__":
1078
- # You can test individual messages like this:
1079
- # chatbot = HealthcareChatbot()
1080
-
1081
- # Test conversational message
1082
- # print("\n=== TESTING CONVERSATIONAL MESSAGE ===")
1083
- # conv_response = chatbot.chat("Hello, how are you today?")
1084
- # print(f"Response: {conv_response.message}")
1085
- # print(f"Type: {conv_response.response_type}")
1086
-
1087
- # Test API action message
1088
- # print("\n=== TESTING API ACTION MESSAGE ===")
1089
- # api_response = chatbot.chat("I want to book an appointment tomorrow at 2 PM")
1090
- # print(f"Response: {api_response.message}")
1091
- # print(f"Type: {api_response.response_type}")
1092
- # print(f"API Called: {api_response.api_call_made}")
1093
-
1094
- # Start interactive session (uncomment to run)
1095
- # start_healthcare_chatbot()
1096
-
1097
- # Fast api section
1098
  from fastapi import FastAPI, HTTPException
1099
  from pydantic import BaseModel
1100
  from typing import Dict, Any, Optional
@@ -1112,12 +912,6 @@ agent = HealthcareChatbot()
1112
  class QueryRequest(BaseModel):
1113
  query: str
1114
 
1115
- class QueryResponse(BaseModel):
1116
- routing_info: Dict[str, Any]
1117
- api_response: Dict[str, Any]
1118
- user_friendly_response: str
1119
- detected_language: str
1120
- sentiment: Dict[str, Any]
1121
 
1122
  @app.post("/query")
1123
  async def process_query(request: QueryRequest):
@@ -1125,7 +919,7 @@ async def process_query(request: QueryRequest):
1125
  Process a user query and return a response
1126
  """
1127
  try:
1128
- response = agent.chat(request.query)
1129
  return response
1130
  except Exception as e:
1131
  raise HTTPException(status_code=500, detail=str(e))
 
27
  from endpoints_documentation import endpoints_documentation
28
 
29
  # Set environment variables for HuggingFace
 
 
 
 
30
  os.environ["HF_HOME"] = "/tmp/huggingface"
31
  os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1"
32
 
 
51
  timestamp: datetime = Field(default_factory=datetime.now, description="When the response was generated")
52
 
53
 
54
+ class RouterResponse(BaseModel):
55
+ """Data model for router chain response"""
56
+ intent: str = Field(..., description="Either 'API_ACTION' or 'CONVERSATION'")
57
+ confidence: float = Field(..., description="Confidence score between 0.0 and 1.0")
58
+ reasoning: str = Field(..., description="Explanation of the decision")
59
+ endpoint: Optional[str] = Field(default=None, description="API endpoint if intent is API_ACTION")
60
+ method: Optional[str] = Field(default=None, description="HTTP method if intent is API_ACTION")
61
+ params: Dict[str, Any] = Field(default_factory=dict, description="Parameters for API call")
62
+ missing_required: List[str] = Field(default_factory=list, description="Missing required parameters")
63
 
64
 
65
  class HealthcareChatbot:
 
67
  self.endpoints_documentation = endpoints_documentation
68
  self.ollama_base_url = "http://localhost:11434"
69
  self.model_name = "gemma3"
70
+ self.BASE_URL = 'https://d623-105-196-69-205.ngrok-free.app'
71
  self.headers = {'Content-type': 'application/json'}
72
+ self.user_id = 'e14761ad-c1ba-4a10-a814-c1b12bcdbb41'
73
  self.max_retries = 3
74
  self.retry_delay = 2
75
+
76
  # Store conversation history
77
  self.conversation_history = []
78
  self.max_history_length = 10 # Keep last 10 exchanges
79
+
80
  # Initialize components
81
  self._initialize_language_tools()
82
  self._initialize_llm()
83
  self._initialize_parsers_and_chains()
84
  self._initialize_date_parser()
85
+
86
  print("Healthcare Chatbot initialized successfully!")
87
  self._print_welcome_message()
88
 
 
110
  try:
111
  self.embeddings = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-large")
112
  self.language_classifier = pipeline(
113
+ "text-classification",
114
  model="papluca/xlm-roberta-base-language-detection",
115
  top_k=1
116
  )
 
150
  )
151
 
152
  def _initialize_parsers_and_chains(self):
153
+ """Initialize all prompt templates and chains - REVAMPED to 3 chains only"""
154
+ self.json_parser = JsonOutputParser(pydantic_object=RouterResponse)
155
+
156
+ # UNIFIED ROUTER CHAIN - Handles both intent classification AND API routing
157
+ self.router_prompt_template = PromptTemplate(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  template="""
159
+ You are an intelligent healthcare chatbot router. Your job is to:
160
+ 1. Understand what the user wants (intent classification)
161
+ 2. If they need API action, determine the exact endpoint and parameters
162
 
163
+ === CONTEXT ===
164
+ User Query: {user_query}
165
+ Language: {detected_language}
166
+ Keywords: {extracted_keywords}
167
+ Sentiment: {sentiment_analysis}
168
+ Conversation History: {conversation_history}
169
+
170
+ === API ENDPOINTS DOCUMENTATION ===
171
+ {endpoints_documentation}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
  === DECISION PROCESS ===
174
+
175
+ STEP 1: INTENT ANALYSIS
176
+ - What is the user trying to accomplish?
177
+ - Is this a casual conversation/greeting/general question? → CONVERSATION
178
+ - Is this a specific request that requires backend data/action? → API_ACTION
179
+
180
+ Common CONVERSATION intents:
181
+ - Greetings, thank you, goodbye
182
+ - General health questions (not personal data requests)
183
+ - Casual chat, complaints, compliments
184
+ - Questions about the bot's capabilities
185
+
186
+ Common API_ACTION intents:
187
+ - "Book an appointment", "Schedule appointment"
188
+ - "Show my appointments", "What are my appointments"
189
+ - "Cancel my appointment", "Reschedule appointment"
190
+ - "Show my medical records", "What's in my file"
191
+ - "Update my information"
192
+ - Any request for personal healthcare data or actions
193
+
194
+ STEP 2: IF API_ACTION - ENDPOINT SELECTION
195
+ - Review each endpoint in the documentation
196
+ - Match user intent to endpoint PURPOSE/DESCRIPTION
197
+ - Consider HTTP method (GET for retrieval, POST for creation)
198
+ - Extract and validate parameters
199
+
200
+ STEP 3: PARAMETER EXTRACTION (API_ACTION only)
201
+ - Identify ALL required parameters from endpoint documentation
202
+ - Extract values from user query
203
+ - Convert dates to ISO 8601 format (YYYY-MM-DDTHH:MM:SS)
204
+ - List any missing required parameters
205
+ - Add patient_id to params if endpoint requires it
206
+
207
+ === RESPONSE FORMAT ===
208
+ You MUST respond with valid JSON in this exact structure:
209
 
210
+ {{
211
+ "intent": "CONVERSATION" or "API_ACTION",
212
+ "confidence": 0.95,
213
+ "reasoning": "Brief explanation of why this intent was chosen and what the user wants",
214
+ "endpoint": "/endpoint/path" or null,
215
+ "method": "GET/POST/PUT/DELETE" or null,
216
+ "params": {{
217
+ "param1": "value1",
218
+ "param2": "value2"
219
+ }},
220
+ "missing_required": ["list", "of", "missing", "required", "params"]
221
+ }}
222
 
223
+ === EXAMPLES ===
224
 
225
+ User: "Hello, how are you?"
226
+ {{
227
+ "intent": "CONVERSATION",
228
+ "confidence": 0.98,
229
+ "reasoning": "User is greeting - casual conversation",
230
+ "endpoint": null,
231
+ "method": null,
232
+ "params": {{}},
233
+ "missing_required": []
234
+ }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
 
236
+ User: "I want to book an appointment for tomorrow"
237
+ {{
238
+ "intent": "API_ACTION",
239
+ "confidence": 0.95,
240
+ "reasoning": "User wants to schedule appointment - requires API call",
241
+ "endpoint": "/appointments",
242
+ "method": "POST",
243
+ "params": {{
244
+ "patient_id": "will_be_injected",
245
+ "appointment_date": "2025-06-02T10:00:00"
246
+ }},
247
+ "missing_required": []
248
+ }}
249
+
250
+ User: "What are my appointments?"
251
+ {{
252
+ "intent": "API_ACTION",
253
+ "confidence": 0.97,
254
+ "reasoning": "User wants to view their appointments - requires data retrieval",
255
+ "endpoint": "/appointments",
256
+ "method": "GET",
257
+ "params": {{
258
+ "patient_id": "will_be_injected"
259
+ }},
260
+ "missing_required": []
261
+ }}
262
 
263
+ === CRITICAL RULES ===
264
+ 1. ONLY use endpoints that exist in the provided documentation
265
+ 2. Focus on user's PRIMARY intent - what do they actually want?
266
+ 3. When in doubt between CONVERSATION and API_ACTION, lean toward CONVERSATION
267
+ 4. Always include confidence score based on clarity of intent
268
+ 5. For API_ACTION, endpoint and method must be specified
269
+ 6. Convert relative dates (tomorrow, next week) to specific ISO dates
270
+ 7. If multiple endpoints could work, choose the most specific one
271
+ 8. Missing required parameters should be listed even if you can't extract them
272
+
273
+ Analyze the user query and respond with the appropriate JSON:""",
274
+ input_variables=["user_query", "detected_language", "extracted_keywords",
275
+ "sentiment_analysis", "conversation_history", "endpoints_documentation"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  )
277
+
278
+ # CONVERSATION CHAIN - Handles conversational responses
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  self.conversation_template = PromptTemplate(
280
  template="""
281
  You are a friendly and professional healthcare chatbot assistant.
 
319
  input_variables=["user_query", "detected_language", "sentiment_analysis", "conversation_history"]
320
  )
321
 
322
+ # API RESPONSE CHAIN - Formats API responses for users
323
+ self.api_response_template = PromptTemplate(
324
  template="""
325
  You are a professional healthcare assistant. Answer the user's question using the provided API data.
326
 
 
332
  {api_response}
333
 
334
  === INSTRUCTIONS ===
335
+
336
  1. Read and understand the API response data above
337
  2. Use ONLY the actual data from the API response - never make up information
338
  3. Respond in {detected_language} language only
 
341
  6. Convert technical data to simple, everyday language
342
 
343
  === DATE AND TIME FORMATTING ===
344
+
345
  When you see date_time fields like '2025-05-30T10:28:10':
346
+ - For English: Convert to "May 30, 2025 at 10:28 AM"
347
  - For Arabic: Convert to "٣٠ مايو ٢٠٢٥ في الساعة ١٠:٢٨ صباحاً"
348
 
349
  === RESPONSE EXAMPLES ===
350
+
351
  For appointment confirmations:
352
  - English: "Great! I've got your appointment set up for May 30, 2025 at 10:28 AM. Everything looks good!"
353
  - Arabic: "ممتاز! موعدك محجوز يوم ٣٠ مايو ٢٠٢٥ الساعة ١٠:٢٨ صباحاً. كل شيء جاهز!"
 
362
  - Sound helpful and caring, not robotic or formal
363
 
364
  === LANGUAGE FORMATTING ===
365
+
366
  For Arabic responses:
367
  - Use Arabic numerals: ٠١٢٣٤٥٦٧٨٩
368
  - Use Arabic month names: يناير، فبراير، مارس، أبريل، مايو، يونيو، يوليو، أغسطس، سبتمبر، أكتوبر، نوفمبر، ديسمبر
369
  - Friendly, warm Arabic tone
370
 
371
+ For English responses:
372
  - Use standard English numerals
373
  - 12-hour time format with AM/PM
374
  - Friendly, conversational English tone
 
384
  """,
385
  input_variables=["user_query", "api_response", "detected_language", "sentiment_analysis"]
386
  )
387
+
388
+ # Create the 3 chains
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
  self.router_chain = LLMChain(llm=self.llm, prompt=self.router_prompt_template)
390
  self.conversation_chain = LLMChain(llm=self.llm, prompt=self.conversation_template)
391
+ self.api_response_chain = LLMChain(llm=self.llm, prompt=self.api_response_template)
392
 
393
  def detect_language(self, text):
394
  """Detect language of the input text"""
 
397
  result = self.language_classifier(text)
398
  detected_lang = result[0][0]['label']
399
  confidence = result[0][0]['score']
400
+
401
  if detected_lang in ['ar', 'arabic']:
402
  return "arabic"
403
  elif detected_lang in ['en', 'english']:
 
406
  return "english" # Default to English for unsupported languages
407
  except:
408
  pass
409
+
410
  # Fallback: Basic Arabic detection
411
  arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]+')
412
  if arabic_pattern.search(text):
413
  return "arabic"
414
+
415
  return "english"
416
 
417
  def analyze_sentiment(self, text):
 
425
  }
426
  except:
427
  pass
428
+
429
  return {"sentiment": "NEUTRAL", "score": 0.5}
430
 
431
  def extract_keywords(self, text):
 
441
  """Get recent conversation history as context"""
442
  if not self.conversation_history:
443
  return "No previous conversation"
444
+
445
  context = []
446
  for item in self.conversation_history[-3:]: # Last 3 exchanges
447
  context.append(f"User: {item['user_message']}")
448
  context.append(f"Bot: {item['bot_response'][:100]}...") # Truncate long responses
449
+
450
  return " | ".join(context)
451
 
452
  def add_to_history(self, user_message, bot_response, response_type):
 
457
  'bot_response': bot_response,
458
  'response_type': response_type
459
  })
460
+
461
  # Keep only recent history
462
  if len(self.conversation_history) > self.max_history_length:
463
  self.conversation_history = self.conversation_history[-self.max_history_length:]
464
 
465
+ def parse_relative_date(self, text, detected_language):
466
+ """Parse relative dates from text using a combination of methods"""
467
+ today = datetime.now()
468
+
469
+ # Handle common relative date patterns in English and Arabic
470
+ tomorrow_patterns = {
471
+ 'english': [r'\btomorrow\b', r'\bnext day\b'],
472
+ 'arabic': [r'\bغدا\b', r'\bبكرة\b', r'\bغدًا\b', r'\bالغد\b']
473
+ }
474
+
475
+ next_week_patterns = {
476
+ 'english': [r'\bnext week\b'],
477
+ 'arabic': [r'\bالأسبوع القادم\b', r'\bالأسبوع المقبل\b', r'\bالاسبوع الجاي\b']
478
+ }
479
+
480
+ # Check for "tomorrow" patterns
481
+ for pattern in tomorrow_patterns.get(detected_language, []) + tomorrow_patterns.get('english', []):
482
+ if re.search(pattern, text, re.IGNORECASE):
483
+ return (today + timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S')
484
+
485
+ # Check for "next week" patterns
486
+ for pattern in next_week_patterns.get(detected_language, []) + next_week_patterns.get('english', []):
487
+ if re.search(pattern, text, re.IGNORECASE):
488
+ return (today + timedelta(days=7)).strftime('%Y-%m-%dT%H:%M:%S')
489
+
490
+ # If NER model is available, use it to extract date entities
491
+ if self.date_parser and detected_language == 'english':
492
+ try:
493
+ date_entities = self.date_parser(text)
494
+ for entity in date_entities:
495
+ if entity['entity_group'] == 'DATE':
496
+ print(f"Found date entity: {entity['word']}")
497
+ # Default to tomorrow if we detect any date
498
+ return (today + timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S')
499
+ except Exception as e:
500
+ print(f"Error in date parsing: {e}")
501
+
502
+ # Default return None if no date pattern is recognized
503
+ return None
504
 
505
+ def parse_router_response(self, router_text):
506
+ """Parse the router chain response into structured data"""
507
+ try:
508
+ # Clean the response text
509
+ cleaned_response = router_text
510
+
511
+ # Remove any comments (both single-line and multi-line)
512
+ cleaned_response = re.sub(r'//.*?$', '', cleaned_response, flags=re.MULTILINE)
513
  cleaned_response = re.sub(r'/\*.*?\*/', '', cleaned_response, flags=re.DOTALL)
514
+
515
+ # Remove any trailing commas
516
  cleaned_response = re.sub(r',(\s*[}\]])', r'\1', cleaned_response)
517
+
518
+ # Try different methods to parse the JSON response
519
  try:
520
+ # First attempt: direct JSON parsing of cleaned response
521
+ parsed_response = json.loads(cleaned_response)
522
  except json.JSONDecodeError:
523
+ try:
524
+ # Second attempt: extract JSON from markdown code block
525
+ json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', cleaned_response, re.DOTALL)
526
+ if json_match:
527
+ parsed_response = json.loads(json_match.group(1))
528
+ else:
529
+ raise ValueError("No JSON found in code block")
530
+ except (json.JSONDecodeError, ValueError):
531
+ try:
532
+ # Third attempt: find JSON-like content using regex
533
+ json_pattern = r'\{\s*"intent"\s*:.*?\}'
534
+ json_match = re.search(json_pattern, cleaned_response, re.DOTALL)
535
+ if json_match:
536
+ json_str = json_match.group(0)
537
+ # Additional cleaning for the extracted JSON
538
+ json_str = re.sub(r'//.*?$', '', json_str, flags=re.MULTILINE)
539
+ json_str = re.sub(r',(\s*[}\]])', r'\1', json_str)
540
+ parsed_response = json.loads(json_str)
541
+ else:
542
+ raise ValueError("Could not extract JSON using regex")
543
+ except (json.JSONDecodeError, ValueError):
544
+ print(f"Failed to parse JSON. Raw response: {router_text}")
545
+ print(f"Cleaned response: {cleaned_response}")
546
+ # Return default conversation response on parse failure
547
+ return {
548
+ "intent": "CONVERSATION",
549
+ "confidence": 0.5,
550
+ "reasoning": "Failed to parse router response - defaulting to conversation",
551
+ "endpoint": None,
552
+ "method": None,
553
+ "params": {},
554
+ "missing_required": []
555
+ }
556
+
557
+ # Validate required fields and set defaults
558
+ validated_response = {
559
+ "intent": parsed_response.get("intent", "CONVERSATION"),
560
+ "confidence": parsed_response.get("confidence", 0.5),
561
+ "reasoning": parsed_response.get("reasoning", "Router decision"),
562
+ "endpoint": parsed_response.get("endpoint"),
563
+ "method": parsed_response.get("method"),
564
+ "params": parsed_response.get("params", {}),
565
+ "missing_required": parsed_response.get("missing_required", [])
566
+ }
567
+
568
+ return validated_response
569
+
570
  except Exception as e:
571
+ print(f"Error parsing router response: {e}")
572
  return {
573
  "intent": "CONVERSATION",
574
  "confidence": 0.5,
575
+ "reasoning": f"Parse error: {str(e)}",
576
+ "endpoint": None,
577
+ "method": None,
578
+ "params": {},
579
+ "missing_required": []
580
  }
581
 
582
  def handle_conversation(self, user_query, detected_language, sentiment_result):
 
588
  "sentiment_analysis": json.dumps(sentiment_result),
589
  "conversation_history": self.get_conversation_context()
590
  })
591
+
592
  return result["text"].strip()
593
+
594
  except Exception as e:
595
  # Fallback response
596
  if detected_language == "arabic":
 
604
  endpoint_method = data.get('method')
605
  endpoint_params = data.get('params', {}).copy()
606
 
 
607
  print(f"🔗 Making API call to {endpoint_method} {self.BASE_URL + endpoint_url} with params: {endpoint_params}")
608
+
609
  # Inject patient_id if needed
610
  if 'patient_id' in endpoint_params:
611
  endpoint_params['patient_id'] = self.user_id
612
+
613
  retries = 0
614
  response = None
615
  while retries < self.max_retries:
 
629
  headers=self.headers,
630
  timeout=10
631
  )
632
+
633
  response.raise_for_status()
634
+ print('Backend Response:', response.json())
635
  return response.json()
636
+
637
  except requests.exceptions.RequestException as e:
638
  retries += 1
639
  if retries >= self.max_retries:
 
642
  "details": str(e),
643
  "status_code": getattr(e.response, 'status_code', None) if hasattr(e, 'response') else None
644
  }
645
+
646
  time.sleep(self.retry_delay)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
647
 
648
+ def handle_api_action(self, user_query, detected_language, sentiment_result, keywords, router_data):
649
+ """Handle API-based actions using router data"""
650
  try:
651
+ # Parse relative dates and inject into parameters
652
+ parsed_date = self.parse_relative_date(user_query, detected_language)
653
+ if parsed_date:
654
+ print(f"Parsed relative date: {parsed_date}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
655
  # Inject parsed date if available and a date parameter exists
656
+ date_params = ['appointment_date', 'date', 'schedule_date', 'date_time', 'new_date_time']
657
+ for param in date_params:
658
+ if param in router_data['params']:
659
+ router_data['params'][param] = parsed_date
660
+
661
+ # Inject patient_id if needed
662
+ if 'patient_id' in router_data['params']:
663
+ router_data['params']['patient_id'] = self.user_id
664
+
665
+ print(f"🔍 Final API call data: {router_data}")
666
 
667
  # Make backend API call
668
+ api_response = self.backend_call(router_data)
669
+
670
  print("🔗 API response received:", api_response)
671
+
672
  # Generate user-friendly response
673
  user_response_result = self.api_response_chain.invoke({
674
  "user_query": user_query,
 
677
  "sentiment_analysis": json.dumps(sentiment_result),
678
  })
679
 
680
+ print("🔗 Final user response:", user_response_result["text"].strip())
681
+
682
  return {
683
  "response": user_response_result["text"].strip(),
684
  "api_data": api_response,
685
+ "routing_info": router_data
686
  }
687
+
688
  except Exception as e:
689
  # Fallback error response
690
  if detected_language == "arabic":
691
  error_msg = "أعتذر، لم أتمكن من معالجة طلبك. يرجى المحاولة مرة أخرى أو صياغة السؤال بطريقة مختلفة."
692
  else:
693
  error_msg = "I apologize, I couldn't process your request. Please try again or rephrase your question."
694
+
695
  return {
696
  "response": error_msg,
697
  "api_data": {"error": str(e)},
 
699
  }
700
 
701
  def chat(self, user_message: str) -> ChatResponse:
702
+ """Main chat method that handles user messages - REVAMPED to use 3 chains"""
703
  start_time = time.time()
704
+
705
  # Check for exit commands
706
+ if user_message.lower().strip() in ['quit', 'exit', 'خروج', 'bye', 'goodbye']:
707
+ if self.detect_language(user_message) == "arabic":
708
+ return ChatResponse(
709
+ response_id=str(time.time()),
710
+ response_type="conversation",
711
+ message="مع السلامة! أتمنى لك يوماً سعيداً. 👋",
712
+ language="arabic"
713
+ )
714
+ else:
715
+ return ChatResponse(
716
+ response_id=str(time.time()),
717
+ response_type="conversation",
718
+ message="Goodbye! Have a great day! 👋",
719
+ language="english"
720
+ )
721
+
722
  try:
723
+ print(f"\n{'='*50}")
724
+ print(f"🔍 Processing: '{user_message}'")
725
+ print(f"{'='*50}")
726
+
727
+ # Step 1: Language and sentiment analysis
728
  detected_language = self.detect_language(user_message)
729
  sentiment_result = self.analyze_sentiment(user_message)
730
  keywords = self.extract_keywords(user_message)
731
+
732
+ print(f"🌐 Detected Language: {detected_language}")
733
+ print(f"😊 Sentiment: {sentiment_result}")
734
+ print(f"🔑 Keywords: {keywords}")
735
+
736
+ # Step 2: Router Chain - Determine intent and route appropriately
737
+ print(f"\n🤖 Running Router Chain...")
738
+ router_result = self.router_chain.invoke({
739
+ "user_query": user_message,
740
+ "detected_language": detected_language,
741
+ "extracted_keywords": json.dumps(keywords),
742
+ "sentiment_analysis": json.dumps(sentiment_result),
743
+ "conversation_history": self.get_conversation_context(),
744
+ "endpoints_documentation": json.dumps(self.endpoints_documentation, indent=2)
745
+ })
746
+
747
+ # Parse router response
748
+ router_data = self.parse_router_response(router_result["text"])
749
+ print(f"🎯 Router Decision: {router_data}")
750
+
751
+ # Step 3: Handle based on intent
752
+ if router_data["intent"] == "CONVERSATION":
753
+ print(f"\n💬 Handling as CONVERSATION")
754
+ response_text = self.handle_conversation(user_message, detected_language, sentiment_result)
755
+
756
+ # Add to conversation history
757
+ self.add_to_history(user_message, response_text, "conversation")
758
+
759
+ return ChatResponse(
760
+ response_id=str(time.time()),
761
+ response_type="conversation",
762
+ message=response_text,
763
+ api_call_made=False,
764
+ language=detected_language,
765
+ api_data=None
766
+ )
767
+
768
+ elif router_data["intent"] == "API_ACTION":
769
+ print(f"\n🔗 Handling as API_ACTION")
770
+
771
+ # Check for missing required parameters
772
+ # if router_data.get("missing_required"):
773
+ # missing_params = router_data["missing_required"]
774
+ # if detected_language == "arabic":
775
+ # response_text = f"أحتاج إلى مزيد من المعلومات: {', '.join(missing_params)}"
776
+ # else:
777
+ # response_text = f"I need more information: {', '.join(missing_params)}"
778
+
779
+ # return ChatResponse(
780
+ # response_id=str(time.time()),
781
+ # response_type="conversation",
782
+ # message=response_text,
783
+ # api_call_made=False,
784
+ # language=detected_language
785
+ # )
786
+
787
+ # Handle API action
788
+ api_result = self.handle_api_action(
789
+ user_message, detected_language, sentiment_result, keywords, router_data
790
+ )
791
+
792
+ # Add to conversation history
793
+ self.add_to_history(user_message, api_result["response"], "api_action")
794
+
795
+ return ChatResponse(
796
+ response_id=str(time.time()),
797
  response_type="api_action",
798
+ message=api_result["response"],
799
  api_call_made=True,
800
+ api_data=api_result["api_data"],
801
  language=detected_language
802
  )
803
+
804
  else:
805
+ # Fallback for unknown intent
806
+ print(f"⚠️ Unknown intent: {router_data['intent']}")
807
+ fallback_response = self.handle_conversation(user_message, detected_language, sentiment_result)
808
+
809
+ return ChatResponse(
810
+ response_id=str(time.time()),
811
  response_type="conversation",
812
+ message=fallback_response,
813
  api_call_made=False,
814
  language=detected_language
815
  )
816
+
 
 
 
 
 
 
817
  except Exception as e:
818
+ print(f"❌ Error in chat method: {str(e)}")
819
+ print(f" Traceback: {traceback.format_exc()}")
820
+
821
+ # Fallback error response
822
+ if self.detect_language(user_message) == "arabic":
823
+ error_message = "أعتذر، حدث خطأ في معالجة رسالتك. يرجى المحاولة مرة أخرى."
824
+ else:
825
+ error_message = "I apologize, there was an error processing your message. Please try again."
826
+
827
  return ChatResponse(
828
+ response_id=str(time.time()),
829
  response_type="conversation",
830
+ message=error_message,
831
  api_call_made=False,
832
+ language=self.detect_language(user_message)
833
  )
834
+
835
+ finally:
836
+ end_time = time.time()
837
+ print(f"⏱️ Processing time: {end_time - start_time:.2f} seconds")
838
 
839
+ def run_interactive_chat(self):
840
+ """Run the interactive chat interface"""
841
+ try:
842
+ while True:
843
+ try:
844
+ # Get user input
845
+ user_input = input("\n👤 You: ").strip()
846
+
847
+ if not user_input:
848
+ continue
849
+
850
+ # Process the message
851
+ response = self.chat(user_input)
852
+
853
+ # Display the response
854
+ print(f"\n🤖 Bot: {response.message}")
855
+
856
+ # Check for exit
857
+ if user_input.lower() in ['quit', 'exit', 'خروج', 'bye', 'goodbye']:
858
+ break
859
+
860
+ except KeyboardInterrupt:
861
+ print("\n\n👋 Chat interrupted. Goodbye!")
862
+ break
863
+ except EOFError:
864
+ print("\n\n👋 Chat ended. Goodbye!")
865
+ break
866
+ except Exception as e:
867
+ print(f"\n❌ Error: {e}")
868
  continue
869
+
870
+ except Exception as e:
871
+ print(f"❌ Fatal error in chat interface: {e}")
872
 
873
+ def clear_history(self):
874
+ """Clear conversation history"""
875
+ self.conversation_history = []
876
+ print("🗑️ Conversation history cleared.")
 
 
877
 
 
 
 
 
 
 
878
 
 
 
 
879
 
880
+ # def main():
881
+ # """Main function to run the healthcare chatbot"""
 
 
 
 
 
 
 
882
  # try:
883
+ # print("🚀 Starting Healthcare Chatbot...")
884
  # chatbot = HealthcareChatbot()
885
+ # chatbot.run_interactive_chat()
886
+
887
+ # except KeyboardInterrupt:
888
+ # print("\n\n👋 Shutting down gracefully...")
889
  # except Exception as e:
890
+ # print(f" Fatal error: {e}")
891
+ # print(f" Traceback: {traceback.format_exc()}")
892
 
893
 
 
894
  # if __name__ == "__main__":
895
+ # main()
896
+
897
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
898
  from fastapi import FastAPI, HTTPException
899
  from pydantic import BaseModel
900
  from typing import Dict, Any, Optional
 
912
  class QueryRequest(BaseModel):
913
  query: str
914
 
 
 
 
 
 
 
915
 
916
  @app.post("/query")
917
  async def process_query(request: QueryRequest):
 
919
  Process a user query and return a response
920
  """
921
  try:
922
+ response = agent.chat(request.query).message
923
  return response
924
  except Exception as e:
925
  raise HTTPException(status_code=500, detail=str(e))
old.py ADDED
@@ -0,0 +1,1146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import json
3
+ import requests
4
+ import traceback
5
+ import time
6
+ import os
7
+ from typing import Dict, Any, List, Optional, Tuple
8
+ from datetime import datetime, timedelta
9
+
10
+ # Updated imports for pydantic
11
+ from pydantic import BaseModel, Field
12
+
13
+ # Updated imports for LangChain
14
+ from langchain_core.prompts import PromptTemplate, ChatPromptTemplate
15
+ from langchain_core.output_parsers import JsonOutputParser
16
+ from langchain_ollama import OllamaLLM
17
+ from langchain.chains import LLMChain
18
+ from langchain.callbacks.manager import CallbackManager
19
+ from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
20
+ from langchain_huggingface.embeddings import HuggingFaceEmbeddings
21
+
22
+ # Enhanced HuggingFace imports for improved functionality
23
+ from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification
24
+ import numpy as np
25
+
26
+ # Import endpoints documentation
27
+ from endpoints_documentation import endpoints_documentation
28
+
29
+ # Set environment variables for HuggingFace
30
+ # if os.name == 'posix' and os.uname().sysname == 'Darwin': # Check if running on macOS
31
+ # os.environ["HF_HOME"] = os.path.expanduser("~/Library/Caches/huggingface")
32
+ # os.environ["TRANSFORMERS_CACHE"] = os.path.expanduser("~/Library/Caches/huggingface/transformers")
33
+ # else:
34
+ os.environ["HF_HOME"] = "/tmp/huggingface"
35
+ os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1"
36
+
37
+
38
+ class ChatMessage(BaseModel):
39
+ """Data model for chat messages"""
40
+ message_id: str = Field(..., description="Unique identifier for the message")
41
+ user_id: str = Field(..., description="User identifier")
42
+ message: str = Field(..., description="The user's message")
43
+ timestamp: datetime = Field(default_factory=datetime.now, description="When the message was sent")
44
+ language: str = Field(default="english", description="Detected language of the message")
45
+
46
+
47
+ class ChatResponse(BaseModel):
48
+ """Data model for chatbot responses"""
49
+ response_id: str = Field(..., description="Unique identifier for the response")
50
+ response_type: str = Field(..., description="Type of response: 'conversation' or 'api_action'")
51
+ message: str = Field(..., description="The chatbot's response message")
52
+ api_call_made: bool = Field(default=False, description="Whether an API call was made")
53
+ api_data: Optional[Dict[str, Any]] = Field(default=None, description="API response data if applicable")
54
+ language: str = Field(default="english", description="Language of the response")
55
+ timestamp: datetime = Field(default_factory=datetime.now, description="When the response was generated")
56
+
57
+
58
+ class EndpointRequest(BaseModel):
59
+ """Data model for API endpoint requests"""
60
+ endpoint: str = Field(..., description="The API endpoint path to call")
61
+ method: str = Field(..., description="The HTTP method to use (GET or POST)")
62
+ params: Dict[str, Any] = Field(default_factory=dict, description="Parameters for the API call")
63
+ missing_required: List[str] = Field(default_factory=list, description="Any required parameters that are missing")
64
+
65
+
66
+ class HealthcareChatbot:
67
+ def __init__(self):
68
+ self.endpoints_documentation = endpoints_documentation
69
+ self.ollama_base_url = "http://localhost:11434"
70
+ self.model_name = "gemma3"
71
+ self.BASE_URL = 'https://f376-197-54-54-66.ngrok-free.app'
72
+ self.headers = {'Content-type': 'application/json'}
73
+ self.user_id = '86639f4c-5dfc-441d-b229-084f0fcdd748'
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
+
90
+ def _print_welcome_message(self):
91
+ """Print welcome message in both languages"""
92
+ print("\n" + "="*60)
93
+ print("🏥 HEALTHCARE CHATBOT READY")
94
+ print("="*60)
95
+ print("English: Hello! I'm your healthcare assistant. I can help you with:")
96
+ print("• Booking and managing appointments")
97
+ print("• Finding hospital information")
98
+ print("• Viewing your medical records")
99
+ print("• General healthcare questions")
100
+ print()
101
+ print("Arabic: مرحباً! أنا مساعدك الطبي. يمكنني مساعدتك في:")
102
+ print("• حجز وإدارة المواعيد")
103
+ print("• العثور على معلومات المستشفى")
104
+ print("• عرض سجلاتك الطبية")
105
+ print("• الأسئلة الطبية العامة")
106
+ print("="*60)
107
+ print("Type 'quit' or 'خروج' to exit\n")
108
+
109
+ def _initialize_language_tools(self):
110
+ """Initialize language processing tools"""
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
+ )
118
+ self.sentiment_analyzer = pipeline(
119
+ "sentiment-analysis",
120
+ model="cardiffnlp/twitter-xlm-roberta-base-sentiment"
121
+ )
122
+ print("✓ Language processing models loaded successfully")
123
+ except Exception as e:
124
+ print(f"⚠ Warning: Some language models failed to load: {e}")
125
+ self.language_classifier = None
126
+ self.sentiment_analyzer = None
127
+
128
+ def _initialize_date_parser(self):
129
+ """Initialize date parsing model"""
130
+ try:
131
+ self.date_parser = pipeline(
132
+ "token-classification",
133
+ model="Jean-Baptiste/roberta-large-ner-english",
134
+ aggregation_strategy="simple"
135
+ )
136
+ except Exception as e:
137
+ print(f"⚠ Warning: Date parsing model failed to load: {e}")
138
+ self.date_parser = None
139
+
140
+ def _initialize_llm(self):
141
+ """Initialize the LLM"""
142
+ callbacks = [StreamingStdOutCallbackHandler()]
143
+ self.llm = OllamaLLM(
144
+ model=self.model_name,
145
+ base_url=self.ollama_base_url,
146
+ callbacks=callbacks,
147
+ temperature=0.7,
148
+ num_ctx=8192,
149
+ top_p=0.9,
150
+ request_timeout=60,
151
+ )
152
+
153
+ def _initialize_parsers_and_chains(self):
154
+ """Initialize all prompt templates and chains"""
155
+ self.json_parser = JsonOutputParser(pydantic_object=EndpointRequest)
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
+ self.intent_classifier_template = PromptTemplate(
192
+ template="""
193
+ You are a strict intent classification system. Your only task is to determine if the user message requires an API action or is general conversation.
194
+
195
+ === ABSOLUTE RULES ===
196
+ 1. OUTPUT FORMAT MUST BE EXACTLY:
197
+ {{
198
+ "intent": "API_ACTION" or "CONVERSATION",
199
+ "confidence": 0.0-1.0,
200
+ "reasoning": "clear justification",
201
+ "requires_backend": true or false
202
+ }}
203
+ 2. Never invent custom intent types
204
+ 3. Never output endpoint names in the intent field
205
+ 4. "requires_backend" must match the intent (true for API_ACTION)
206
+
207
+ === CLASSIFICATION CRITERIA ===
208
+ API_ACTION must meet ALL of:
209
+ - The message contains a clear, actionable request
210
+ - The request matches a documented API endpoint's purpose
211
+ - The request requires specific backend functionality
212
+
213
+ CONVERSATION applies when:
214
+ - The message is social/greeting/smalltalk
215
+ - The request is too vague for API action
216
+ - No API endpoint matches the request
217
+
218
+ === INPUT DATA ===
219
+ User Message: {user_query}
220
+ Detected Language: {detected_language}
221
+ API Endpoints: {endpoints_documentation}
222
+
223
+ === DECISION PROCESS ===
224
+ 1. Analyze the message literally - what is the explicit request?
225
+ 2. Check endpoints documentation - is there an exact functional match?
226
+ 3. If uncertain, default to CONVERSATION
227
+ 4. Validate against rules before responding
228
+
229
+ === OUTPUT VALIDATION ===
230
+ Before responding, verify:
231
+ - Intent is ONLY "API_ACTION" or "CONVERSATION"
232
+ - Confidence reflects certainty (1.0 = perfect match)
233
+ - Reasoning explains the endpoint match (for API_ACTION)
234
+ - requires_backend aligns with intent
235
+
236
+ Respond ONLY in the exact specified format.
237
+ """,
238
+ input_variables=["user_query", "detected_language", "conversation_history", "endpoints_documentation"]
239
+ )
240
+
241
+
242
+
243
+ # API routing prompt (reuse existing router_prompt_template)
244
+ self.router_prompt_template = PromptTemplate(
245
+ template="""
246
+ You are a precise API routing assistant. Your job is to analyze user queries and select the correct API endpoint with proper parameters.
247
+
248
+ === ENDPOINT DOCUMENTATION ===
249
+ {endpoints_documentation}
250
+
251
+ === USER REQUEST ANALYSIS ===
252
+ User Query: {user_query}
253
+ Language: {detected_language}
254
+ Keywords: {extracted_keywords}
255
+ Sentiment: {sentiment_analysis}
256
+ Current Context:
257
+ - DateTime: {current_datetime}
258
+ - Timezone: {timezone}
259
+ - User Locale: {user_locale}
260
+
261
+ === ROUTING PROCESS ===
262
+ Follow these steps in order:
263
+
264
+ STEP 1: INTENT ANALYSIS
265
+ - What is the user trying to accomplish?
266
+ - What type of operation are they requesting? (create, read, update, delete, search, etc.)
267
+ - What entity/resource are they working with?
268
+
269
+ STEP 2: DATE/TIME PROCESSING
270
+ - Identify any temporal expressions in the user query
271
+ - Convert relative dates/times using the current context:
272
+ * "اليوم" (today) = current date
273
+ * "غدا" (tomorrow) = current date + 1 day
274
+ * "أمس" (yesterday) = current date - 1 day
275
+ * "الأسبوع القادم" (next week) = current date + 7 days
276
+ * "بعد ساعتين" (in 2 hours) = current time + 2 hours
277
+ * "صباحًا" (morning/AM), "مساءً" (evening/PM)
278
+ - Handle different date formats and languages
279
+ - Account for timezone differences
280
+ - Convert to ISO 8601 format: YYYY-MM-DDTHH:MM:SS
281
+
282
+ STEP 3: ENDPOINT MATCHING
283
+ - Review each endpoint in the documentation
284
+ - Match the user's intent to the endpoint's PURPOSE/DESCRIPTION
285
+ - Consider the HTTP method (GET for retrieval, POST for creation, etc.)
286
+ - Verify the endpoint can handle the user's specific request
287
+
288
+ STEP 4: PARAMETER EXTRACTION
289
+ - Identify ALL required parameters from the endpoint documentation
290
+ - Extract parameter values from the user query
291
+ - Convert data types as needed:
292
+ - Dates/times to ISO 8601 format (YYYY-MM-DDTHH:mm:ss)
293
+ - Numbers to integers
294
+ - Set appropriate defaults for optional parameters if beneficial
295
+
296
+
297
+
298
+ STEP 5: VALIDATION
299
+ - Ensure ALL required parameters are provided or identified as missing
300
+ - Verify parameter formats match documentation requirements
301
+ - Check that the selected endpoint actually solves the user's problem
302
+
303
+ === RESPONSE FORMAT ===
304
+ Provide your analysis and decision in this exact JSON structure:
305
+
306
+ {{
307
+ "reasoning": {{
308
+ "user_intent": "Brief description of what the user wants to accomplish",
309
+ "selected_endpoint": "Why this endpoint was chosen over others",
310
+ "parameter_mapping": "How user query maps to endpoint parameters"
311
+ }},
312
+ "endpoint": "/exact_endpoint_path_from_documentation",
313
+ "method": "HTTP_METHOD",
314
+ "params": {{
315
+ "required_param_1": "extracted_or_converted_value",
316
+ "required_param_2": "extracted_or_converted_value",
317
+ "optional_param": "value_if_applicable"
318
+ }},
319
+ "missing_required": ["list", "of", "missing", "required", "parameters"],
320
+ "confidence": 0.95
321
+ }}
322
+
323
+ === CRITICAL RULES ===
324
+ 1. ONLY select endpoints that exist in the provided documentation
325
+ 2. NEVER fabricate or assume endpoint parameters not in documentation
326
+ 3. ALL required parameters MUST be included or listed as missing
327
+ 4. Convert dates/times to ISO 8601 format (YYYY-MM-DDTHH:mm:ss)
328
+ 5. If patient_id is required and not provided, add it to missing_required
329
+ 6. Match endpoints by PURPOSE, not just keywords in the path
330
+ 7. If multiple endpoints could work, choose the most specific one
331
+ 8. If no endpoint matches, set endpoint to null and explain in reasoning
332
+
333
+ === EXAMPLES OF GOOD MATCHING ===
334
+ - User wants "patient records" → Use patient retrieval endpoint, not general search
335
+ - User wants to "schedule appointment" → Use appointment creation endpoint
336
+ - User asks "what appointments today" → Use appointment listing with date filter
337
+ - User wants to "update medication" → Use medication update endpoint with patient_id
338
+
339
+ Think step by step and be precise with your endpoint selection and parameter extraction.:""",
340
+ input_variables=["endpoints_documentation", "user_query", "detected_language",
341
+ "extracted_keywords", "sentiment_analysis", "conversation_history",
342
+ "current_datetime", "timezone", "user_locale"]
343
+ )
344
+ # old one
345
+ # self.router_prompt_template = PromptTemplate(
346
+ # template="""
347
+ # You are a precise API routing assistant. Your job is to analyze user queries and select the correct API endpoint with proper parameters.
348
+
349
+ # === ENDPOINT DOCUMENTATION ===
350
+ # {endpoints_documentation}
351
+
352
+ # === USER REQUEST ANALYSIS ===
353
+ # User Query: {user_query}
354
+ # Language: {detected_language}
355
+ # Keywords: {extracted_keywords}
356
+ # Sentiment: {sentiment_analysis}
357
+
358
+ # === ROUTING PROCESS ===
359
+ # Follow these steps in order:
360
+
361
+ # STEP 1: INTENT ANALYSIS
362
+ # - What is the user trying to accomplish?
363
+ # - What type of operation are they requesting? (create, read, update, delete, search, etc.)
364
+ # - What entity/resource are they working with?
365
+
366
+ # STEP 2: ENDPOINT MATCHING
367
+ # - Review each endpoint in the documentation
368
+ # - Match the user's intent to the endpoint's PURPOSE/DESCRIPTION
369
+ # - Consider the HTTP method (GET for retrieval, POST for creation, etc.)
370
+ # - Verify the endpoint can handle the user's specific request
371
+
372
+ # STEP 3: PARAMETER EXTRACTION
373
+ # - Identify ALL required parameters from the endpoint documentation
374
+ # - Extract parameter values from the user query
375
+ # - Convert data types as needed (dates to ISO 8601, numbers to integers, etc.)
376
+ # - Set appropriate defaults for optional parameters if beneficial
377
+
378
+ # STEP 4: VALIDATION
379
+ # - Ensure ALL required parameters are provided or identified as missing
380
+ # - Verify parameter formats match documentation requirements
381
+ # - Check that the selected endpoint actually solves the user's problem
382
+
383
+ # === RESPONSE FORMAT ===
384
+ # Provide your analysis and decision in this exact JSON structure:
385
+
386
+ # {{
387
+ # "reasoning": {{
388
+ # "user_intent": "Brief description of what the user wants to accomplish",
389
+ # "selected_endpoint": "Why this endpoint was chosen over others",
390
+ # "parameter_mapping": "How user query maps to endpoint parameters"
391
+ # }},
392
+ # "endpoint": "/exact_endpoint_path_from_documentation",
393
+ # "method": "HTTP_METHOD",
394
+ # "params": {{
395
+ # "required_param_1": "extracted_or_converted_value",
396
+ # "required_param_2": "extracted_or_converted_value",
397
+ # "optional_param": "value_if_applicable"
398
+ # }},
399
+ # "missing_required": ["list", "of", "missing", "required", "parameters"],
400
+ # "confidence": 0.95
401
+ # }}
402
+
403
+ # === CRITICAL RULES ===
404
+ # 1. ONLY select endpoints that exist in the provided documentation
405
+ # 2. NEVER fabricate or assume endpoint parameters not in documentation
406
+ # 3. ALL required parameters MUST be included or listed as missing
407
+ # 4. Convert dates/times to ISO 8601 format (YYYY-MM-DDTHH:MM:SS)
408
+ # 5. If patient_id is required and not provided, add it to missing_required
409
+ # 6. Match endpoints by PURPOSE, not just keywords in the path
410
+ # 7. If multiple endpoints could work, choose the most specific one
411
+ # 8. If no endpoint matches, set endpoint to null and explain in reasoning
412
+
413
+ # === EXAMPLES OF GOOD MATCHING ===
414
+ # - User wants "patient records" → Use patient retrieval endpoint, not general search
415
+ # - User wants to "schedule appointment" → Use appointment creation endpoint
416
+ # - User asks "what appointments today" → Use appointment listing with date filter
417
+ # - User wants to "update medication" → Use medication update endpoint with patient_id
418
+
419
+ # Think step by step and be precise with your endpoint selection and parameter extraction.:""",
420
+ # input_variables=["endpoints_documentation", "user_query", "detected_language",
421
+ # "extracted_keywords", "sentiment_analysis", "conversation_history"]
422
+ # )
423
+
424
+ # Conversational response prompt
425
+ self.conversation_template = PromptTemplate(
426
+ template="""
427
+ You are a friendly and professional healthcare chatbot assistant.
428
+
429
+ === RESPONSE GUIDELINES ===
430
+ - Respond ONLY in {detected_language}
431
+ - Be helpful, empathetic, and professional
432
+ - Keep responses concise but informative
433
+ - Use appropriate medical terminology when needed
434
+ - Maintain a caring and supportive tone
435
+
436
+ === CONTEXT ===
437
+ User Message: {user_query}
438
+ Language: {detected_language}
439
+ Sentiment: {sentiment_analysis}
440
+ Conversation History: {conversation_history}
441
+
442
+ === LANGUAGE-SPECIFIC INSTRUCTIONS ===
443
+
444
+ FOR ARABIC RESPONSES:
445
+ - Use Modern Standard Arabic (الفصحى)
446
+ - Be respectful and formal as appropriate in Arabic culture
447
+ - Use proper Arabic medical terminology
448
+ - Keep sentences clear and grammatically correct
449
+
450
+ FOR ENGLISH RESPONSES:
451
+ - Use clear, professional English
452
+ - Be warm and approachable
453
+ - Use appropriate medical terminology
454
+
455
+ === RESPONSE RULES ===
456
+ 1. Address the user's question or comment directly
457
+ 2. Provide helpful information when possible
458
+ 3. If you cannot help with something specific, explain what you CAN help with
459
+ 4. Never provide specific medical advice - always recommend consulting healthcare professionals
460
+ 5. Be encouraging and supportive
461
+ 6. Do NOT mix languages in your response
462
+ 7. End responses naturally without asking multiple questions
463
+
464
+ Generate a helpful conversational response:""",
465
+ input_variables=["user_query", "detected_language", "sentiment_analysis", "conversation_history"]
466
+ )
467
+
468
+ # API response formatting prompt (reuse existing user_response_template)
469
+ self.user_response_template = PromptTemplate(
470
+ template="""
471
+ You are a professional healthcare assistant. Answer the user's question using the provided API data.
472
+
473
+ User Query: {user_query}
474
+ User Sentiment: {sentiment_analysis}
475
+ Response Language: {detected_language}
476
+
477
+ API Response Data:
478
+ {api_response}
479
+
480
+ === INSTRUCTIONS ===
481
+
482
+ 1. Read and understand the API response data above
483
+ 2. Use ONLY the actual data from the API response - never make up information
484
+ 3. Respond in {detected_language} language only
485
+ 4. Write like you're talking to a friend or family member - warm, friendly, and caring
486
+ 5. Make it sound natural and conversational, not like a system message
487
+ 6. Convert technical data to simple, everyday language
488
+
489
+ === DATE AND TIME FORMATTING ===
490
+
491
+ When you see date_time fields like '2025-05-30T10:28:10':
492
+ - For English: Convert to "May 30, 2025 at 10:28 AM"
493
+ - For Arabic: Convert to "٣٠ مايو ٢٠٢٥ في الساعة ١٠:٢٨ صباحاً"
494
+
495
+ === RESPONSE EXAMPLES ===
496
+
497
+ For appointment confirmations:
498
+ - English: "Great! I've got your appointment set up for May 30, 2025 at 10:28 AM. Everything looks good!"
499
+ - Arabic: "ممتاز! موعدك محجوز يوم ٣٠ مايو ٢٠٢٥ الساعة ١٠:٢٨ صباحاً. كل شيء جاهز!"
500
+
501
+ For appointment info:
502
+ - English: "Your next appointment is on May 30, 2025 at 10:28 AM. See you then!"
503
+ - Arabic: "موعدك القادم يوم ٣٠ مايو ٢٠٢٥ الساعة ١٠:٢٨ صباحاً. نراك قريباً!"
504
+
505
+ === TONE GUIDELINES ===
506
+ - Use friendly words like: "Great!", "Perfect!", "All set!", "ممتاز!", "رائع!", "تمام!"
507
+ - Add reassuring phrases: "Everything looks good", "You're all set", "كل شيء جاهز", "تم بنجاح"
508
+ - Sound helpful and caring, not robotic or formal
509
+
510
+ === LANGUAGE FORMATTING ===
511
+
512
+ For Arabic responses:
513
+ - Use Arabic numerals: ٠١٢٣٤٥٦٧٨٩
514
+ - Use Arabic month names: يناير، فبراير، مارس، أبريل، مايو، يونيو، يوليو، أغسطس، سبتمبر، أكتوبر، نوفمبر، ديسمبر
515
+ - Friendly, warm Arabic tone
516
+
517
+ For English responses:
518
+ - Use standard English numerals
519
+ - 12-hour time format with AM/PM
520
+ - Friendly, conversational English tone
521
+
522
+ === CRITICAL RULES ===
523
+ - Extract dates and times exactly as they appear in the API response
524
+ - Never use example dates or placeholder information
525
+ - Respond only in the specified language
526
+ - Make your response sound like a helpful friend, not a computer
527
+ - Focus on answering the user's specific question with warmth and care
528
+
529
+ Generate a friendly, helpful response using the API data provided above.
530
+ """,
531
+ input_variables=["user_query", "api_response", "detected_language", "sentiment_analysis"]
532
+ )
533
+ # self.user_response_template = PromptTemplate(
534
+ # template="""
535
+ # You are a professional healthcare assistant. Your task is to carefully analyze the API data and respond to the user's question accurately.
536
+
537
+ # User Query: {user_query}
538
+ # User Sentiment: {sentiment_analysis}
539
+ # Response Language: {detected_language}
540
+
541
+ # API Response Data:
542
+ # {api_response}
543
+
544
+ # === CRITICAL INSTRUCTIONS ===
545
+
546
+ # 1. FIRST: Carefully read and analyze the API response data above
547
+ # 2. SECOND: Identify all date_time fields in the format 'YYYY-MM-DDTHH:MM:SS'
548
+ # 3. THIRD: Extract the EXACT dates and times from the API response - DO NOT use any example dates
549
+ # 4. FOURTH: Convert these extracted dates to the user-friendly format specified below
550
+ # 5. FIFTH: Respond ONLY in {detected_language} language
551
+ # 6. Use a warm, friendly, conversational tone like talking to a friend
552
+
553
+ # === DATE EXTRACTION AND CONVERSION ===
554
+
555
+ # Step 1: Find date_time fields in the API response (format: 'YYYY-MM-DDTHH:MM:SS')
556
+ # Step 2: Convert ONLY the actual extracted dates using these rules:
557
+
558
+ # For English:
559
+ # - Convert 'YYYY-MM-DDTHH:MM:SS' to readable format
560
+ # - Example: '2025-06-01T08:00:00' becomes "June 1, 2025 at 8:00 AM"
561
+ # - Use 12-hour format with AM/PM
562
+
563
+ # For Arabic:
564
+ # - Convert to Arabic numerals and month names
565
+ # - Example: '2025-06-01T08:00:00' becomes "١ يونيو ٢٠٢٥ في الساعة ٨:٠٠ صباحاً"
566
+ # - Arabic months: يناير، فبراير، مارس، أبريل، مايو، يونيو، يوليو، أغسطس، سبتمبر، أكتوبر، نوفمبر، ديسمبر
567
+ # - Arabic numerals: ٠١٢٣٤٥٦٧٨٩
568
+
569
+ # === RESPONSE APPROACH ===
570
+
571
+ # 1. Analyze what the user is asking for
572
+ # 2. Find the relevant information in the API response
573
+ # 3. Extract actual dates/times from the API data
574
+ # 4. Convert technical information to simple language
575
+ # 5. Respond warmly and helpfully
576
+
577
+ # === TONE AND LANGUAGE ===
578
+
579
+ # English responses:
580
+ # - Use phrases like: "Great!", "Perfect!", "All set!", "Here's what I found:"
581
+ # - Be conversational and reassuring
582
+
583
+ # Arabic responses:
584
+ # - Use phrases like: "ممتاز!", "رائع!", "تمام!", "إليك ما وجدته:"
585
+ # - Be warm and helpful in Arabic style
586
+
587
+ # === IMPORTANT REMINDERS ===
588
+ # - NEVER use example dates from this prompt
589
+ # - ALWAYS extract dates from the actual API response data
590
+ # - If no dates exist in API response, don't mention any dates
591
+ # - Stay focused on answering the user's specific question
592
+ # - Use only information that exists in the API response
593
+
594
+ # Now, carefully analyze the API response above and generate a helpful response to the user's query using ONLY the actual data provided.
595
+ # """,
596
+ # input_variables=["user_query", "api_response", "detected_language", "sentiment_analysis"]
597
+ # )
598
+
599
+ # Create chains
600
+ self.intent_chain = LLMChain(llm=self.llm, prompt=self.intent_classifier_template)
601
+ self.router_chain = LLMChain(llm=self.llm, prompt=self.router_prompt_template)
602
+ self.conversation_chain = LLMChain(llm=self.llm, prompt=self.conversation_template)
603
+ self.api_response_chain = LLMChain(llm=self.llm, prompt=self.user_response_template)
604
+
605
+ def detect_language(self, text):
606
+ """Detect language of the input text"""
607
+ if self.language_classifier and len(text.strip()) > 3:
608
+ try:
609
+ result = self.language_classifier(text)
610
+ detected_lang = result[0][0]['label']
611
+ confidence = result[0][0]['score']
612
+
613
+ if detected_lang in ['ar', 'arabic']:
614
+ return "arabic"
615
+ elif detected_lang in ['en', 'english']:
616
+ return "english"
617
+ elif confidence > 0.8:
618
+ return "english" # Default to English for unsupported languages
619
+ except:
620
+ pass
621
+
622
+ # Fallback: Basic Arabic detection
623
+ arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]+')
624
+ if arabic_pattern.search(text):
625
+ return "arabic"
626
+
627
+ return "english"
628
+
629
+ def analyze_sentiment(self, text):
630
+ """Analyze sentiment of the text"""
631
+ if self.sentiment_analyzer and len(text.strip()) > 3:
632
+ try:
633
+ result = self.sentiment_analyzer(text)
634
+ return {
635
+ "sentiment": result[0]['label'],
636
+ "score": result[0]['score']
637
+ }
638
+ except:
639
+ pass
640
+
641
+ return {"sentiment": "NEUTRAL", "score": 0.5}
642
+
643
+ def extract_keywords(self, text):
644
+ """Extract keywords from text"""
645
+ # Simple keyword extraction
646
+ words = re.findall(r'\b\w+\b', text.lower())
647
+ # Filter out common words and keep meaningful ones
648
+ stopwords = {'the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'is', 'are', 'was', 'were'}
649
+ keywords = [w for w in words if len(w) > 3 and w not in stopwords]
650
+ return list(set(keywords))[:5] # Return top 5 unique keywords
651
+
652
+ def get_conversation_context(self):
653
+ """Get recent conversation history as context"""
654
+ if not self.conversation_history:
655
+ return "No previous conversation"
656
+
657
+ context = []
658
+ for item in self.conversation_history[-3:]: # Last 3 exchanges
659
+ context.append(f"User: {item['user_message']}")
660
+ context.append(f"Bot: {item['bot_response'][:100]}...") # Truncate long responses
661
+
662
+ return " | ".join(context)
663
+
664
+ def add_to_history(self, user_message, bot_response, response_type):
665
+ """Add exchange to conversation history"""
666
+ self.conversation_history.append({
667
+ 'timestamp': datetime.now(),
668
+ 'user_message': user_message,
669
+ 'bot_response': bot_response,
670
+ 'response_type': response_type
671
+ })
672
+
673
+ # Keep only recent history
674
+ if len(self.conversation_history) > self.max_history_length:
675
+ self.conversation_history = self.conversation_history[-self.max_history_length:]
676
+
677
+ def classify_intent(self, user_query, detected_language):
678
+ """Classify if the user query requires API action or is conversational"""
679
+ try:
680
+ result = self.intent_chain.invoke({
681
+ "user_query": user_query,
682
+ "detected_language": detected_language,
683
+ "conversation_history": self.get_conversation_context(),
684
+ "endpoints_documentation": json.dumps(self.endpoints_documentation, indent=2)
685
+ })
686
+
687
+ # Parse the JSON response
688
+ intent_text = result["text"]
689
+ # Clean and parse JSON
690
+ cleaned_response = re.sub(r'//.*?$', '', intent_text, flags=re.MULTILINE)
691
+ cleaned_response = re.sub(r'/\*.*?\*/', '', cleaned_response, flags=re.DOTALL)
692
+ cleaned_response = re.sub(r',(\s*[}\]])', r'\1', cleaned_response)
693
+
694
+ try:
695
+ intent_data = json.loads(cleaned_response)
696
+ return intent_data
697
+ except json.JSONDecodeError:
698
+ # Try to extract JSON from the response
699
+ json_match = re.search(r'\{.*?\}', cleaned_response, re.DOTALL)
700
+ if json_match:
701
+ intent_data = json.loads(json_match.group(0))
702
+ return intent_data
703
+ else:
704
+ # Default classification if parsing fails
705
+ return {
706
+ "intent": "CONVERSATION",
707
+ "confidence": 0.5,
708
+ "reasoning": "Failed to parse LLM response",
709
+ "requires_backend": False
710
+ }
711
+ except Exception as e:
712
+ print(f"Error in intent classification: {e}")
713
+ return {
714
+ "intent": "CONVERSATION",
715
+ "confidence": 0.5,
716
+ "reasoning": f"Error in classification: {str(e)}",
717
+ "requires_backend": False
718
+ }
719
+
720
+ def handle_conversation(self, user_query, detected_language, sentiment_result):
721
+ """Handle conversational responses"""
722
+ try:
723
+ result = self.conversation_chain.invoke({
724
+ "user_query": user_query,
725
+ "detected_language": detected_language,
726
+ "sentiment_analysis": json.dumps(sentiment_result),
727
+ "conversation_history": self.get_conversation_context()
728
+ })
729
+
730
+ return result["text"].strip()
731
+
732
+ except Exception as e:
733
+ # Fallback response
734
+ if detected_language == "arabic":
735
+ return "أعتذر، واجهت مشكلة في المعالجة. كيف ��مكنني مساعدتك؟"
736
+ else:
737
+ return "I apologize, I encountered a processing issue. How can I help you?"
738
+
739
+ def backend_call(self, data: Dict[str, Any]) -> Dict[str, Any]:
740
+ """Make API call to backend with retry logic"""
741
+ endpoint_url = data.get('endpoint')
742
+ endpoint_method = data.get('method')
743
+ endpoint_params = data.get('params', {}).copy()
744
+
745
+ print('Sending the api request')
746
+ print(f"🔗 Making API call to {endpoint_method} {self.BASE_URL + endpoint_url} with params: {endpoint_params}")
747
+
748
+ # Inject patient_id if needed
749
+ if 'patient_id' in endpoint_params:
750
+ endpoint_params['patient_id'] = self.user_id
751
+
752
+ retries = 0
753
+ response = None
754
+ while retries < self.max_retries:
755
+ try:
756
+ if endpoint_method.upper() == 'GET':
757
+ response = requests.get(
758
+ self.BASE_URL + endpoint_url,
759
+ params=endpoint_params,
760
+ headers=self.headers,
761
+ timeout=10
762
+ )
763
+ elif endpoint_method.upper() in ['POST', 'PUT', 'DELETE']:
764
+ response = requests.request(
765
+ endpoint_method.upper(),
766
+ self.BASE_URL + endpoint_url,
767
+ json=endpoint_params,
768
+ headers=self.headers,
769
+ timeout=10
770
+ )
771
+
772
+ response.raise_for_status()
773
+ print('Backend Response : ', response.json())
774
+ return response.json()
775
+
776
+ except requests.exceptions.RequestException as e:
777
+ retries += 1
778
+ if retries >= self.max_retries:
779
+ return {
780
+ "error": "Backend API call failed after multiple retries",
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:
816
+ date_entities = self.date_parser(text)
817
+ for entity in date_entities:
818
+ if entity['entity_group'] == 'DATE':
819
+ # Here you would need more complex date parsing logic
820
+ # This is just a placeholder
821
+ print(f"Found date entity: {entity['word']}")
822
+ # For now, just default to tomorrow if we detect any date
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
+
830
+
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
+ # print(f"Parsed relative date: {parsed_date}")
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),
842
+ "user_query": user_query,
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
+ "current_datetime": datetime.now().strftime('%Y-%m-%dT%H:%M:%S'),
848
+ "timezone": "UTC",
849
+ "user_locale": "en-US"
850
+ })
851
+
852
+ # Parse router response
853
+ route_text = router_result["text"]
854
+ # cleaned_response = re.sub(r'//.*?$', '', route_text, flags=re.MULTILINE)
855
+ # cleaned_response = re.sub(r'/\*.*?\*/', '', cleaned_response, flags=re.DOTALL)
856
+ # cleaned_response = re.sub(r',(\s*[}\]])', r'\1', cleaned_response)
857
+
858
+ # try:
859
+ # parsed_route = json.loads(cleaned_response)
860
+ # except json.JSONDecodeError:
861
+ # json_match = re.search(r'\{.*?\}', cleaned_response, re.DOTALL)
862
+ # if json_match:
863
+ # parsed_route = json.loads(json_match.group(0))
864
+ # else:
865
+ # raise ValueError("Could not parse routing response")
866
+
867
+ # print(f"🔍 Parsed route: {parsed_route}")
868
+ cleaned_response = route_text
869
+
870
+ # Remove any comments (both single-line and multi-line)
871
+ cleaned_response = re.sub(r'//.*?$', '', cleaned_response, flags=re.MULTILINE)
872
+ cleaned_response = re.sub(r'/\*.*?\*/', '', cleaned_response, flags=re.DOTALL)
873
+
874
+ # Remove any trailing commas
875
+ cleaned_response = re.sub(r',(\s*[}\]])', r'\1', cleaned_response)
876
+
877
+ # Try different methods to parse the JSON response
878
+ try:
879
+ # First attempt: direct JSON parsing of cleaned response
880
+ parsed_route = json.loads(cleaned_response)
881
+ except json.JSONDecodeError:
882
+ try:
883
+ # Second attempt: extract JSON from markdown code block
884
+ json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', cleaned_response, re.DOTALL)
885
+ if json_match:
886
+ parsed_route = json.loads(json_match.group(1))
887
+ except (json.JSONDecodeError, AttributeError):
888
+ try:
889
+ # Third attempt: find JSON-like content using regex
890
+ json_pattern = r'\{\s*"endpoint"\s*:.*?\}'
891
+ json_match = re.search(json_pattern, cleaned_response, re.DOTALL)
892
+ if json_match:
893
+ json_str = json_match.group(0)
894
+ # Additional cleaning for the extracted JSON
895
+ json_str = re.sub(r'//.*?$', '', json_str, flags=re.MULTILINE)
896
+ json_str = re.sub(r',(\s*[}\]])', r'\1', json_str)
897
+ parsed_route = json.loads(json_str)
898
+ except (json.JSONDecodeError, AttributeError):
899
+ print(f"Failed to parse JSON. Raw response: {route_text}")
900
+ print(f"Cleaned response: {cleaned_response}")
901
+ raise ValueError("Could not extract valid JSON from LLM response")
902
+
903
+ if not parsed_route:
904
+ raise ValueError("Failed to parse LLM response into valid JSON")
905
+
906
+ # Replace any placeholder values and inject parsed dates if available
907
+ if 'params' in parsed_route:
908
+ if 'patient_id' in parsed_route['params']:
909
+ parsed_route['params']['patient_id'] = self.user_id
910
+ else:
911
+ parsed_route['params']['patient_id'] = self.user_id
912
+
913
+ # Inject parsed date if available and a date parameter exists
914
+ # date_params = ['appointment_date', 'date', 'schedule_date', 'date_time', 'new_date_time']
915
+ # if parsed_date:
916
+ # for param in date_params:
917
+ # if param in parsed_route['params']:
918
+ # parsed_route['params'][param] = parsed_date
919
+
920
+ print('Parsed route: ', parsed_route)
921
+
922
+ # Make backend API call
923
+ api_response = self.backend_call(parsed_route)
924
+
925
+ print("🔗 API response received:", api_response)
926
+ # Generate user-friendly response
927
+ user_response_result = self.api_response_chain.invoke({
928
+ "user_query": user_query,
929
+ "api_response": json.dumps(api_response, indent=2),
930
+ "detected_language": detected_language,
931
+ "sentiment_analysis": json.dumps(sentiment_result),
932
+ })
933
+
934
+ print("🔗 API response:", user_response_result["text"].strip())
935
+
936
+ return {
937
+ "response": user_response_result["text"].strip(),
938
+ "api_data": api_response,
939
+ "routing_info": parsed_route
940
+ }
941
+
942
+ except Exception as e:
943
+ # Fallback error response
944
+ if detected_language == "arabic":
945
+ error_msg = "أعتذر، لم أتمكن من معالجة طلبك. يرجى المحاولة مرة أخرى أو صياغة السؤال بطريقة مختلفة."
946
+ else:
947
+ error_msg = "I apologize, I couldn't process your request. Please try again or rephrase your question."
948
+
949
+ return {
950
+ "response": error_msg,
951
+ "api_data": {"error": str(e)},
952
+ "routing_info": None
953
+ }
954
+
955
+ def chat(self, user_message: str) -> ChatResponse:
956
+ """Main chat method that handles user messages"""
957
+ start_time = time.time()
958
+
959
+ # Check for exit commands
960
+ exit_commands = ['quit', 'exit', 'bye', 'خروج', 'وداعا', 'مع السلامة']
961
+ if user_message.lower().strip() in exit_commands:
962
+ return ChatResponse(
963
+ response_id=f"resp_{int(time.time())}",
964
+ response_type="conversation",
965
+ message="Goodbye! Take care of your health! / وداعاً! اعتن بصحتك!",
966
+ language="bilingual"
967
+ )
968
+
969
+ try:
970
+ # Language detection and analysis
971
+ detected_language = self.detect_language(user_message)
972
+ sentiment_result = self.analyze_sentiment(user_message)
973
+ keywords = self.extract_keywords(user_message)
974
+
975
+ print(f"🔍 Language: {detected_language} | Sentiment: {sentiment_result['sentiment']} | Keywords: {keywords}")
976
+
977
+ # Classify intent
978
+ intent_data = self.classify_intent(user_message, detected_language)
979
+ print(f"🎯 Intent: {intent_data['intent']} (confidence: {intent_data.get('confidence', 'N/A')})")
980
+
981
+ # Handle based on intent
982
+ if intent_data["intent"] == "API_ACTION" and intent_data.get("requires_backend", False):
983
+ # Handle API-based actions
984
+ print("🔗 Processing API action...")
985
+ action_result = self.handle_api_action(user_message, detected_language, sentiment_result, keywords)
986
+
987
+ # print(action_result)
988
+
989
+ response = ChatResponse(
990
+ response_id=f"resp_{int(time.time())}",
991
+ response_type="api_action",
992
+ message=action_result["response"],
993
+ api_call_made=True,
994
+ api_data=json.dumps(action_result["api_data"]) if 'action_result' in action_result else None,
995
+ language=detected_language
996
+ )
997
+
998
+ else:
999
+ # Handle conversational responses
1000
+ print("💬 Processing conversational response...")
1001
+ conv_response = self.handle_conversation(user_message, detected_language, sentiment_result)
1002
+
1003
+ response = ChatResponse(
1004
+ response_id=f"resp_{int(time.time())}",
1005
+ response_type="conversation",
1006
+ message=conv_response,
1007
+ api_call_made=False,
1008
+ language=detected_language
1009
+ )
1010
+
1011
+ # Add to conversation history
1012
+ self.add_to_history(user_message, response.message, response.response_type)
1013
+
1014
+ print(f"⏱️ Processing time: {time.time() - start_time:.2f}s")
1015
+ return response
1016
+
1017
+ except Exception as e:
1018
+ print(f"❌ Error in chat processing: {e}")
1019
+ error_msg = "I apologize for the technical issue. Please try again. / أعتذر عن المشكلة التقنية. يرجى المحاولة مرة أخرى."
1020
+
1021
+ return ChatResponse(
1022
+ response_id=f"resp_{int(time.time())}",
1023
+ response_type="conversation",
1024
+ message=error_msg,
1025
+ api_call_made=False,
1026
+ language="bilingual"
1027
+ )
1028
+
1029
+ def start_interactive_chat(self):
1030
+ """Start an interactive chat session"""
1031
+ print("🚀 Starting interactive chat session...")
1032
+
1033
+ while True:
1034
+ try:
1035
+ # Get user input
1036
+ user_input = input("\n👤 You: ").strip()
1037
+
1038
+ if not user_input:
1039
+ continue
1040
+
1041
+ # Process the message
1042
+ print("🤖 Processing...")
1043
+ response = self.chat(user_input)
1044
+
1045
+ # Display response
1046
+ print(f"\n🏥 Healthcare Bot: {response.message}")
1047
+
1048
+ # Show additional info if API call was made
1049
+ if response.api_call_made and response.api_data:
1050
+ if "error" not in response.api_data:
1051
+ print("✅ Successfully retrieved information from healthcare system")
1052
+ else:
1053
+ print("⚠️ There was an issue accessing the healthcare system")
1054
+
1055
+ # Check for exit
1056
+ if "Goodbye" in response.message or "وداعاً" in response.message:
1057
+ break
1058
+
1059
+ except KeyboardInterrupt:
1060
+ print("\n\n👋 Chat session ended. Goodbye!")
1061
+ break
1062
+ except Exception as e:
1063
+ print(f"\n❌ Unexpected error: {e}")
1064
+ print("The chat session will continue...")
1065
+ # Create a simple function to start the chatbot
1066
+ # def start_healthcare_chatbot():
1067
+ # """Initialize and start the healthcare chatbot"""
1068
+ # try:
1069
+ # chatbot = HealthcareChatbot()
1070
+ # chatbot.start_interactive_chat()
1071
+ # except Exception as e:
1072
+ # print(f"Failed to start chatbot: {e}")
1073
+ # print("Please check your Ollama installation and endpoint documentation.")
1074
+
1075
+
1076
+ # Test the chatbot
1077
+ # if __name__ == "__main__":
1078
+ # You can test individual messages like this:
1079
+ # chatbot = HealthcareChatbot()
1080
+
1081
+ # Test conversational message
1082
+ # print("\n=== TESTING CONVERSATIONAL MESSAGE ===")
1083
+ # conv_response = chatbot.chat("Hello, how are you today?")
1084
+ # print(f"Response: {conv_response.message}")
1085
+ # print(f"Type: {conv_response.response_type}")
1086
+
1087
+ # Test API action message
1088
+ # print("\n=== TESTING API ACTION MESSAGE ===")
1089
+ # api_response = chatbot.chat("I want to book an appointment tomorrow at 2 PM")
1090
+ # print(f"Response: {api_response.message}")
1091
+ # print(f"Type: {api_response.response_type}")
1092
+ # print(f"API Called: {api_response.api_call_made}")
1093
+
1094
+ # Start interactive session (uncomment to run)
1095
+ # start_healthcare_chatbot()
1096
+
1097
+ # Fast api section
1098
+ from fastapi import FastAPI, HTTPException
1099
+ from pydantic import BaseModel
1100
+ from typing import Dict, Any, Optional
1101
+
1102
+
1103
+ app = FastAPI(
1104
+ title="Healthcare AI Assistant",
1105
+ description="An AI-powered healthcare assistant that handles appointment booking and queries",
1106
+ version="1.0.0"
1107
+ )
1108
+
1109
+ # Initialize the AI agent
1110
+ agent = HealthcareChatbot()
1111
+
1112
+ class QueryRequest(BaseModel):
1113
+ query: str
1114
+
1115
+ class QueryResponse(BaseModel):
1116
+ routing_info: Dict[str, Any]
1117
+ api_response: Dict[str, Any]
1118
+ user_friendly_response: str
1119
+ detected_language: str
1120
+ sentiment: Dict[str, Any]
1121
+
1122
+ @app.post("/query")
1123
+ async def process_query(request: QueryRequest):
1124
+ """
1125
+ Process a user query and return a response
1126
+ """
1127
+ try:
1128
+ response = agent.chat(request.query)
1129
+ return response
1130
+ except Exception as e:
1131
+ raise HTTPException(status_code=500, detail=str(e))
1132
+
1133
+ @app.get("/health")
1134
+ async def health_check():
1135
+ """
1136
+ Health check endpoint
1137
+ """
1138
+ return {"status": "healthy", "service": "healthcare-ai-assistant"}
1139
+
1140
+ @app.get("/")
1141
+ async def root():
1142
+ return {"message": "Hello World"}
1143
+
1144
+ # if __name__ == "__main__":
1145
+ # import uvicorn
1146
+ # uvicorn.run(app, host="0.0.0.0", port=8000)