moazx's picture
Project setup
c5e1945
from langchain.tools.retriever import create_retriever_tool
from langchain_community.tools import TavilySearchResults
from langchain.tools import tool
from retriever import hybrid_retriever
import pytz
from datetime import datetime
from pydantic import BaseModel, Field
from typing import Optional
import json
import uuid
# Optimized retriever tool with faster settings
retriever_tool = create_retriever_tool(
hybrid_retriever,
name="company_knowledge_tool",
description="Fast tool for company info, history, products, and policies. Use only for Al Shifa Digital Healthcare questions."
)
# Optimized web search tool with reduced results for speed
websearch_tool = TavilySearchResults(
max_results=3,
include_answer=True,
include_raw_content=False,
include_images=False,
search_depth="basic",
name="Tavily_Search_Tool",
description="Fast search for medical questions and current info. Use for medical advice and up-to-date information."
)
@tool
def get_current_datetime_tool() -> str:
"""
Returns the current date, time, and day of the week for Saudi Arabia (Asia/Riyadh).
This is the only reliable source for date and time information. Use this tool
whenever a user asks about 'today', 'now', or any other time-sensitive query.
The output is in English but shows Saudi Arabia local time.
"""
try:
# Define the timezone for Saudi Arabia
saudi_tz = pytz.timezone('Asia/Riyadh')
# Get the current time in that timezone
now_saudi = datetime.now(saudi_tz)
# Manual mapping to ensure English output regardless of system locale
days_en = {
0: "Monday", 1: "Tuesday", 2: "Wednesday", 3: "Thursday",
4: "Friday", 5: "Saturday", 6: "Sunday"
}
months_en = {
1: "January", 2: "February", 3: "March", 4: "April",
5: "May", 6: "June", 7: "July", 8: "August",
9: "September", 10: "October", 11: "November", 12: "December"
}
# Get English names using manual mapping
day_name = days_en[now_saudi.weekday()]
month_name = months_en[now_saudi.month]
day = now_saudi.day
year = now_saudi.year
# Format time manually to avoid locale issues
hour = now_saudi.hour
minute = now_saudi.minute
# Convert to 12-hour format
if hour == 0:
hour_12 = 12
period = "AM"
elif hour < 12:
hour_12 = hour
period = "AM"
elif hour == 12:
hour_12 = 12
period = "PM"
else:
hour_12 = hour - 12
period = "PM"
time_str = f"{hour_12:02d}:{minute:02d} {period}"
# Create the final string
return f"Current date and time in Saudi Arabia: {day_name}, {month_name} {day}, {year} at {time_str}"
except Exception as e:
return f"Error getting current datetime: {str(e)}"
# --- Pydantic Model for Structured Tool Input ---
# All fields are now Optional to prevent validation errors at the agent level.
# The validation logic is moved inside the tool itself.
class BookingInput(BaseModel):
"""Inputs for the book_consultation tool."""
patient_name: Optional[str] = Field(None, description="The user's full name.")
age: Optional[str] = Field(None, description="The user's age.")
gender: Optional[str] = Field(None, description="The user's gender.")
contact_number: Optional[str] = Field(None, description="A phone number for confirmation.")
email: Optional[str] = Field(None, description="An email address for confirmation and reminders.")
reason_for_consultation: Optional[str] = Field(None, description="A brief description of the user's symptoms or reason for the visit.")
preferred_date: Optional[str] = Field(None, description="The user's desired date for the appointment.")
preferred_time: Optional[str] = Field(None, description="The user's desired time for the appointment.")
specialty: Optional[str] = Field(None, description="The specific medical field needed.")
doctor_preference: Optional[str] = Field(None, description="The name of a specific doctor, if requested.")
consultation_type: Optional[str] = Field(None, description="The method of consultation.")
@tool(args_schema=BookingInput)
def book_consultation_tool(patient_name: Optional[str] = None, age: Optional[str] = None, gender: Optional[str] = None, contact_number: Optional[str] = None, email: Optional[str] = None, reason_for_consultation: Optional[str] = None, preferred_date: Optional[str] = None, preferred_time: Optional[str] = None, consultation_type: Optional[str] = None, specialty: Optional[str] = None, doctor_preference: Optional[str] = None) -> str:
"""
Books a medical consultation. If required information is missing, it returns a message
asking for the missing details. Otherwise, it returns the booking data as a JSON object.
Email is optional and will be set to 'unknown@alshifa-care.com' if not provided.
"""
# --- 1. Internal Validation: Check for missing required information ---
missing_fields = []
if not patient_name:
missing_fields.append("اسم المريض الكامل (patient_name)")
if not age:
missing_fields.append("العمر (age)")
if not gender:
missing_fields.append("الجنس (gender)")
if not contact_number:
missing_fields.append("رقم التواصل (contact_number)")
if not reason_for_consultation:
missing_fields.append("سبب الاستشارة (reason_for_consultation)")
if not preferred_date:
missing_fields.append("التاريخ المفضل (preferred_date)")
if not preferred_time:
missing_fields.append("الوقت المفضل (preferred_time)")
# Set default email if not provided
if not email:
email = "unknown@alshifa-care.com"
# --- 2. Handle missing required fields ---
if missing_fields:
missing_fields_str = "\n- ".join([""] + missing_fields) # Add newline and bullet points
return f"""
عذراً، لا يمكن إتمام الحجز بسبب نقص المعلومات المطلوبة. يرجى توفير:
{missing_fields_str}
Sorry, we cannot complete the booking due to missing required information. Please provide:
{missing_fields_str}
"""
# --- 2. If all data is present, proceed with booking ---
booking_id = f"BK-{uuid.uuid4().hex[:8].upper()}"
timestamp = datetime.now().isoformat()
booking_data = {
"booking_id": booking_id,
"booking_timestamp": timestamp,
"patient_details": {"name": patient_name, "age": age, "gender": gender, "contact_number": contact_number, "email": email},
"consultation_details": {
"reason": reason_for_consultation,
"preferred_date": preferred_date,
"preferred_time": preferred_time,
"specialty": specialty or "Not Specified",
"doctor_preference": doctor_preference or "Any",
"type": consultation_type or "Video Call"
}
}
# 3. Return the final data as a formatted JSON string
return json.dumps(booking_data, indent=4, ensure_ascii=False)