Spaces:
Running
Running
| import logging | |
| import time | |
| import json | |
| from agents.model_router import router | |
| from tools.faq_tools import faq_lookup | |
| from tools.audit_tools import audit_trail_logger | |
| logger = logging.getLogger(__name__) | |
| SYSTEM_PROMPT_TEMPLATE = """You are PolicyEye's Medical Chat Assistant. | |
| You help Indian patients understand their health insurance coverage and medical terms. | |
| GUIDELINES: | |
| 1. Be professional, empathetic, and concise. | |
| 2. If the user asks about specific policy rules, mention IRDAI standards where applicable. | |
| 3. Keep answers under 3 paragraphs. | |
| 4. STRICT MODERATION: You must completely refuse to answer questions involving abusive language, romantic advances, harmful/dangerous activities (e.g., jumping from a building, self-harm), or security threats. If asked such questions, reply politely and firmly: "I am a medical insurance assistant and cannot respond to requests involving harm, abuse, or unrelated personal matters." | |
| 5. OFF-TOPIC: If a question is entirely non-contextual (e.g., asking for recipes, writing code, or general chatting unrelated to health/insurance), reply: "I specialize in health insurance and medical claims. How can I assist you with your policy today?" | |
| {user_context} | |
| """ | |
| async def handle_chat_query(query: str, history: list = None, user_id: str = None) -> dict: | |
| """ | |
| Optimized Multi-Tier Chat logic: | |
| Tier 1: Local FAQ Cache (Instant, Free) | |
| Tier 2: Cerebras / Fast LLM (Fast, Cheap) | |
| Tier 3: Complex Reasoning (Gemini/Groq) | |
| """ | |
| start_time = time.time() | |
| # Tier 1: Local FAQ Cache | |
| faq_result = faq_lookup(query) | |
| if faq_result: | |
| duration = (time.time() - start_time) * 1000 | |
| audit_trail_logger( | |
| agent_name="ChatAgent", | |
| action="faq_hit", | |
| input_summary=query, | |
| output_summary=faq_result["answer"][:100] + "...", | |
| tools_used=["faq_lookup"], | |
| duration_ms=duration | |
| ) | |
| return { | |
| "answer": faq_result["answer"], | |
| "method": "cache", | |
| "duration_ms": duration | |
| } | |
| # Build User Context (RAG) | |
| user_context = "" | |
| if user_id: | |
| from db.database import get_all_policies, get_check_history | |
| try: | |
| # Fetch up to 100 checks to provide comprehensive history | |
| policies = await get_all_policies(user_id=user_id) | |
| history_checks = await get_check_history(limit=100, user_id=user_id) | |
| ctx_lines = ["\n--- SECURESHIELD USER DATA CONTEXT ---"] | |
| ctx_lines.append("CRITICAL: You have access to the user's REAL uploaded policies and claim checks below.") | |
| ctx_lines.append("DO NOT hallucinate templates like '[insert policy number]'. Use the real data below to answer precisely. You DO NOT have access to their password or profile info; state this if asked.") | |
| if policies: | |
| ctx_lines.append("\nACTIVE POLICIES:") | |
| for p in policies: | |
| ctx_lines.append(f"- ID: #{p['id']}, Insurer: {p['insurer']}, Plan: {p['plan_name']}, Sum Insured: ₹{p['sum_insured']:,.2f}, Type: {p['policy_type']}") | |
| else: | |
| ctx_lines.append("\nACTIVE POLICIES: None uploaded yet.") | |
| if history_checks: | |
| ctx_lines.append("\nPAST CLAIMS / ELIGIBILITY CHECKS:") | |
| # We cap context to 15 recent checks to avoid LLM token limits while answering "tell me about my claims" | |
| for c in history_checks[:15]: | |
| try: | |
| case_data = json.loads(c['case_json']) | |
| verdict_data = json.loads(c['verdict_json']) | |
| proc = case_data.get('procedure', 'Unknown') | |
| hosp = case_data.get('hospital_name', 'Unknown') | |
| claimed = case_data.get('total_claimed_amount', 0) | |
| status = verdict_data.get('overall_verdict', 'UNKNOWN') | |
| ctx_lines.append(f"- Check #{c['id']}: Procedure: {proc} at {hosp}. Claimed: ₹{claimed:,.2f}. Status: {status}.") | |
| except Exception: | |
| pass | |
| else: | |
| ctx_lines.append("\nPAST CLAIMS: No previous checks found.") | |
| ctx_lines.append("--------------------------------------") | |
| user_context = "\n".join(ctx_lines) | |
| except Exception as e: | |
| logger.warning(f"[ChatAgent] Failed to retrieve user context: {e}") | |
| user_context = "" | |
| system_prompt = SYSTEM_PROMPT_TEMPLATE.replace("{user_context}", user_context) | |
| # Context integration for LLM | |
| user_prompt = query | |
| if history and len(history) > 0: | |
| context_str = "\n".join([f"{msg['role'].capitalize()}: {msg['content']}" for msg in history]) | |
| user_prompt = f"--- Previous Conversation ---\n{context_str}\n\n--- Current Query ---\nUser: {query}" | |
| # Tier 2: LLM (Cerebras / Groq / Gemini via Router) | |
| # The router will automatically try the best available provider. | |
| try: | |
| t0 = time.time() | |
| response = await router.call( | |
| role="chat", # Router will use DEFAULT_ROUTING which starts with Cerebras | |
| system_prompt=system_prompt, | |
| user_prompt=user_prompt, | |
| temperature=0.4, | |
| max_tokens=1024 | |
| ) | |
| # Output Sanitization (XSS prevention for markdown rendering) | |
| import re | |
| response = re.sub(r'<\s*script[^>]*>.*?(</\s*script\s*>|$)', '', response, flags=re.IGNORECASE | re.DOTALL) | |
| response = re.sub(r'\b(on\w+)\s*=', '', response, flags=re.IGNORECASE) | |
| duration = (time.time() - t0) * 1000 | |
| audit_trail_logger( | |
| agent_name="ChatAgent", | |
| action="llm_chat", | |
| input_summary=query, | |
| output_summary=response[:100] + "...", | |
| tools_used=["model_router"], | |
| duration_ms=duration | |
| ) | |
| return { | |
| "answer": response, | |
| "method": "llm", | |
| "duration_ms": duration | |
| } | |
| except Exception as e: | |
| logger.error(f"[ChatAgent] LLM failed: {e}") | |
| return { | |
| "answer": "I'm sorry, I encountered an error while processing your request. Please try again later.", | |
| "method": "error", | |
| "duration_ms": (time.time() - start_time) * 1000 | |
| } | |