| import os |
| import json |
| import httpx |
| from dotenv import load_dotenv |
| from supabase import create_client |
|
|
| load_dotenv() |
|
|
| GROQ_API_KEY = os.getenv("GROQ_API_KEY") |
| SUPABASE_URL = os.getenv("SUPABASE_URL") |
| SUPABASE_KEY = os.getenv("SUPABASE_KEY") |
|
|
| supabase = None |
| if all([GROQ_API_KEY, SUPABASE_URL, SUPABASE_KEY]): |
| try: |
| supabase = create_client(SUPABASE_URL, SUPABASE_KEY) |
| except Exception as e: |
| print(f"WARNING: Could not initialize Supabase: {e}") |
|
|
|
|
| |
| |
| |
|
|
| def get_patient_by_name(name: str): |
| if not supabase: |
| return {"error": "Database not connected. Check SUPABASE_URL and SUPABASE_KEY."} |
| res = supabase.table("patients").select("*").ilike("name", f"%{name}%").execute() |
| return res.data if res.data else {"error": "Patient not found"} |
|
|
| def get_patients_by_doctor(doctor: str): |
| if not supabase: |
| return {"error": "Database not connected. Check SUPABASE_URL and SUPABASE_KEY."} |
| res = supabase.table("patients").select("*").ilike("doctor", f"%{doctor}%").execute() |
| return res.data if res.data else {"error": "No patients found for this doctor"} |
|
|
| def get_admitted_patients(): |
| if not supabase: |
| return {"error": "Database not connected. Check SUPABASE_URL and SUPABASE_KEY."} |
| res = supabase.table("patients").select("*").eq("status", "admitted").execute() |
| return res.data if res.data else {"message": "No admitted patients currently"} |
|
|
| def get_appointments_by_date(date: str): |
| if not supabase: |
| return {"error": "Database not connected. Check SUPABASE_URL and SUPABASE_KEY."} |
| res = supabase.table("appointments").select("*").eq("date", date).execute() |
| return res.data if res.data else {"message": f"No appointments on {date}"} |
|
|
| def get_patient_appointments(patient_name: str): |
| if not supabase: |
| return {"error": "Database not connected. Check SUPABASE_URL and SUPABASE_KEY."} |
| res = supabase.table("appointments").select("*").ilike("patient_name", f"%{patient_name}%").execute() |
| return res.data if res.data else {"error": "No appointments found for this patient"} |
|
|
| def add_patient(name: str, age: int, gender: str, disease: str, doctor: str, admission_date: str): |
| if not supabase: |
| return {"error": "Database not connected. Check SUPABASE_URL and SUPABASE_KEY."} |
| data = { |
| "name": name, |
| "age": age, |
| "gender": gender, |
| "disease": disease, |
| "doctor": doctor, |
| "admission_date": admission_date, |
| "status": "admitted" |
| } |
| res = supabase.table("patients").insert(data).execute() |
| return {"success": True, "patient": res.data[0]} if res.data else {"error": "Failed to add patient"} |
|
|
| def discharge_patient(patient_id: int): |
| if not supabase: |
| return {"error": "Database not connected. Check SUPABASE_URL and SUPABASE_KEY."} |
| res = supabase.table("patients").update({"status": "discharged"}).eq("id", patient_id).execute() |
| return {"success": True, "message": f"Patient {patient_id} discharged"} if res.data else {"error": "Patient not found"} |
|
|
| def update_appointment_status(appointment_id: int, status: str): |
| if not supabase: |
| return {"error": "Database not connected. Check SUPABASE_URL and SUPABASE_KEY."} |
| if status not in ["scheduled", "completed", "cancelled"]: |
| return {"error": "Invalid status. Use: scheduled, completed, cancelled"} |
| res = supabase.table("appointments").update({"status": status}).eq("id", appointment_id).execute() |
| return {"success": True, "message": f"Appointment {appointment_id} updated to {status}"} if res.data else {"error": "Appointment not found"} |
|
|
| def get_disease_statistics(): |
| if not supabase: |
| return {"error": "Database not connected. Check SUPABASE_URL and SUPABASE_KEY."} |
| res = supabase.table("patients").select("disease").execute() |
| if not res.data: |
| return {"message": "No data available"} |
| stats = {} |
| for row in res.data: |
| d = row["disease"] |
| stats[d] = stats.get(d, 0) + 1 |
| return stats |
|
|
| |
| |
| |
|
|
| available_functions = { |
| "get_patient_by_name": get_patient_by_name, |
| "get_patients_by_doctor": get_patients_by_doctor, |
| "get_admitted_patients": get_admitted_patients, |
| "get_appointments_by_date": get_appointments_by_date, |
| "get_patient_appointments": get_patient_appointments, |
| "add_patient": add_patient, |
| "discharge_patient": discharge_patient, |
| "update_appointment_status": update_appointment_status, |
| "get_disease_statistics": get_disease_statistics, |
| } |
|
|
| |
| |
| |
|
|
| tools = [ |
| { |
| "type": "function", |
| "function": { |
| "name": "get_patient_by_name", |
| "description": "Search and get patient details by name", |
| "parameters": { |
| "type": "object", |
| "properties": {"name": {"type": "string"}}, |
| "required": ["name"] |
| } |
| } |
| }, |
| { |
| "type": "function", |
| "function": { |
| "name": "get_patients_by_doctor", |
| "description": "Get all patients assigned to a specific doctor", |
| "parameters": { |
| "type": "object", |
| "properties": {"doctor": {"type": "string"}}, |
| "required": ["doctor"] |
| } |
| } |
| }, |
| { |
| "type": "function", |
| "function": { |
| "name": "get_admitted_patients", |
| "description": "Get all currently admitted patients", |
| "parameters": {"type": "object", "properties": {}} |
| } |
| }, |
| { |
| "type": "function", |
| "function": { |
| "name": "get_appointments_by_date", |
| "description": "Get all appointments on a specific date (format: YYYY-MM-DD)", |
| "parameters": { |
| "type": "object", |
| "properties": {"date": {"type": "string"}}, |
| "required": ["date"] |
| } |
| } |
| }, |
| { |
| "type": "function", |
| "function": { |
| "name": "get_patient_appointments", |
| "description": "Get all appointments for a specific patient by name", |
| "parameters": { |
| "type": "object", |
| "properties": {"patient_name": {"type": "string"}}, |
| "required": ["patient_name"] |
| } |
| } |
| }, |
| { |
| "type": "function", |
| "function": { |
| "name": "add_patient", |
| "description": "Register a new patient in the hospital system", |
| "parameters": { |
| "type": "object", |
| "properties": { |
| "name": {"type": "string"}, |
| "age": {"type": "integer"}, |
| "gender": {"type": "string"}, |
| "disease": {"type": "string"}, |
| "doctor": {"type": "string"}, |
| "admission_date": {"type": "string", "description": "Format: YYYY-MM-DD"} |
| }, |
| "required": ["name", "age", "gender", "disease", "doctor", "admission_date"] |
| } |
| } |
| }, |
| { |
| "type": "function", |
| "function": { |
| "name": "discharge_patient", |
| "description": "Discharge a patient by their ID (change status to discharged)", |
| "parameters": { |
| "type": "object", |
| "properties": {"patient_id": {"type": "integer"}}, |
| "required": ["patient_id"] |
| } |
| } |
| }, |
| { |
| "type": "function", |
| "function": { |
| "name": "update_appointment_status", |
| "description": "Update the status of an appointment (scheduled, completed, or cancelled)", |
| "parameters": { |
| "type": "object", |
| "properties": { |
| "appointment_id": {"type": "integer"}, |
| "status": {"type": "string", "enum": ["scheduled", "completed", "cancelled"]} |
| }, |
| "required": ["appointment_id", "status"] |
| } |
| } |
| }, |
| { |
| "type": "function", |
| "function": { |
| "name": "get_disease_statistics", |
| "description": "Get count of patients grouped by disease/condition", |
| "parameters": {"type": "object", "properties": {}} |
| } |
| } |
| ] |
|
|
| |
| |
| |
|
|
| SYSTEM_PROMPT = """You are MedBot, a hospital admin assistant for City General Hospital. |
| |
| ABSOLUTE RULES - NEVER BREAK THESE: |
| 1. YOU MUST ALWAYS RESPOND IN ENGLISH ONLY. This is mandatory. Never use Hindi, Urdu, Hinglish, or any other language. Not even one word in another language. |
| 2. You are ONLY a hospital admin assistant. Reject all non-hospital queries politely in English. |
| |
| You help admins with: |
| - Patient records (search, add, discharge) |
| - Doctor-patient assignments |
| - Appointment management |
| - Disease statistics |
| |
| Always use available tools for database queries. |
| Format responses cleanly with bullet points for multiple records. |
| """ |
|
|
| async def process_agent_chat(user_message: str): |
| try: |
| if not GROQ_API_KEY: |
| return {"response": "Error: GROQ_API_KEY not configured. Please set it in Space Variables."} |
| |
| headers = { |
| "Authorization": f"Bearer {GROQ_API_KEY}", |
| "Content-Type": "application/json" |
| } |
|
|
| messages = [ |
| {"role": "system", "content": SYSTEM_PROMPT}, |
| {"role": "user", "content": user_message} |
| ] |
|
|
| payload = { |
| "model": "llama-3.3-70b-versatile", |
| "messages": messages, |
| "tools": tools, |
| "tool_choice": "auto", |
| "temperature": 0.2 |
| } |
|
|
| async with httpx.AsyncClient(timeout=30.0) as client: |
| response = await client.post( |
| "https://api.groq.com/openai/v1/chat/completions", |
| headers=headers, |
| json=payload |
| ) |
|
|
| |
| if response.status_code != 200: |
| try: |
| error_data = response.json() |
| error_msg = error_data.get("error", {}).get("message", response.text) |
| except Exception: |
| error_msg = response.text |
| return {"response": f"⚠️ Groq API Error (Status {response.status_code}): {error_msg}"} |
|
|
| try: |
| data = response.json() |
| except Exception as e: |
| return {"response": f"⚠️ Failed to parse Groq response as JSON. Status code: {response.status_code}. Raw response: {response.text}"} |
|
|
| print("GROQ RESPONSE:", data) |
|
|
| if "choices" not in data or not data["choices"]: |
| return {"response": f"⚠️ Error: Groq response did not contain 'choices'. Full response: {data}"} |
|
|
| assistant_message = data["choices"][0]["message"] |
| tool_calls = assistant_message.get("tool_calls") |
|
|
| if not tool_calls: |
| return {"response": assistant_message.get("content", "No response text received.")} |
|
|
| messages.append(assistant_message) |
|
|
| for tool_call in tool_calls: |
| function_name = tool_call["function"]["name"] |
| function_args = tool_call["function"].get("arguments") |
| try: |
| function_args = json.loads(function_args) if function_args else {} |
| except Exception as e: |
| function_args = {} |
| print(f"Error parsing tool arguments: {e}") |
|
|
| if function_name not in available_functions: |
| function_response = {"error": f"Function '{function_name}' is not supported."} |
| else: |
| function_to_call = available_functions[function_name] |
| try: |
| try: |
| function_response = function_to_call(**function_args) |
| except TypeError: |
| function_response = function_to_call() |
| except Exception as e: |
| import traceback |
| print(f"Error executing database tool '{function_name}': {e}") |
| traceback.print_exc() |
| function_response = {"error": f"Database query failed in {function_name}: {str(e)}"} |
|
|
| messages.append({ |
| "role": "tool", |
| "tool_call_id": tool_call["id"], |
| "name": function_name, |
| "content": json.dumps(function_response) |
| }) |
|
|
| second_payload = { |
| "model": "llama-3.3-70b-versatile", |
| "messages": messages, |
| "temperature": 0.2 |
| } |
|
|
| async with httpx.AsyncClient(timeout=30.0) as client: |
| final_response = await client.post( |
| "https://api.groq.com/openai/v1/chat/completions", |
| headers=headers, |
| json=second_payload |
| ) |
|
|
| if final_response.status_code != 200: |
| try: |
| error_data = final_response.json() |
| error_msg = error_data.get("error", {}).get("message", final_response.text) |
| except Exception: |
| error_msg = final_response.text |
| return {"response": f"⚠️ Groq Final Response Generation Error (Status {final_response.status_code}): {error_msg}"} |
|
|
| try: |
| final_data = final_response.json() |
| except Exception as e: |
| return {"response": f"⚠️ Failed to parse Groq final response as JSON. Status code: {final_response.status_code}. Raw response: {final_response.text}"} |
|
|
| if "choices" not in final_data or not final_data["choices"]: |
| return {"response": f"⚠️ Error: Groq final response did not contain 'choices'. Full response: {final_data}"} |
|
|
| final_answer = final_data["choices"][0]["message"].get("content", "No final answer generated.") |
| return {"response": final_answer} |
|
|
| except Exception as e: |
| import traceback |
| error_trace = traceback.format_exc() |
| print("RUNTIME ERROR IN process_agent_chat:\n", error_trace) |
| return {"response": f"⚠️ Server Runtime Error: {str(e)}\n\nTraceback:\n{error_trace}"} |