""" Query Router Agent - Routes queries to appropriate handlers. Classifies incoming queries and determines the best processing path. """ import sys from pathlib import Path from enum import Enum from typing import Dict, Optional from langchain_openai import ChatOpenAI from langchain_core.messages import SystemMessage, HumanMessage from loguru import logger sys.path.append(str(Path(__file__).parent.parent)) from utils.config import config class QueryType(Enum): """Types of medical queries""" SYMPTOM_CHECK = "symptom_check" REPORT_EXPLANATION = "report_explanation" DRUG_INFO = "drug_info" EMERGENCY = "emergency" GENERAL_QA = "general_qa" PREVENTIVE_CARE = "preventive_care" FOLLOW_UP = "follow_up" class RouterAgent: """Routes queries to appropriate handlers based on query type and urgency""" ROUTER_PROMPT = """You are a medical query classifier. Analyze the user's query and classify it into ONE category. Categories: - symptom_check: User describing symptoms or asking about symptoms - report_explanation: Asking about lab results, medical reports, test results - drug_info: Questions about medications, drugs, prescriptions - emergency: Urgent medical situation requiring immediate attention - general_qa: General medical knowledge questions - preventive_care: Prevention, lifestyle, wellness, diet questions - follow_up: Follow-up question based on previous conversation Respond with ONLY the category name (lowercase, underscore-separated).""" EMERGENCY_KEYWORDS = [ "emergency", "urgent", "severe", "critical", "chest pain", "difficulty breathing", "can't breathe", "heart attack", "stroke", "seizure", "unconscious", "bleeding heavily", "severe pain", "suicide", "overdose" ] def __init__(self): self.llm = ChatOpenAI( api_key=config.OPENAI_API_KEY, model="gpt-4o-mini", temperature=0 ) logger.info("[RouterAgent] Initialized") async def route(self, query: str, context: Optional[Dict] = None) -> Dict: """ Route a query to the appropriate handler. Args: query: User's query text context: Optional context (previous queries, patient info, etc.) Returns: Dict with routing information """ # Quick emergency check is_urgent = self._check_emergency(query) if is_urgent: logger.warning(f"[RouterAgent] EMERGENCY query detected: {query[:100]}") return { "type": QueryType.EMERGENCY.value, "is_urgent": True, "confidence": 1.0, "reason": "Emergency keywords detected", "handler": "emergency_handler" } # Check if it's a follow-up based on context if context and context.get("has_previous_interaction"): # Simple heuristic: short queries are likely follow-ups if len(query.split()) < 10: return { "type": QueryType.FOLLOW_UP.value, "is_urgent": False, "confidence": 0.8, "reason": "Short query with conversation history", "handler": "rag_pipeline" } # Use LLM for classification try: messages = [ SystemMessage(content=self.ROUTER_PROMPT), HumanMessage(content=f"Query: {query}") ] response = await self.llm.ainvoke(messages) query_type = response.content.strip().lower().replace(" ", "_") # Validate query type try: QueryType(query_type) except ValueError: logger.warning(f"[RouterAgent] Invalid query type '{query_type}', defaulting to general_qa") query_type = QueryType.GENERAL_QA.value # Determine handler handler = self._get_handler(query_type) logger.info(f"[RouterAgent] Routed query to: {query_type} (handler: {handler})") return { "type": query_type, "is_urgent": False, "confidence": 0.85, "reason": f"LLM classified as {query_type}", "handler": handler } except Exception as e: logger.error(f"[RouterAgent] Routing failed: {e}") return { "type": QueryType.GENERAL_QA.value, "is_urgent": False, "confidence": 0.5, "reason": f"Routing error, defaulting to general_qa: {str(e)}", "handler": "rag_pipeline" } def _check_emergency(self, query: str) -> bool: """Quick check for emergency keywords""" query_lower = query.lower() return any(keyword in query_lower for keyword in self.EMERGENCY_KEYWORDS) def _get_handler(self, query_type: str) -> str: """Map query type to handler""" handler_map = { QueryType.SYMPTOM_CHECK.value: "rag_pipeline", QueryType.REPORT_EXPLANATION.value: "report_analyzer", QueryType.DRUG_INFO.value: "rag_pipeline", QueryType.EMERGENCY.value: "emergency_handler", QueryType.GENERAL_QA.value: "rag_pipeline", QueryType.PREVENTIVE_CARE.value: "rag_pipeline", QueryType.FOLLOW_UP.value: "rag_pipeline" } return handler_map.get(query_type, "rag_pipeline")