from fastapi import FastAPI from pydantic import BaseModel, Field from dotenv import load_dotenv import google.generativeai as genai import os import re import gradio as gr from typing import Dict, Any, Union, List # ---------------- Initialize ---------------- app = FastAPI(title="LLM Model API + Gradio UI", version="4.0") # ✅ Fetch Gemini API Key GEMINI_API_KEY = "AIzaSyC0XU6yLCILZFUVhKoIcqoy2k5qwQmnDsc" if not GEMINI_API_KEY: raise ValueError("❌ GEMINI_API_KEY not found. Please set it in your .env file.") genai.configure(api_key=GEMINI_API_KEY) MODEL_ID = "gemini-2.5-flash" # ---------------- Schema ---------------- class BiomarkerRequest(BaseModel): albumin: float = Field(default=3.2) creatinine: float = Field(default=1.4) glucose: float = Field(default=145) crp: float = Field(default=12.0) mcv: float = Field(default=88) rdw: float = Field(default=15.5) alp: float = Field(default=120) wbc: float = Field(default=11.8) lymphocytes: float = Field(default=20) hb: float = Field(default=13.0) pv: float = Field(default=2.1) age: int = Field(default=52) gender: str = Field(default="female") height: float = Field(default=165) weight: float = Field(default=70) # ---------------- Utilities ---------------- def clean_json(data: Union[Dict, List, str]) -> Union[Dict, List, str]: if isinstance(data, str): text = re.sub(r"-{3,}", "", data) text = re.sub(r"\s+", " ", text) text = text.strip(" -\n\t\r") return text elif isinstance(data, list): return [clean_json(i) for i in data if i and clean_json(i)] elif isinstance(data, dict): return {k.strip(): clean_json(v) for k, v in data.items()} return data # ---------------- Parser ---------------- def parse_medical_report(text: str): def clean_line(line: str) -> str: return re.sub(r"[\-\*\u2022]+\s*", "", line.strip()) def parse_bold_entities(block: str) -> Dict[str, str]: entities = {} pattern = re.compile(r"\*\*(.*?)\*\*(.*?)(?=\*\*|###|$)", re.S) for match in pattern.finditer(block): key = match.group(1).strip().strip(":") val = match.group(2).strip().replace("\n", " ") val = re.sub(r"\s+", " ", val) if key: entities[key] = val return entities data = { "executive_summary": {"top_priorities": [], "key_strengths": []}, "system_analysis": {}, "personalized_action_plan": {}, "interaction_alerts": [], "normal_ranges": {}, "biomarker_table": [] } exec_match = re.search(r"###\s*Executive Summary(.*?)(?=###|$)", text, re.S | re.I) if exec_match: block = exec_match.group(1) priorities = re.findall(r"\d+\.\s*(.*?)\n", block) if priorities: data["executive_summary"]["top_priorities"] = [clean_line(p) for p in priorities] strengths_match = re.search(r"\*\*Key Strengths:\*\*(.*)", block, re.S) if strengths_match: strengths_text = strengths_match.group(1) strengths = [clean_line(s) for s in strengths_text.splitlines() if clean_line(s)] data["executive_summary"]["key_strengths"] = strengths sys_match = re.search(r"###\s*System[- ]Specific Analysis(.*?)(?=###|$)", text, re.S | re.I) if sys_match: sys_block = sys_match.group(1) data["system_analysis"] = parse_bold_entities(sys_block) plan_match = re.search(r"###\s*Personalized Action Plan(.*?)(?=###|$)", text, re.S | re.I) if plan_match: plan_block = plan_match.group(1) data["personalized_action_plan"] = parse_bold_entities(plan_block) alerts_match = re.search(r"###\s*Interaction Alerts(.*?)(?=###|$)", text, re.S | re.I) if alerts_match: alerts_block = alerts_match.group(1) alerts = [clean_line(a) for a in alerts_block.splitlines() if clean_line(a)] data["interaction_alerts"] = alerts normal_match = re.search(r"###\s*Normal Ranges(.*?)(?=###|$)", text, re.S | re.I) if normal_match: normal_block = normal_match.group(1) for match in re.findall(r"-\s*([^:]+):\s*([^\n]+)", normal_block): biomarker, rng = match data["normal_ranges"][biomarker.strip()] = rng.strip() table_match = re.search(r"###\s*Tabular Mapping(.*)", text, re.S | re.I) if table_match: table_block = table_match.group(1) table_pattern = r"\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|" for biomarker, value, status, insight, ref in re.findall(table_pattern, table_block): if not any([biomarker, value, status, insight, ref]): continue data["biomarker_table"].append({ "biomarker": biomarker.strip(), "value": value.strip(), "status": status.strip(), "insight": insight.strip(), "reference_range": ref.strip(), }) return data # ---------------- Prediction Core ---------------- def generate_report(data: BiomarkerRequest) -> str: """Main logic — uses Gemini to generate markdown medical report""" prompt = """ You are an advanced **Medical Insight Generation AI** trained to analyze **biomarkers and lab results**. ⚠️ IMPORTANT — OUTPUT FORMAT INSTRUCTIONS: Return your report in this strict markdown structure. ------------------------------ ### Executive Summary **Top 3 Health Priorities:** 1. ... 2. ... 3. ... **Key Strengths:** - ... - ... ------------------------------ ### System-Specific Analysis **Cardiovascular System** Status: Normal. Explanation: ... **Liver Function** Status: Elevated ALP. Explanation: ... ------------------------------ ### Personalized Action Plan **Nutrition:** ... **Lifestyle:** ... **Testing:** ... **Medical Consultation:** ... ------------------------------ ### Interaction Alerts - ... - ... ------------------------------ ### Normal Ranges - Albumin: 3.5–5.0 g/dL - Creatinine: 0.7–1.3 mg/dL - Glucose: 70–100 mg/dL - CRP: 0–10 mg/L - MCV: 80–100 fL - RDW: 11.5–14.5 % - ALP: 44–147 U/L - WBC: 4.0–10.0 ×10^3/μL - Lymphocytes: 20–40 % - Hemoglobin: 13–17 g/dL - PV: 2500–3000 mL ------------------------------ ### Tabular Mapping | Biomarker | Value | Status | Insight | Reference Range | | Albumin | X | Normal | ... | 3.5–5.0 g/dL | | Creatinine | X | High | ... | 0.7–1.3 mg/dL | | Glucose | X | ... | ... | 70–100 mg/dL | ------------------------------ """ user_message = f""" Patient Info: - Age: {data.age} - Gender: {data.gender} - Height: {data.height} cm - Weight: {data.weight} kg Biomarkers: - Albumin: {data.albumin} g/dL - Creatinine: {data.creatinine} mg/dL - Glucose: {data.glucose} mg/dL - CRP: {data.crp} mg/L - MCV: {data.mcv} fL - RDW: {data.rdw} % - ALP: {data.alp} U/L - WBC: {data.wbc} ×10^3/μL - Lymphocytes: {data.lymphocytes} % - Hemoglobin: {data.hb} g/dL - Plasma Volume (PV): {data.pv} mL """ model = genai.GenerativeModel(MODEL_ID) response = model.generate_content(f"{prompt}\n\n{user_message}") if not response or not getattr(response, "text", None): return "⚠️ Gemini returned an empty response." return response.text.strip() # ---------------- Gradio Interface ---------------- def gradio_interface(albumin, creatinine, glucose, crp, mcv, rdw, alp, wbc, lymphocytes, hb, pv, age, gender, height, weight): req = BiomarkerRequest( albumin=albumin, creatinine=creatinine, glucose=glucose, crp=crp, mcv=mcv, rdw=rdw, alp=alp, wbc=wbc, lymphocytes=lymphocytes, hb=hb, pv=pv, age=int(age), gender=gender, height=height, weight=weight ) report = generate_report(req) return report iface = gr.Interface( fn=gradio_interface, inputs=[ gr.Number(label="Albumin (g/dL)", value=3.2), gr.Number(label="Creatinine (mg/dL)", value=1.4), gr.Number(label="Glucose (mg/dL)", value=145), gr.Number(label="CRP (mg/L)", value=12.0), gr.Number(label="MCV (fL)", value=88), gr.Number(label="RDW (%)", value=15.5), gr.Number(label="ALP (U/L)", value=120), gr.Number(label="WBC (×10³/μL)", value=11.8), gr.Number(label="Lymphocytes (%)", value=20), gr.Number(label="Hemoglobin (g/dL)", value=13.0), gr.Number(label="Plasma Volume (L)", value=2.1), gr.Number(label="Age (years)", value=52), gr.Radio(["male", "female"], label="Gender", value="female"), gr.Number(label="Height (cm)", value=165), gr.Number(label="Weight (kg)", value=70) ], outputs=gr.Markdown(label="🩺 AI Medical Report"), title="LLM Biomarker Analyzer", description="Enter your biomarker and demographic data to generate a detailed AI-based medical report (Gemini-powered).", theme="soft", allow_flagging="never" ) # ---------------- Launch ---------------- if __name__ == "__main__": iface.launch(server_name="0.0.0.0", server_port=7860)