abdibrahem commited on
Commit
b9333d0
·
1 Parent(s): 9b13398

Update main.py and docker files

Browse files
Files changed (4) hide show
  1. Dockerfile +1 -1
  2. final.py +0 -708
  3. main.py +706 -1
  4. testing.py +50 -0
Dockerfile CHANGED
@@ -94,7 +94,7 @@ exec uvicorn main:app --host 0.0.0.0 --port 8000 --log-level info\n\
94
  ' > /app/start.sh && chmod +x /app/start.sh
95
 
96
  # Expose ports
97
- EXPOSE 8000 11434
98
 
99
  # Switch to non-root user
100
  USER appuser
 
94
  ' > /app/start.sh && chmod +x /app/start.sh
95
 
96
  # Expose ports
97
+ # EXPOSE 8000 11434
98
 
99
  # Switch to non-root user
100
  USER appuser
final.py DELETED
@@ -1,708 +0,0 @@
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
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
- # os.environ["HF_HOME"] = "/tmp/huggingface"
31
- if os.name == 'posix' and os.uname().sysname == 'Darwin': # Check if running on macOS
32
- # Use macOS appropriate paths
33
- os.environ["HF_HOME"] = os.path.expanduser("~/Library/Caches/huggingface")
34
- os.environ["TRANSFORMERS_CACHE"] = os.path.expanduser("~/Library/Caches/huggingface/transformers")
35
- else:
36
- # Default paths for Linux/Windows
37
- os.environ["HF_HOME"] = "/tmp/huggingface"
38
- os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1"
39
-
40
- class EndpointRequest(BaseModel):
41
- """Data model for API endpoint requests"""
42
- endpoint: str = Field(..., description="The API endpoint path to call")
43
- method: str = Field(..., description="The HTTP method to use (GET or POST)")
44
- params: Dict[str, Any] = Field(default_factory=dict, description="Parameters for the API call")
45
- missing_required: List[str] = Field(default_factory=list, description="Any required parameters that are missing")
46
-
47
-
48
- class AIAgent:
49
- def __init__(self):
50
- self.endpoints_documentation = endpoints_documentation
51
- self.ollama_base_url = "http://localhost:11434" # Default Ollama URL
52
- self.model_name = "mistral" # Using mistral model for better multilingual support
53
- # self.model_name = 'llama3'
54
- self.BASE_URL = 'https://agent.serveo.net'
55
- self.headers = {
56
- 'Content-type': 'application/json'
57
- }
58
- self.user_id = 'd8507df8-cec6-49f9-adcc-367b13805e73'
59
- self.max_retries = 3
60
- self.retry_delay = 2 # seconds
61
-
62
- # Enhanced language detection using HuggingFace models
63
- self._initialize_language_tools()
64
-
65
- # Initialize LangChain components
66
- self._initialize_llm()
67
- self._initialize_parsers_and_chains()
68
-
69
- # Add date parsing capabilities
70
- self._initialize_date_parser()
71
-
72
- def _initialize_language_tools(self):
73
- """Initialize more sophisticated language processing tools"""
74
- # Use multilingual embeddings for semantic understanding
75
- self.embeddings = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-large")
76
-
77
- # Initialize language identification model
78
- try:
79
- self.language_classifier = pipeline(
80
- "text-classification",
81
- model="papluca/xlm-roberta-base-language-detection",
82
- top_k=1
83
- )
84
- print("Language classification model loaded successfully")
85
- except Exception as e:
86
- print(f"Failed to load language classification model: {e}")
87
- # Fallback to basic regex detection if model fails to load
88
- self.language_classifier = None
89
-
90
- # Add sentiment analysis for enhanced response generation
91
- try:
92
- self.sentiment_analyzer = pipeline(
93
- "sentiment-analysis",
94
- model="cardiffnlp/twitter-xlm-roberta-base-sentiment"
95
- )
96
- print("Sentiment analysis model loaded successfully")
97
- except Exception as e:
98
- print(f"Failed to load sentiment analysis model: {e}")
99
- self.sentiment_analyzer = None
100
-
101
- def _initialize_date_parser(self):
102
- """Initialize date parsing model for handling relative date expressions"""
103
- try:
104
- self.date_parser = pipeline(
105
- "token-classification",
106
- model="Jean-Baptiste/roberta-large-ner-english",
107
- aggregation_strategy="simple"
108
- )
109
- print("Date parsing model loaded successfully")
110
- except Exception as e:
111
- print(f"Failed to load date parsing model: {e}")
112
- self.date_parser = None
113
-
114
- def detect_language(self, text):
115
- """
116
- Enhanced language detection using HuggingFace models
117
- """
118
- # First try using the HuggingFace language classification model if available
119
- if self.language_classifier and len(text.strip()) > 3:
120
- try:
121
- result = self.language_classifier(text)
122
- detected_lang = result[0][0]['label']
123
- confidence = result[0][0]['score']
124
-
125
- print(f"Language detected: {detected_lang} with confidence {confidence:.4f}")
126
-
127
- # Map the detected language to our simplified language set
128
- if detected_lang in ['ar', 'arabic']:
129
- return "arabic"
130
- elif detected_lang in ['en', 'english']:
131
- return "english"
132
- elif confidence > 0.8: # If confident but not English/Arabic
133
- # We currently only support English/Arabic, but log other languages
134
- print(f"Detected unsupported language: {detected_lang}")
135
- # Default to English for other languages for now
136
- return "english"
137
- except Exception as e:
138
- print(f"Error in language detection model: {e}")
139
- # Continue to fallback methods
140
-
141
- # Fallback: Basic detection of Arabic text using regex
142
- arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]+')
143
- if arabic_pattern.search(text):
144
- return "arabic"
145
-
146
- # Default to English
147
- return "english"
148
-
149
- def analyze_sentiment(self, text):
150
- """Analyze the sentiment of the input text"""
151
- if self.sentiment_analyzer and len(text.strip()) > 3:
152
- try:
153
- result = self.sentiment_analyzer(text)
154
- sentiment = result[0]['label']
155
- score = result[0]['score']
156
- return {
157
- "sentiment": sentiment,
158
- "score": score
159
- }
160
- except Exception as e:
161
- print(f"Error in sentiment analysis: {e}")
162
-
163
- # Default neutral sentiment if analysis fails
164
- return {"sentiment": "NEUTRAL", "score": 0.5}
165
-
166
- def extract_semantic_keywords(self, text, top_n=5):
167
- """Extract semantic keywords from text using embeddings"""
168
- try:
169
- # Simple keyword extraction using embeddings comparison
170
- # This is a basic implementation - could be enhanced further
171
- words = re.findall(r'\b\w+\b', text.lower())
172
- unique_words = list(set([w for w in words if len(w) > 3]))
173
-
174
- if not unique_words:
175
- return []
176
-
177
- # Get embeddings for all words
178
- embeddings_list = []
179
- for word in unique_words:
180
- try:
181
- emb = self.embeddings.embed_query(word)
182
- embeddings_list.append((word, emb))
183
- except Exception as e:
184
- print(f"Error embedding word {word}: {e}")
185
-
186
- # Get embedding for full text
187
- text_embedding = self.embeddings.embed_query(text)
188
-
189
- # Calculate similarity to full text
190
- similarities = []
191
- for word, emb in embeddings_list:
192
- similarity = np.dot(emb, text_embedding) / (np.linalg.norm(emb) * np.linalg.norm(text_embedding))
193
- similarities.append((word, similarity))
194
-
195
- # Sort by similarity
196
- similarities.sort(key=lambda x: x[1], reverse=True)
197
-
198
- # Return top N keywords
199
- return [word for word, _ in similarities[:top_n]]
200
-
201
- except Exception as e:
202
- print(f"Error extracting keywords: {e}")
203
- return []
204
-
205
- def _initialize_llm(self):
206
- """Initialize the LLM with appropriate configuration"""
207
- # Set up the callback manager for streaming (optional)
208
- callbacks = [StreamingStdOutCallbackHandler()]
209
-
210
- # Initialize the Ollama LLM with updated parameters
211
- self.llm = OllamaLLM(
212
- model=self.model_name,
213
- base_url=self.ollama_base_url,
214
- callbacks=callbacks,
215
- temperature=0.7,
216
- num_ctx=8192, # Increased context window
217
- top_p=0.9,
218
- request_timeout=60, # Timeout in seconds
219
- )
220
-
221
- def _initialize_parsers_and_chains(self):
222
- """Initialize output parsers and LLM chains"""
223
- # Setup JSON parser for structured output
224
- self.json_parser = JsonOutputParser(pydantic_object=EndpointRequest)
225
-
226
- # Create multilingual router prompt template with enhanced context
227
- self.router_prompt_template = PromptTemplate(
228
- template="""
229
- You are a precise API routing assistant. Your job is to analyze user queries and select the correct API endpoint with proper parameters.
230
-
231
- === ENDPOINT DOCUMENTATION ===
232
- {endpoints_documentation}
233
-
234
- === USER REQUEST ANALYSIS ===
235
- User Query: {user_query}
236
- Language: {detected_language}
237
- Keywords: {extracted_keywords}
238
- Sentiment: {sentiment_analysis}
239
-
240
- === ROUTING PROCESS ===
241
- Follow these steps in order:
242
-
243
- STEP 1: INTENT ANALYSIS
244
- - What is the user trying to accomplish?
245
- - What type of operation are they requesting? (create, read, update, delete, search, etc.)
246
- - What entity/resource are they working with?
247
-
248
- STEP 2: ENDPOINT MATCHING
249
- - Review each endpoint in the documentation
250
- - Match the user's intent to the endpoint's PURPOSE/DESCRIPTION
251
- - Consider the HTTP method (GET for retrieval, POST for creation, etc.)
252
- - Verify the endpoint can handle the user's specific request
253
-
254
- STEP 3: PARAMETER EXTRACTION
255
- - Identify ALL required parameters from the endpoint documentation
256
- - Extract parameter values from the user query
257
- - Convert data types as needed (dates to ISO 8601, numbers to integers, etc.)
258
- - Set appropriate defaults for optional parameters if beneficial
259
-
260
- STEP 4: VALIDATION
261
- - Ensure ALL required parameters are provided or identified as missing
262
- - Verify parameter formats match documentation requirements
263
- - Check that the selected endpoint actually solves the user's problem
264
-
265
- === RESPONSE FORMAT ===
266
- Provide your analysis and decision in this exact JSON structure:
267
-
268
- {{
269
- "reasoning": {{
270
- "user_intent": "Brief description of what the user wants to accomplish",
271
- "selected_endpoint": "Why this endpoint was chosen over others",
272
- "parameter_mapping": "How user query maps to endpoint parameters"
273
- }},
274
- "endpoint": "/exact_endpoint_path_from_documentation",
275
- "method": "HTTP_METHOD",
276
- "params": {{
277
- "required_param_1": "extracted_or_converted_value",
278
- "required_param_2": "extracted_or_converted_value",
279
- "optional_param": "value_if_applicable"
280
- }},
281
- "missing_required": ["list", "of", "missing", "required", "parameters"],
282
- "confidence": 0.95
283
- }}
284
-
285
- === CRITICAL RULES ===
286
- 1. ONLY select endpoints that exist in the provided documentation
287
- 2. NEVER fabricate or assume endpoint parameters not in documentation
288
- 3. ALL required parameters MUST be included or listed as missing
289
- 4. Convert dates/times to ISO 8601 format (YYYY-MM-DDTHH:MM:SS)
290
- 5. If patient_id is required and not provided, add it to missing_required
291
- 6. Match endpoints by PURPOSE, not just keywords in the path
292
- 7. If multiple endpoints could work, choose the most specific one
293
- 8. If no endpoint matches, set endpoint to null and explain in reasoning
294
-
295
- === EXAMPLES OF GOOD MATCHING ===
296
- - User wants "patient records" → Use patient retrieval endpoint, not general search
297
- - User wants to "schedule appointment" → Use appointment creation endpoint
298
- - User asks "what appointments today" → Use appointment listing with date filter
299
- - User wants to "update medication" → Use medication update endpoint with patient_id
300
-
301
- Think step by step and be precise with your endpoint selection and parameter extraction.
302
- """,
303
- input_variables=["endpoints_documentation", "user_query", "detected_language",
304
- "extracted_keywords", "sentiment_analysis"],
305
- partial_variables={"format_instructions": self.json_parser.get_format_instructions()}
306
- )
307
-
308
- # # Create user-friendly response template with enhanced context awareness
309
- # self.user_response_template = PromptTemplate(
310
- # template="""
311
- # You are a professional and friendly virtual assistant for a healthcare system.
312
- # Your task is to generate clear, concise, and professional responses to user queries.
313
-
314
- # IMPORTANT RULES:
315
- # - Respond ONLY in {detected_language}
316
- # - For Arabic, use Modern Standard Arabic (فصحى)
317
- # - Keep responses SHORT and DIRECT
318
- # - Include ONLY essential information
319
- # - NEVER mix languages
320
- # - ALWAYS use the EXACT data from the system response
321
- # - NEVER make up or modify hospital information
322
- # - Use professional and polite tone
323
-
324
- # Original query: {user_query}
325
- # System result: {api_response}
326
- # User sentiment: {sentiment_analysis}
327
-
328
- # ARABIC RESPONSE RULES:
329
- # - Use Arabic numbers (١، ٢، ٣)
330
- # - Use proper date format (١٥ مايو ٢٠٢٥)
331
- # - Use proper time format (الساعة ٨ صباحاً)
332
- # - Use formal medical terms
333
- # - Keep sentences short and clear
334
- # - Use exact hospital names and addresses from the data
335
- # - Use exact working hours from the data
336
- # - Use professional healthcare terminology
337
-
338
- # ENGLISH RESPONSE RULES:
339
- # - Use clear, direct language
340
- # - Include only essential details
341
- # - Use proper medical terms
342
- # - Keep responses concise
343
- # - Use exact hospital names and addresses from the data
344
- # - Use exact working hours from the data
345
- # - Use professional healthcare terminology
346
-
347
- # Remember:
348
- # - Keep responses SHORT and FOCUSED
349
- # - Use ONLY data from the system response
350
- # - NEVER modify or make up hospital information
351
- # - Include only what's necessary to answer the query
352
- # - Maintain professional and polite tone
353
- # - Use proper healthcare terminology
354
- # """,
355
- # input_variables=["user_query", "api_response", "detected_language",
356
- # "sentiment_analysis", "extracted_keywords"]
357
- # )
358
- # Create user-friendly response template with enhanced context awareness
359
- # Create user-friendly response template with enhanced context awareness
360
- # Create user-friendly response template with enhanced context awareness
361
- self.user_response_template = PromptTemplate(
362
- template="""
363
- You are a professional healthcare assistant. Generate clear, accurate responses using EXACT data from the system.
364
-
365
- === STRICT REQUIREMENTS ===
366
- - Respond ONLY in {detected_language}
367
- - Use EXACT information from api_response - NO modifications
368
- - Keep responses SHORT, SIMPLE, and DIRECT
369
- - Use professional healthcare tone
370
- - NEVER mix languages or make up information
371
-
372
- === ORIGINAL REQUEST ===
373
- User Query: {user_query}
374
- User Sentiment: {sentiment_analysis}
375
-
376
- === SYSTEM DATA ===
377
- {api_response}
378
-
379
- === LANGUAGE-SPECIFIC FORMATTING ===
380
-
381
- FOR ARABIC RESPONSES:
382
- - Use Modern Standard Arabic (الفصحى)
383
- - Use Arabic numerals: ١، ٢، ٣، ٤، ٥، ٦، ٧، ٨، ٩، ١٠
384
- - Time format: "من الساعة ٨:٠٠ صباحاً إلى ٥:٠٠ مساءً"
385
- - Date format: "١٥ مايو ٢٠٢٥"
386
- - Use proper Arabic medical terminology
387
- - Keep sentences short and grammatically correct
388
- - Example format for hospitals:
389
- "مستشفى [الاسم] - العنوان: [العنوان الكامل] - أوقات العمل: من [الوقت] إلى [الوقت]"
390
-
391
- FOR ENGLISH RESPONSES:
392
- - Use clear, professional language
393
- - Time format: "8:00 AM to 5:00 PM"
394
- - Date format: "May 15, 2025"
395
- - Keep sentences concise and direct
396
- - Example format for hospitals:
397
- "[Hospital Name] - Address: [Full Address] - Hours: [Opening Time] to [Closing Time]"
398
-
399
- === RESPONSE STRUCTURE ===
400
- 1. Direct answer to the user's question
401
- 2. Essential details only (names, addresses, hours, contact info)
402
- 3. Brief helpful note if needed
403
- 4. No unnecessary introductions or conclusions
404
-
405
- === CRITICAL RULES ===
406
- - Extract information EXACTLY as provided in api_response
407
- - Do NOT include technical URLs, IDs, or system codes in the response
408
- - Do NOT show raw links or booking URLs to users
409
- - Present information in natural, conversational language
410
- - Do NOT use bullet points or technical formatting
411
- - Write as if you're speaking to the patient directly
412
- - If data is missing, state "المعلومات غير متوفرة" (Arabic) or "Information not available" (English)
413
- - Convert technical data into human-readable format
414
- - NEVER add translations or explanations in other languages
415
- - NEVER include "Translated response" or similar phrases
416
- - END your response immediately after providing the requested information
417
- - Do NOT add any English translation when responding in Arabic
418
- - Do NOT add any Arabic translation when responding in English
419
-
420
- === HUMAN-LIKE FORMATTING RULES ===
421
- FOR ARABIC:
422
- - Instead of "رابط الحجز: [URL]" → say "تم حجز موعدك بنجاح"
423
- - Instead of "الأزمة: غير متوفرة" → omit or say "بدون أعراض محددة"
424
- - Use natural sentences like "موعدك مع الدكتور [Name] يوم [Date] في تمام الساعة [Time]"
425
- - Avoid technical terms and system language
426
-
427
- FOR ENGLISH:
428
- - Instead of "Booking URL: [link]" → say "Your appointment has been scheduled"
429
- - Use natural sentences like "You have an appointment with Dr. [Name] on [Date] at [Time]"
430
- - Avoid showing raw URLs, IDs, or technical data
431
-
432
- === QUALITY CHECKS ===
433
- Before responding, verify:
434
- ✓ Response sounds natural and conversational
435
- ✓ No technical URLs, IDs, or system codes are shown
436
- ✓ Information is presented in human-friendly language
437
- ✓ Grammar is correct in the target language
438
- ✓ Response directly answers the user's question
439
- ✓ No bullet points or technical formatting
440
- ✓ Sounds like a helpful human assistant, not a system
441
-
442
- Generate a response that is accurate, helpful, and professionally formatted.
443
-
444
- === FINAL INSTRUCTION ===
445
- Respond ONLY in the requested language. Do NOT provide translations, explanations, or additional text in any other language. Stop immediately after answering the user's question.
446
- """,
447
- input_variables=["user_query", "api_response", "detected_language",
448
- "sentiment_analysis", "extracted_keywords"]
449
- )
450
-
451
- # Create LLM chains
452
- self.router_chain = LLMChain(
453
- llm=self.llm,
454
- prompt=self.router_prompt_template,
455
- output_key="route_result"
456
- )
457
-
458
- self.user_response_chain = LLMChain(
459
- llm=self.llm,
460
- prompt=self.user_response_template,
461
- output_key="user_friendly_response"
462
- )
463
-
464
- def parse_relative_date(self, text, detected_language):
465
- """
466
- Parse relative dates from text using a combination of methods
467
- """
468
- today = datetime.now()
469
-
470
- # Handle common relative date patterns in English and Arabic
471
- tomorrow_patterns = {
472
- 'english': [r'\btomorrow\b', r'\bnext day\b'],
473
- 'arabic': [r'\bغدا\b', r'\bبكرة\b', r'\bغدًا\b', r'\bالغد\b']
474
- }
475
-
476
- next_week_patterns = {
477
- 'english': [r'\bnext week\b'],
478
- 'arabic': [r'\bالأسبوع القادم\b', r'\bالأسبوع المقبل\b', r'\bالاسبوع الجاي\b']
479
- }
480
-
481
- # Check for "tomorrow" patterns
482
- for pattern in tomorrow_patterns.get(detected_language, []) + tomorrow_patterns.get('english', []):
483
- if re.search(pattern, text, re.IGNORECASE):
484
- return (today + timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S')
485
-
486
- # Check for "next week" patterns
487
- for pattern in next_week_patterns.get(detected_language, []) + next_week_patterns.get('english', []):
488
- if re.search(pattern, text, re.IGNORECASE):
489
- return (today + timedelta(days=7)).strftime('%Y-%m-%dT%H:%M:%S')
490
-
491
- # If NER model is available, use it to extract date entities
492
- if self.date_parser and detected_language == 'english':
493
- try:
494
- date_entities = self.date_parser(text)
495
- for entity in date_entities:
496
- if entity['entity_group'] == 'DATE':
497
- # Here you would need more complex date parsing logic
498
- # This is just a placeholder
499
- print(f"Found date entity: {entity['word']}")
500
- # For now, just default to tomorrow if we detect any date
501
- return (today + timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S')
502
- except Exception as e:
503
- print(f"Error in date parsing: {e}")
504
-
505
- # Default return None if no date pattern is recognized
506
- return None
507
-
508
- def process_user_query(self, user_query: str) -> Dict[str, Any]:
509
- """
510
- Process the user query through the LangChain pipeline and return a response
511
- """
512
- try:
513
- start_time = time.time()
514
-
515
- # Detect language of the query
516
- detected_language = self.detect_language(user_query)
517
- print(f"Detected language: {detected_language}")
518
-
519
- # Enhanced context using Hugging Face models
520
- sentiment_result = self.analyze_sentiment(user_query)
521
- print(f"Sentiment analysis: {sentiment_result}")
522
-
523
- extracted_keywords = self.extract_semantic_keywords(user_query)
524
- print(f"Extracted keywords: {extracted_keywords}")
525
-
526
- # Try to extract dates from query
527
- parsed_date = self.parse_relative_date(user_query, detected_language)
528
- if parsed_date:
529
- print(f"Parsed relative date: {parsed_date}")
530
-
531
- # 1. Route the query to determine which API endpoint to call
532
- router_result = self.router_chain.invoke({
533
- "endpoints_documentation": json.dumps(self.endpoints_documentation, indent=2),
534
- "user_query": user_query,
535
- "detected_language": detected_language,
536
- "extracted_keywords": ", ".join(extracted_keywords),
537
- "sentiment_analysis": json.dumps(sentiment_result)
538
- })
539
-
540
- # 2. Parse the router response
541
- route_result = router_result["route_result"]
542
- parsed_route = None
543
-
544
- # Clean the response first
545
- cleaned_response = route_result
546
-
547
- # Remove any comments (both single-line and multi-line)
548
- cleaned_response = re.sub(r'//.*?$', '', cleaned_response, flags=re.MULTILINE)
549
- cleaned_response = re.sub(r'/\*.*?\*/', '', cleaned_response, flags=re.DOTALL)
550
-
551
- # Remove any trailing commas
552
- cleaned_response = re.sub(r',(\s*[}\]])', r'\1', cleaned_response)
553
-
554
- # Try different methods to parse the JSON response
555
- try:
556
- # First attempt: direct JSON parsing of cleaned response
557
- parsed_route = json.loads(cleaned_response)
558
- except json.JSONDecodeError:
559
- try:
560
- # Second attempt: extract JSON from markdown code block
561
- json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', cleaned_response, re.DOTALL)
562
- if json_match:
563
- parsed_route = json.loads(json_match.group(1))
564
- except (json.JSONDecodeError, AttributeError):
565
- try:
566
- # Third attempt: find JSON-like content using regex
567
- json_pattern = r'\{\s*"endpoint"\s*:.*?\}'
568
- json_match = re.search(json_pattern, cleaned_response, re.DOTALL)
569
- if json_match:
570
- json_str = json_match.group(0)
571
- # Additional cleaning for the extracted JSON
572
- json_str = re.sub(r'//.*?$', '', json_str, flags=re.MULTILINE)
573
- json_str = re.sub(r',(\s*[}\]])', r'\1', json_str)
574
- parsed_route = json.loads(json_str)
575
- except (json.JSONDecodeError, AttributeError):
576
- print(f"Failed to parse JSON. Raw response: {route_result}")
577
- print(f"Cleaned response: {cleaned_response}")
578
- raise ValueError("Could not extract valid JSON from LLM response")
579
-
580
- if not parsed_route:
581
- raise ValueError("Failed to parse LLM response into valid JSON")
582
-
583
- # Replace any placeholder values and inject parsed dates if available
584
- if 'params' in parsed_route:
585
- if 'patient_id' in parsed_route['params']:
586
- parsed_route['params']['patient_id'] = self.user_id
587
-
588
- # Inject parsed date if available and a date parameter exists
589
- date_params = ['appointment_date', 'date', 'schedule_date', 'date_time', 'new_date_time']
590
- if parsed_date:
591
- for param in date_params:
592
- if param in parsed_route['params']:
593
- parsed_route['params'][param] = parsed_date
594
-
595
- print('Parsed route: ', parsed_route)
596
- print(f"Routing completed in {time.time() - start_time:.2f} seconds")
597
-
598
- # 3. Make the backend API call
599
- backend_response = self.backend_call(parsed_route)
600
-
601
- # 4. Generate user-friendly response
602
- user_friendly_result = self.user_response_chain.invoke({
603
- "user_query": user_query,
604
- "api_response": json.dumps(backend_response, indent=2),
605
- "detected_language": detected_language,
606
- "sentiment_analysis": json.dumps(sentiment_result),
607
- "extracted_keywords": ", ".join(extracted_keywords)
608
- })
609
- print('user response: ', user_friendly_result["user_friendly_response"])
610
-
611
- print(f"Total processing time: {time.time() - start_time:.2f} seconds")
612
-
613
- return {
614
- "routing_info": parsed_route,
615
- "api_response": backend_response,
616
- "user_friendly_response": user_friendly_result["user_friendly_response"],
617
- "detected_language": detected_language,
618
- "sentiment": sentiment_result,
619
- "keywords": extracted_keywords
620
- }
621
-
622
- except Exception as e:
623
- error_detail = {
624
- "error": f"Error processing query: {str(e)}",
625
- "type": type(e).__name__,
626
- "traceback": traceback.format_exc()
627
- }
628
- print(f"Error: {error_detail['error']}")
629
- print(f"Traceback: {error_detail['traceback']}")
630
- return error_detail
631
-
632
- def backend_call(self, data: Dict[str, Any]) -> Dict[str, Any]:
633
- """
634
- Make the actual API call to the backend with retry logic
635
- """
636
- endpoint_url = data.get('endpoint')
637
- endpoint_method = data.get('method')
638
- endpoint_params = data.get('params', {}).copy() # Create a copy to avoid modifying the original
639
-
640
- print('Endpoint url: ' + endpoint_url)
641
- print('Method: ', endpoint_method)
642
- print('Params: ', endpoint_params)
643
-
644
- # Add retry logic for more robust API calls
645
- retries = 0
646
- while retries < self.max_retries:
647
- try:
648
- if endpoint_method.upper() == 'GET':
649
- response = requests.get(
650
- self.BASE_URL + endpoint_url,
651
- params=endpoint_params,
652
- headers=self.headers,
653
- timeout=10 # Add timeout for backend calls
654
- )
655
- elif endpoint_method.upper() == 'POST': # POST or other methods
656
- response = requests.post(
657
- self.BASE_URL + endpoint_url,
658
- json=endpoint_params,
659
- headers=self.headers,
660
- timeout=10
661
- )
662
- elif endpoint_method.upper() == 'PUT':
663
- response = requests.put(
664
- self.BASE_URL + endpoint_url,
665
- json=endpoint_params,
666
- headers=self.headers,
667
- timeout=10
668
- )
669
-
670
- # Check if response status is success
671
- response.raise_for_status()
672
- return response.json()
673
-
674
- except requests.exceptions.RequestException as e:
675
- retries += 1
676
- if retries >= self.max_retries:
677
- return {
678
- "error": "Backend API call failed after multiple retries",
679
- "details": str(e),
680
- "status_code": getattr(e.response, 'status_code', None) if hasattr(e, 'response') else None
681
- }
682
-
683
- print(f"API call attempt {retries} failed, retrying in {self.retry_delay} seconds...")
684
- time.sleep(self.retry_delay)
685
-
686
-
687
- # Initialize the AI agent singleton
688
- # ai_agent = AIAgent()
689
-
690
-
691
- # Test the agent directly
692
- # if __name__ == "__main__":
693
- # agent = AIAgent()
694
-
695
- # # Test with English query
696
- # # print("\n---Testing English Query---")
697
- # # english_response = agent.process_user_query("I need to book an appointment with Dr. Smith tomorrow at 8 PM")
698
- # # print("\nEnglish response:")
699
- # # print(english_response["user_friendly_response"])
700
-
701
- # # Test with Arabic query
702
- # print("\n---Testing Arabic Query---")
703
- # # arabic_response = agent.process_user_query(" اريد الغاء الحجز مع الدكتور Smith")
704
- # arabic_response = agent.process_user_query("اريد حجز ميعاد غدا في الساعه الثامنه مساء مع الدكتور Smith")
705
- # # arabic_response = agent.process_user_query("متى يفتح المستشفى؟")
706
- # # arabic_response = agent.process_user_query("اريد معرفه كل الحجوزات الخاصه بي")
707
- # print("\nArabic response:")
708
- # print(arabic_response["user_friendly_response"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
main.py CHANGED
@@ -1,7 +1,712 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from fastapi import FastAPI, HTTPException
2
  from pydantic import BaseModel
3
  from typing import Dict, Any, Optional
4
- from final import AIAgent
5
 
6
  app = FastAPI(
7
  title="Healthcare AI Assistant",
 
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
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
+ # os.environ["HF_HOME"] = "/tmp/huggingface"
31
+ os.environ["HF_HOME"] = "/tmp/huggingface"
32
+ os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1"
33
+
34
+ class EndpointRequest(BaseModel):
35
+ """Data model for API endpoint requests"""
36
+ endpoint: str = Field(..., description="The API endpoint path to call")
37
+ method: str = Field(..., description="The HTTP method to use (GET or POST)")
38
+ params: Dict[str, Any] = Field(default_factory=dict, description="Parameters for the API call")
39
+ missing_required: List[str] = Field(default_factory=list, description="Any required parameters that are missing")
40
+
41
+
42
+ class AIAgent:
43
+ def __init__(self):
44
+ self.endpoints_documentation = endpoints_documentation
45
+ self.ollama_base_url = "http://localhost:11434" # Default Ollama URL
46
+ self.model_name = "mistral" # Using mistral model for better multilingual support
47
+ # self.model_name = 'llama3'
48
+ self.BASE_URL = 'https://agent.serveo.net'
49
+ self.headers = {
50
+ 'Content-type': 'application/json'
51
+ }
52
+ self.user_id = 'd8507df8-cec6-49f9-adcc-367b13805e73'
53
+ self.max_retries = 3
54
+ self.retry_delay = 2 # seconds
55
+
56
+ # Enhanced language detection using HuggingFace models
57
+ self._initialize_language_tools()
58
+
59
+ # Initialize LangChain components
60
+ self._initialize_llm()
61
+ self._initialize_parsers_and_chains()
62
+
63
+ # Add date parsing capabilities
64
+ self._initialize_date_parser()
65
+
66
+ def _initialize_language_tools(self):
67
+ """Initialize more sophisticated language processing tools"""
68
+ # Use multilingual embeddings for semantic understanding
69
+ self.embeddings = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-large")
70
+
71
+ # Initialize language identification model
72
+ try:
73
+ self.language_classifier = pipeline(
74
+ "text-classification",
75
+ model="papluca/xlm-roberta-base-language-detection",
76
+ top_k=1
77
+ )
78
+ print("Language classification model loaded successfully")
79
+ except Exception as e:
80
+ print(f"Failed to load language classification model: {e}")
81
+ # Fallback to basic regex detection if model fails to load
82
+ self.language_classifier = None
83
+
84
+ # Add sentiment analysis for enhanced response generation
85
+ try:
86
+ self.sentiment_analyzer = pipeline(
87
+ "sentiment-analysis",
88
+ model="cardiffnlp/twitter-xlm-roberta-base-sentiment"
89
+ )
90
+ print("Sentiment analysis model loaded successfully")
91
+ except Exception as e:
92
+ print(f"Failed to load sentiment analysis model: {e}")
93
+ self.sentiment_analyzer = None
94
+
95
+ def _initialize_date_parser(self):
96
+ """Initialize date parsing model for handling relative date expressions"""
97
+ try:
98
+ self.date_parser = pipeline(
99
+ "token-classification",
100
+ model="Jean-Baptiste/roberta-large-ner-english",
101
+ aggregation_strategy="simple"
102
+ )
103
+ print("Date parsing model loaded successfully")
104
+ except Exception as e:
105
+ print(f"Failed to load date parsing model: {e}")
106
+ self.date_parser = None
107
+
108
+ def detect_language(self, text):
109
+ """
110
+ Enhanced language detection using HuggingFace models
111
+ """
112
+ # First try using the HuggingFace language classification model if available
113
+ if self.language_classifier and len(text.strip()) > 3:
114
+ try:
115
+ result = self.language_classifier(text)
116
+ detected_lang = result[0][0]['label']
117
+ confidence = result[0][0]['score']
118
+
119
+ print(f"Language detected: {detected_lang} with confidence {confidence:.4f}")
120
+
121
+ # Map the detected language to our simplified language set
122
+ if detected_lang in ['ar', 'arabic']:
123
+ return "arabic"
124
+ elif detected_lang in ['en', 'english']:
125
+ return "english"
126
+ elif confidence > 0.8: # If confident but not English/Arabic
127
+ # We currently only support English/Arabic, but log other languages
128
+ print(f"Detected unsupported language: {detected_lang}")
129
+ # Default to English for other languages for now
130
+ return "english"
131
+ except Exception as e:
132
+ print(f"Error in language detection model: {e}")
133
+ # Continue to fallback methods
134
+
135
+ # Fallback: Basic detection of Arabic text using regex
136
+ arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]+')
137
+ if arabic_pattern.search(text):
138
+ return "arabic"
139
+
140
+ # Default to English
141
+ return "english"
142
+
143
+ def analyze_sentiment(self, text):
144
+ """Analyze the sentiment of the input text"""
145
+ if self.sentiment_analyzer and len(text.strip()) > 3:
146
+ try:
147
+ result = self.sentiment_analyzer(text)
148
+ sentiment = result[0]['label']
149
+ score = result[0]['score']
150
+ return {
151
+ "sentiment": sentiment,
152
+ "score": score
153
+ }
154
+ except Exception as e:
155
+ print(f"Error in sentiment analysis: {e}")
156
+
157
+ # Default neutral sentiment if analysis fails
158
+ return {"sentiment": "NEUTRAL", "score": 0.5}
159
+
160
+ def extract_semantic_keywords(self, text, top_n=5):
161
+ """Extract semantic keywords from text using embeddings"""
162
+ try:
163
+ # Simple keyword extraction using embeddings comparison
164
+ # This is a basic implementation - could be enhanced further
165
+ words = re.findall(r'\b\w+\b', text.lower())
166
+ unique_words = list(set([w for w in words if len(w) > 3]))
167
+
168
+ if not unique_words:
169
+ return []
170
+
171
+ # Get embeddings for all words
172
+ embeddings_list = []
173
+ for word in unique_words:
174
+ try:
175
+ emb = self.embeddings.embed_query(word)
176
+ embeddings_list.append((word, emb))
177
+ except Exception as e:
178
+ print(f"Error embedding word {word}: {e}")
179
+
180
+ # Get embedding for full text
181
+ text_embedding = self.embeddings.embed_query(text)
182
+
183
+ # Calculate similarity to full text
184
+ similarities = []
185
+ for word, emb in embeddings_list:
186
+ similarity = np.dot(emb, text_embedding) / (np.linalg.norm(emb) * np.linalg.norm(text_embedding))
187
+ similarities.append((word, similarity))
188
+
189
+ # Sort by similarity
190
+ similarities.sort(key=lambda x: x[1], reverse=True)
191
+
192
+ # Return top N keywords
193
+ return [word for word, _ in similarities[:top_n]]
194
+
195
+ except Exception as e:
196
+ print(f"Error extracting keywords: {e}")
197
+ return []
198
+
199
+ def _initialize_llm(self):
200
+ """Initialize the LLM with appropriate configuration"""
201
+ # Set up the callback manager for streaming (optional)
202
+ callbacks = [StreamingStdOutCallbackHandler()]
203
+
204
+ # Initialize the Ollama LLM with updated parameters
205
+ self.llm = OllamaLLM(
206
+ model=self.model_name,
207
+ base_url=self.ollama_base_url,
208
+ callbacks=callbacks,
209
+ temperature=0.7,
210
+ num_ctx=8192, # Increased context window
211
+ top_p=0.9,
212
+ request_timeout=60, # Timeout in seconds
213
+ )
214
+
215
+ def _initialize_parsers_and_chains(self):
216
+ """Initialize output parsers and LLM chains"""
217
+ # Setup JSON parser for structured output
218
+ self.json_parser = JsonOutputParser(pydantic_object=EndpointRequest)
219
+
220
+ # Create multilingual router prompt template with enhanced context
221
+ self.router_prompt_template = PromptTemplate(
222
+ template="""
223
+ You are a precise API routing assistant. Your job is to analyze user queries and select the correct API endpoint with proper parameters.
224
+
225
+ === ENDPOINT DOCUMENTATION ===
226
+ {endpoints_documentation}
227
+
228
+ === USER REQUEST ANALYSIS ===
229
+ User Query: {user_query}
230
+ Language: {detected_language}
231
+ Keywords: {extracted_keywords}
232
+ Sentiment: {sentiment_analysis}
233
+
234
+ === ROUTING PROCESS ===
235
+ Follow these steps in order:
236
+
237
+ STEP 1: INTENT ANALYSIS
238
+ - What is the user trying to accomplish?
239
+ - What type of operation are they requesting? (create, read, update, delete, search, etc.)
240
+ - What entity/resource are they working with?
241
+
242
+ STEP 2: ENDPOINT MATCHING
243
+ - Review each endpoint in the documentation
244
+ - Match the user's intent to the endpoint's PURPOSE/DESCRIPTION
245
+ - Consider the HTTP method (GET for retrieval, POST for creation, etc.)
246
+ - Verify the endpoint can handle the user's specific request
247
+
248
+ STEP 3: PARAMETER EXTRACTION
249
+ - Identify ALL required parameters from the endpoint documentation
250
+ - Extract parameter values from the user query
251
+ - Convert data types as needed (dates to ISO 8601, numbers to integers, etc.)
252
+ - Set appropriate defaults for optional parameters if beneficial
253
+
254
+ STEP 4: VALIDATION
255
+ - Ensure ALL required parameters are provided or identified as missing
256
+ - Verify parameter formats match documentation requirements
257
+ - Check that the selected endpoint actually solves the user's problem
258
+
259
+ === RESPONSE FORMAT ===
260
+ Provide your analysis and decision in this exact JSON structure:
261
+
262
+ {{
263
+ "reasoning": {{
264
+ "user_intent": "Brief description of what the user wants to accomplish",
265
+ "selected_endpoint": "Why this endpoint was chosen over others",
266
+ "parameter_mapping": "How user query maps to endpoint parameters"
267
+ }},
268
+ "endpoint": "/exact_endpoint_path_from_documentation",
269
+ "method": "HTTP_METHOD",
270
+ "params": {{
271
+ "required_param_1": "extracted_or_converted_value",
272
+ "required_param_2": "extracted_or_converted_value",
273
+ "optional_param": "value_if_applicable"
274
+ }},
275
+ "missing_required": ["list", "of", "missing", "required", "parameters"],
276
+ "confidence": 0.95
277
+ }}
278
+
279
+ === CRITICAL RULES ===
280
+ 1. ONLY select endpoints that exist in the provided documentation
281
+ 2. NEVER fabricate or assume endpoint parameters not in documentation
282
+ 3. ALL required parameters MUST be included or listed as missing
283
+ 4. Convert dates/times to ISO 8601 format (YYYY-MM-DDTHH:MM:SS)
284
+ 5. If patient_id is required and not provided, add it to missing_required
285
+ 6. Match endpoints by PURPOSE, not just keywords in the path
286
+ 7. If multiple endpoints could work, choose the most specific one
287
+ 8. If no endpoint matches, set endpoint to null and explain in reasoning
288
+
289
+ === EXAMPLES OF GOOD MATCHING ===
290
+ - User wants "patient records" → Use patient retrieval endpoint, not general search
291
+ - User wants to "schedule appointment" → Use appointment creation endpoint
292
+ - User asks "what appointments today" → Use appointment listing with date filter
293
+ - User wants to "update medication" → Use medication update endpoint with patient_id
294
+
295
+ Think step by step and be precise with your endpoint selection and parameter extraction.
296
+ """,
297
+ input_variables=["endpoints_documentation", "user_query", "detected_language",
298
+ "extracted_keywords", "sentiment_analysis"],
299
+ partial_variables={"format_instructions": self.json_parser.get_format_instructions()}
300
+ )
301
+
302
+ # # Create user-friendly response template with enhanced context awareness
303
+ # self.user_response_template = PromptTemplate(
304
+ # template="""
305
+ # You are a professional and friendly virtual assistant for a healthcare system.
306
+ # Your task is to generate clear, concise, and professional responses to user queries.
307
+
308
+ # IMPORTANT RULES:
309
+ # - Respond ONLY in {detected_language}
310
+ # - For Arabic, use Modern Standard Arabic (فصحى)
311
+ # - Keep responses SHORT and DIRECT
312
+ # - Include ONLY essential information
313
+ # - NEVER mix languages
314
+ # - ALWAYS use the EXACT data from the system response
315
+ # - NEVER make up or modify hospital information
316
+ # - Use professional and polite tone
317
+
318
+ # Original query: {user_query}
319
+ # System result: {api_response}
320
+ # User sentiment: {sentiment_analysis}
321
+
322
+ # ARABIC RESPONSE RULES:
323
+ # - Use Arabic numbers (١، ٢، ٣)
324
+ # - Use proper date format (١٥ مايو ٢٠٢٥)
325
+ # - Use proper time format (الساعة ٨ صباحاً)
326
+ # - Use formal medical terms
327
+ # - Keep sentences short and clear
328
+ # - Use exact hospital names and addresses from the data
329
+ # - Use exact working hours from the data
330
+ # - Use professional healthcare terminology
331
+
332
+ # ENGLISH RESPONSE RULES:
333
+ # - Use clear, direct language
334
+ # - Include only essential details
335
+ # - Use proper medical terms
336
+ # - Keep responses concise
337
+ # - Use exact hospital names and addresses from the data
338
+ # - Use exact working hours from the data
339
+ # - Use professional healthcare terminology
340
+
341
+ # Remember:
342
+ # - Keep responses SHORT and FOCUSED
343
+ # - Use ONLY data from the system response
344
+ # - NEVER modify or make up hospital information
345
+ # - Include only what's necessary to answer the query
346
+ # - Maintain professional and polite tone
347
+ # - Use proper healthcare terminology
348
+ # """,
349
+ # input_variables=["user_query", "api_response", "detected_language",
350
+ # "sentiment_analysis", "extracted_keywords"]
351
+ # )
352
+ # Create user-friendly response template with enhanced context awareness
353
+ # Create user-friendly response template with enhanced context awareness
354
+ # Create user-friendly response template with enhanced context awareness
355
+ self.user_response_template = PromptTemplate(
356
+ template="""
357
+ You are a professional healthcare assistant. Generate clear, accurate responses using EXACT data from the system.
358
+
359
+ === STRICT REQUIREMENTS ===
360
+ - Respond ONLY in {detected_language}
361
+ - Use EXACT information from api_response - NO modifications
362
+ - Keep responses SHORT, SIMPLE, and DIRECT
363
+ - Use professional healthcare tone
364
+ - NEVER mix languages or make up information
365
+
366
+ === ORIGINAL REQUEST ===
367
+ User Query: {user_query}
368
+ User Sentiment: {sentiment_analysis}
369
+
370
+ === SYSTEM DATA ===
371
+ {api_response}
372
+
373
+ === LANGUAGE-SPECIFIC FORMATTING ===
374
+
375
+ FOR ARABIC RESPONSES:
376
+ - Use Modern Standard Arabic (الفصحى)
377
+ - Use Arabic numerals: ١، ٢، ٣، ٤، ٥، ٦، ٧، ٨، ٩، ١٠
378
+ - Time format: "من الساعة ٨:٠٠ صباحاً إلى ٥:٠٠ مساءً"
379
+ - Date format: "١٥ مايو ٢٠٢٥"
380
+ - Use proper Arabic medical terminology
381
+ - Keep sentences short and grammatically correct
382
+ - Example format for hospitals:
383
+ "مستشفى [الاسم] - العنوان: [العنوان الكامل] - أوقات العمل: من [الوقت] إلى [الوقت]"
384
+
385
+ FOR ENGLISH RESPONSES:
386
+ - Use clear, professional language
387
+ - Time format: "8:00 AM to 5:00 PM"
388
+ - Date format: "May 15, 2025"
389
+ - Keep sentences concise and direct
390
+ - Example format for hospitals:
391
+ "[Hospital Name] - Address: [Full Address] - Hours: [Opening Time] to [Closing Time]"
392
+
393
+ === RESPONSE STRUCTURE ===
394
+ 1. Direct answer to the user's question
395
+ 2. Essential details only (names, addresses, hours, contact info)
396
+ 3. Brief helpful note if needed
397
+ 4. No unnecessary introductions or conclusions
398
+
399
+ === CRITICAL RULES ===
400
+ - Extract information EXACTLY as provided in api_response
401
+ - Do NOT include technical URLs, IDs, or system codes in the response
402
+ - Do NOT show raw links or booking URLs to users
403
+ - Present information in natural, conversational language
404
+ - Do NOT use bullet points or technical formatting
405
+ - Write as if you're speaking to the patient directly
406
+ - If data is missing, state "المعلومات غير متوفرة" (Arabic) or "Information not available" (English)
407
+ - Convert technical data into human-readable format
408
+ - NEVER add translations or explanations in other languages
409
+ - NEVER include "Translated response" or similar phrases
410
+ - END your response immediately after providing the requested information
411
+ - Do NOT add any English translation when responding in Arabic
412
+ - Do NOT add any Arabic translation when responding in English
413
+
414
+ === HUMAN-LIKE FORMATTING RULES ===
415
+ FOR ARABIC:
416
+ - Instead of "رابط الحجز: [URL]" → say "تم حجز موعدك بنجاح"
417
+ - Instead of "الأزمة: غير متوفرة" → omit or say "بدون أعراض محددة"
418
+ - Use natural sentences like "موعدك مع الدكتور [Name] يوم [Date] في تمام الساعة [Time]"
419
+ - Avoid technical terms and system language
420
+
421
+ FOR ENGLISH:
422
+ - Instead of "Booking URL: [link]" → say "Your appointment has been scheduled"
423
+ - Use natural sentences like "You have an appointment with Dr. [Name] on [Date] at [Time]"
424
+ - Avoid showing raw URLs, IDs, or technical data
425
+
426
+ === QUALITY CHECKS ===
427
+ Before responding, verify:
428
+ ✓ Response sounds natural and conversational
429
+ ✓ No technical URLs, IDs, or system codes are shown
430
+ ✓ Information is presented in human-friendly language
431
+ ✓ Grammar is correct in the target language
432
+ ✓ Response directly answers the user's question
433
+ ✓ No bullet points or technical formatting
434
+ ✓ Sounds like a helpful human assistant, not a system
435
+
436
+ Generate a response that is accurate, helpful, and professionally formatted.
437
+
438
+ === FINAL INSTRUCTION ===
439
+ Respond ONLY in the requested language. Do NOT provide translations, explanations, or additional text in any other language. Stop immediately after answering the user's question.
440
+ """,
441
+ input_variables=["user_query", "api_response", "detected_language",
442
+ "sentiment_analysis", "extracted_keywords"]
443
+ )
444
+
445
+ # Create LLM chains
446
+ self.router_chain = LLMChain(
447
+ llm=self.llm,
448
+ prompt=self.router_prompt_template,
449
+ output_key="route_result"
450
+ )
451
+
452
+ self.user_response_chain = LLMChain(
453
+ llm=self.llm,
454
+ prompt=self.user_response_template,
455
+ output_key="user_friendly_response"
456
+ )
457
+
458
+ def parse_relative_date(self, text, detected_language):
459
+ """
460
+ Parse relative dates from text using a combination of methods
461
+ """
462
+ today = datetime.now()
463
+
464
+ # Handle common relative date patterns in English and Arabic
465
+ tomorrow_patterns = {
466
+ 'english': [r'\btomorrow\b', r'\bnext day\b'],
467
+ 'arabic': [r'\bغدا\b', r'\bبكرة\b', r'\bغدًا\b', r'\bالغد\b']
468
+ }
469
+
470
+ next_week_patterns = {
471
+ 'english': [r'\bnext week\b'],
472
+ 'arabic': [r'\bالأسبوع القادم\b', r'\bالأسبوع المقبل\b', r'\bالاسبوع الجاي\b']
473
+ }
474
+
475
+ # Check for "tomorrow" patterns
476
+ for pattern in tomorrow_patterns.get(detected_language, []) + tomorrow_patterns.get('english', []):
477
+ if re.search(pattern, text, re.IGNORECASE):
478
+ return (today + timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S')
479
+
480
+ # Check for "next week" patterns
481
+ for pattern in next_week_patterns.get(detected_language, []) + next_week_patterns.get('english', []):
482
+ if re.search(pattern, text, re.IGNORECASE):
483
+ return (today + timedelta(days=7)).strftime('%Y-%m-%dT%H:%M:%S')
484
+
485
+ # If NER model is available, use it to extract date entities
486
+ if self.date_parser and detected_language == 'english':
487
+ try:
488
+ date_entities = self.date_parser(text)
489
+ for entity in date_entities:
490
+ if entity['entity_group'] == 'DATE':
491
+ # Here you would need more complex date parsing logic
492
+ # This is just a placeholder
493
+ print(f"Found date entity: {entity['word']}")
494
+ # For now, just default to tomorrow if we detect any date
495
+ return (today + timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S')
496
+ except Exception as e:
497
+ print(f"Error in date parsing: {e}")
498
+
499
+ # Default return None if no date pattern is recognized
500
+ return None
501
+
502
+ def process_user_query(self, user_query: str) -> Dict[str, Any]:
503
+ """
504
+ Process the user query through the LangChain pipeline and return a response
505
+ """
506
+ try:
507
+ start_time = time.time()
508
+
509
+ # Detect language of the query
510
+ detected_language = self.detect_language(user_query)
511
+ print(f"Detected language: {detected_language}")
512
+
513
+ # Enhanced context using Hugging Face models
514
+ sentiment_result = self.analyze_sentiment(user_query)
515
+ print(f"Sentiment analysis: {sentiment_result}")
516
+
517
+ extracted_keywords = self.extract_semantic_keywords(user_query)
518
+ print(f"Extracted keywords: {extracted_keywords}")
519
+
520
+ # Try to extract dates from query
521
+ parsed_date = self.parse_relative_date(user_query, detected_language)
522
+ if parsed_date:
523
+ print(f"Parsed relative date: {parsed_date}")
524
+
525
+ # 1. Route the query to determine which API endpoint to call
526
+ router_result = self.router_chain.invoke({
527
+ "endpoints_documentation": json.dumps(self.endpoints_documentation, indent=2),
528
+ "user_query": user_query,
529
+ "detected_language": detected_language,
530
+ "extracted_keywords": ", ".join(extracted_keywords),
531
+ "sentiment_analysis": json.dumps(sentiment_result)
532
+ })
533
+
534
+ # 2. Parse the router response
535
+ route_result = router_result["route_result"]
536
+ parsed_route = None
537
+
538
+ # Clean the response first
539
+ cleaned_response = route_result
540
+
541
+ # Remove any comments (both single-line and multi-line)
542
+ cleaned_response = re.sub(r'//.*?$', '', cleaned_response, flags=re.MULTILINE)
543
+ cleaned_response = re.sub(r'/\*.*?\*/', '', cleaned_response, flags=re.DOTALL)
544
+
545
+ # Remove any trailing commas
546
+ cleaned_response = re.sub(r',(\s*[}\]])', r'\1', cleaned_response)
547
+
548
+ # Try different methods to parse the JSON response
549
+ try:
550
+ # First attempt: direct JSON parsing of cleaned response
551
+ parsed_route = json.loads(cleaned_response)
552
+ except json.JSONDecodeError:
553
+ try:
554
+ # Second attempt: extract JSON from markdown code block
555
+ json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', cleaned_response, re.DOTALL)
556
+ if json_match:
557
+ parsed_route = json.loads(json_match.group(1))
558
+ except (json.JSONDecodeError, AttributeError):
559
+ try:
560
+ # Third attempt: find JSON-like content using regex
561
+ json_pattern = r'\{\s*"endpoint"\s*:.*?\}'
562
+ json_match = re.search(json_pattern, cleaned_response, re.DOTALL)
563
+ if json_match:
564
+ json_str = json_match.group(0)
565
+ # Additional cleaning for the extracted JSON
566
+ json_str = re.sub(r'//.*?$', '', json_str, flags=re.MULTILINE)
567
+ json_str = re.sub(r',(\s*[}\]])', r'\1', json_str)
568
+ parsed_route = json.loads(json_str)
569
+ except (json.JSONDecodeError, AttributeError):
570
+ print(f"Failed to parse JSON. Raw response: {route_result}")
571
+ print(f"Cleaned response: {cleaned_response}")
572
+ raise ValueError("Could not extract valid JSON from LLM response")
573
+
574
+ if not parsed_route:
575
+ raise ValueError("Failed to parse LLM response into valid JSON")
576
+
577
+ # Replace any placeholder values and inject parsed dates if available
578
+ if 'params' in parsed_route:
579
+ if 'patient_id' in parsed_route['params']:
580
+ parsed_route['params']['patient_id'] = self.user_id
581
+
582
+ # Inject parsed date if available and a date parameter exists
583
+ date_params = ['appointment_date', 'date', 'schedule_date', 'date_time', 'new_date_time']
584
+ if parsed_date:
585
+ for param in date_params:
586
+ if param in parsed_route['params']:
587
+ parsed_route['params'][param] = parsed_date
588
+
589
+ print('Parsed route: ', parsed_route)
590
+ print(f"Routing completed in {time.time() - start_time:.2f} seconds")
591
+
592
+ # 3. Make the backend API call
593
+ backend_response = self.backend_call(parsed_route)
594
+
595
+ # 4. Generate user-friendly response
596
+ user_friendly_result = self.user_response_chain.invoke({
597
+ "user_query": user_query,
598
+ "api_response": json.dumps(backend_response, indent=2),
599
+ "detected_language": detected_language,
600
+ "sentiment_analysis": json.dumps(sentiment_result),
601
+ "extracted_keywords": ", ".join(extracted_keywords)
602
+ })
603
+ print('user response: ', user_friendly_result["user_friendly_response"])
604
+
605
+ print(f"Total processing time: {time.time() - start_time:.2f} seconds")
606
+
607
+ return {
608
+ "routing_info": parsed_route,
609
+ "api_response": backend_response,
610
+ "user_friendly_response": user_friendly_result["user_friendly_response"],
611
+ "detected_language": detected_language,
612
+ "sentiment": sentiment_result,
613
+ "keywords": extracted_keywords
614
+ }
615
+
616
+ except Exception as e:
617
+ error_detail = {
618
+ "error": f"Error processing query: {str(e)}",
619
+ "type": type(e).__name__,
620
+ "traceback": traceback.format_exc()
621
+ }
622
+ print(f"Error: {error_detail['error']}")
623
+ print(f"Traceback: {error_detail['traceback']}")
624
+ return error_detail
625
+
626
+ def backend_call(self, data: Dict[str, Any]) -> Dict[str, Any]:
627
+ """
628
+ Make the actual API call to the backend with retry logic
629
+ """
630
+ endpoint_url = data.get('endpoint')
631
+ endpoint_method = data.get('method')
632
+ endpoint_params = data.get('params', {}).copy() # Create a copy to avoid modifying the original
633
+
634
+ print('Endpoint url: ' + endpoint_url)
635
+ print('Method: ', endpoint_method)
636
+ print('Params: ', endpoint_params)
637
+
638
+ # Add retry logic for more robust API calls
639
+ retries = 0
640
+ while retries < self.max_retries:
641
+ try:
642
+ if endpoint_method.upper() == 'GET':
643
+ response = requests.get(
644
+ self.BASE_URL + endpoint_url,
645
+ params=endpoint_params,
646
+ headers=self.headers,
647
+ timeout=10 # Add timeout for backend calls
648
+ )
649
+ elif endpoint_method.upper() == 'POST': # POST or other methods
650
+ response = requests.post(
651
+ self.BASE_URL + endpoint_url,
652
+ json=endpoint_params,
653
+ headers=self.headers,
654
+ timeout=10
655
+ )
656
+ elif endpoint_method.upper() == 'PUT':
657
+ response = requests.put(
658
+ self.BASE_URL + endpoint_url,
659
+ json=endpoint_params,
660
+ headers=self.headers,
661
+ timeout=10
662
+ )
663
+
664
+ # Check if response status is success
665
+ response.raise_for_status()
666
+ return response.json()
667
+
668
+ except requests.exceptions.RequestException as e:
669
+ retries += 1
670
+ if retries >= self.max_retries:
671
+ return {
672
+ "error": "Backend API call failed after multiple retries",
673
+ "details": str(e),
674
+ "status_code": getattr(e.response, 'status_code', None) if hasattr(e, 'response') else None
675
+ }
676
+
677
+ print(f"API call attempt {retries} failed, retrying in {self.retry_delay} seconds...")
678
+ time.sleep(self.retry_delay)
679
+
680
+
681
+ # Initialize the AI agent singleton
682
+ # ai_agent = AIAgent()
683
+
684
+
685
+ # Test the agent directly
686
+ # if __name__ == "__main__":
687
+ # agent = AIAgent()
688
+
689
+ # # Test with English query
690
+ # # print("\n---Testing English Query---")
691
+ # # english_response = agent.process_user_query("I need to book an appointment with Dr. Smith tomorrow at 8 PM")
692
+ # # print("\nEnglish response:")
693
+ # # print(english_response["user_friendly_response"])
694
+
695
+ # # Test with Arabic query
696
+ # print("\n---Testing Arabic Query---")
697
+ # # arabic_response = agent.process_user_query(" اريد الغاء الحجز مع الدكتور Smith")
698
+ # arabic_response = agent.process_user_query("اريد حجز ميعاد غدا في الساعه الثامنه مساء مع الدكتور Smith")
699
+ # # arabic_response = agent.process_user_query("متى يفتح المستشفى؟")
700
+ # # arabic_response = agent.process_user_query("اريد معرفه كل الحجوزات الخاصه بي")
701
+ # print("\nArabic response:")
702
+ # print(arabic_response["user_friendly_response"])
703
+
704
+
705
+ # Fast api section
706
  from fastapi import FastAPI, HTTPException
707
  from pydantic import BaseModel
708
  from typing import Dict, Any, Optional
709
+
710
 
711
  app = FastAPI(
712
  title="Healthcare AI Assistant",
testing.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from pydantic import BaseModel
3
+ from typing import Dict, Any, Optional
4
+ from final import AIAgent
5
+
6
+ app = FastAPI(
7
+ title="Healthcare AI Assistant",
8
+ description="An AI-powered healthcare assistant that handles appointment booking and queries",
9
+ version="1.0.0"
10
+ )
11
+
12
+ # Initialize the AI agent
13
+ agent = AIAgent()
14
+
15
+ class QueryRequest(BaseModel):
16
+ query: str
17
+ language: Optional[str] = None
18
+
19
+ class QueryResponse(BaseModel):
20
+ routing_info: Dict[str, Any]
21
+ api_response: Dict[str, Any]
22
+ user_friendly_response: str
23
+ detected_language: str
24
+ sentiment: Dict[str, Any]
25
+
26
+ @app.post("/query", response_model=QueryResponse)
27
+ async def process_query(request: QueryRequest):
28
+ """
29
+ Process a user query and return a response
30
+ """
31
+ try:
32
+ response = agent.process_user_query(request.query)
33
+ return response
34
+ except Exception as e:
35
+ raise HTTPException(status_code=500, detail=str(e))
36
+
37
+ @app.get("/health")
38
+ async def health_check():
39
+ """
40
+ Health check endpoint
41
+ """
42
+ return {"status": "healthy", "service": "healthcare-ai-assistant"}
43
+
44
+ @app.get("/")
45
+ async def root():
46
+ return {"message": "Hello World"}
47
+
48
+ if __name__ == "__main__":
49
+ import uvicorn
50
+ uvicorn.run(app, host="0.0.0.0", port=8000)