Spaces:
Sleeping
Sleeping
Update backend.py (#52)
Browse files- Update backend.py (6213b11ef7a2345f46c3f331ea1b3ae9c719d25b)
Co-authored-by: ashrith repaka <godtier812@users.noreply.huggingface.co>
- backend.py +14 -56
backend.py
CHANGED
|
@@ -74,7 +74,6 @@ safety_settings = [
|
|
| 74 |
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
| 75 |
]
|
| 76 |
|
| 77 |
-
# --- Pydantic Models for API Endpoints ---
|
| 78 |
class ChatRequest(BaseModel):
|
| 79 |
user_id: Optional[str] = "anonymous"
|
| 80 |
question: str
|
|
@@ -86,27 +85,18 @@ class TextRequest(BaseModel):
|
|
| 86 |
text: str
|
| 87 |
|
| 88 |
system_prompt = """ You are a highly skilled medical practitioner specializing in medical image and document analysis. You will be given either a medical image or a PDF.
|
| 89 |
-
|
| 90 |
Your responsibilities are:
|
| 91 |
-
|
| 92 |
1. **Extract Text**: If the input is a PDF or image, first extract all the text content (lab values, notes, measurements, etc.). Do not summarize — keep the extracted text verbatim.
|
| 93 |
-
|
| 94 |
2. **Detailed Analysis**: Use both the extracted text and the visual features of the image to identify any anomalies, diseases, or health issues.
|
| 95 |
-
|
| 96 |
3. **Finding Report**: Document all observed anomalies or signs of disease.
|
| 97 |
- Include any measurements (e.g., triglycerides, HBa1c, HDL) in the format:
|
| 98 |
`{"findings": "Condition only if risky: measurement type -- value with unit(current range)"}`
|
| 99 |
- Simplify the finding in **3 words** at the beginning when helpful.
|
| 100 |
-
|
| 101 |
4. **Checking for Past**: If a disease is family history or previously recovered, mark severity as:
|
| 102 |
`"severity": "severity of anomaly (Past Anomaly but Still Under Risk)"`
|
| 103 |
-
|
| 104 |
5. **Recommendations and Next Steps**: Provide detailed recommendations (tests, follow-ups, consultations).
|
| 105 |
-
|
| 106 |
6. **Treatment Suggestions**: Offer preliminary treatments or interventions.
|
| 107 |
-
|
| 108 |
7. **Output Format**: Always return a JSON object containing both the raw extracted text and the structured analysis, like this:
|
| 109 |
-
|
| 110 |
```json
|
| 111 |
{
|
| 112 |
"ocr_text": "<<<FULL VERBATIM TEXT FROM THE PDF/IMAGE>>>",
|
|
@@ -127,7 +117,6 @@ Your responsibilities are:
|
|
| 127 |
}
|
| 128 |
]
|
| 129 |
}
|
| 130 |
-
|
| 131 |
Important Notes:
|
| 132 |
1. Scope of Response: Only respond if the image pertains to a human health issue.
|
| 133 |
2. Clarity of Image: Ensure the image is clear and suitable for accurate analysis.
|
|
@@ -136,33 +125,24 @@ Important Notes:
|
|
| 136 |
5. Completely UPPERCASE the main concern in the finding """
|
| 137 |
|
| 138 |
system_prompt_chat = """
|
| 139 |
-
*** Role: Medical
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
*** Response Structure:
|
| 151 |
-
Start with a direct answer to the user’s primary question (maximum 4 concise sentences, each on a new line).
|
| 152 |
-
If a physician/specialist is needed, recommend at least two local providers within the requested radius (include name, specialty, address, distance, and contact info).
|
| 153 |
-
If insurance details are available, indicate which physicians are in-network.
|
| 154 |
-
End with a short safety disclaimer.
|
| 155 |
-
***Input Fields:
|
| 156 |
-
Provided Document Text: {document_text}
|
| 157 |
User Question: {user_question}
|
| 158 |
-
|
| 159 |
"""
|
| 160 |
|
| 161 |
-
# Initialize model
|
| 162 |
model = genai.GenerativeModel(model_name="gemini-2.5-flash-lite")
|
| 163 |
|
| 164 |
async def _call_model_blocking(request_inputs, generation_cfg, safety_cfg):
|
| 165 |
-
"""Run blocking model call in threadpool (so uvicorn's event loop isn't blocked)."""
|
| 166 |
fn = functools.partial(
|
| 167 |
model.generate_content,
|
| 168 |
request_inputs,
|
|
@@ -172,7 +152,6 @@ async def _call_model_blocking(request_inputs, generation_cfg, safety_cfg):
|
|
| 172 |
loop = asyncio.get_event_loop()
|
| 173 |
return await loop.run_in_executor(None, fn)
|
| 174 |
|
| 175 |
-
|
| 176 |
async def analyze_image(image_bytes: bytes, mime_type: str, prompt: Optional[str] = None) -> Any:
|
| 177 |
base64_img = base64.b64encode(image_bytes).decode("utf-8")
|
| 178 |
text_prompt = (prompt or system_prompt).strip()
|
|
@@ -206,7 +185,6 @@ async def analyze_image(image_bytes: bytes, mime_type: str, prompt: Optional[str
|
|
| 206 |
match = re.search(r"(\[.*\]|\{.*\})", clean, re.DOTALL)
|
| 207 |
if match:
|
| 208 |
try:
|
| 209 |
-
|
| 210 |
parsed = json.loads(match.group(1)), None
|
| 211 |
ocr_text = parsed["ocr_text"]
|
| 212 |
analysis = parsed["analysis"]
|
|
@@ -226,16 +204,10 @@ def get_past_reports_from_sqllite(user_id: str):
|
|
| 226 |
history_text = "No past reports found for this user."
|
| 227 |
return history_text
|
| 228 |
|
| 229 |
-
|
| 230 |
@app.post("/chat/", response_model=ChatResponse)
|
| 231 |
async def chat_endpoint(request: ChatRequest):
|
| 232 |
global result
|
| 233 |
print(f"Received chat request for user: {request.user_id}")
|
| 234 |
-
"""
|
| 235 |
-
Chatbot endpoint that answers questions based on the last analyzed document and user history.
|
| 236 |
-
"""
|
| 237 |
-
|
| 238 |
-
#history_text = get_past_reports_from_firestore(request.user_id)
|
| 239 |
full_document_text = get_past_reports_from_sqllite(request.user_id.strip())
|
| 240 |
|
| 241 |
full_document_text = EXTRACTED_TEXT_CACHE+"\n\n" + "PAST REPORTS:\n" + full_document_text
|
|
@@ -243,7 +215,6 @@ async def chat_endpoint(request: ChatRequest):
|
|
| 243 |
if not full_document_text:
|
| 244 |
raise HTTPException(status_code=400, detail="No past reports or current data exists for this user")
|
| 245 |
|
| 246 |
-
|
| 247 |
try:
|
| 248 |
full_prompt = system_prompt_chat.format(
|
| 249 |
document_text=full_document_text,
|
|
@@ -258,20 +229,13 @@ async def chat_endpoint(request: ChatRequest):
|
|
| 258 |
|
| 259 |
@app.post("/analyze")
|
| 260 |
async def analyze_endpoint(file: UploadFile = File(...), prompt: str = Form(None)):
|
| 261 |
-
|
| 262 |
-
"""
|
| 263 |
-
Upload an image file (field name `file`) and optional text `prompt`.
|
| 264 |
-
Returns parsed JSON (or raw model output if JSON couldn't be parsed).
|
| 265 |
-
"""
|
| 266 |
-
|
| 267 |
global result,EXTRACTED_TEXT_CACHE
|
| 268 |
|
| 269 |
filename = file.filename.lower()
|
| 270 |
print(f"Received analyze request for file {filename}")
|
| 271 |
-
contents = await file.read()
|
| 272 |
mime = file.content_type or "image/png"
|
| 273 |
|
| 274 |
-
#result = await analyze_image(contents, mime, prompt)
|
| 275 |
try:
|
| 276 |
result, ocr_text = await analyze_image(contents, mime, prompt)
|
| 277 |
EXTRACTED_TEXT_CACHE = ocr_text
|
|
@@ -301,16 +265,12 @@ def _log_routes():
|
|
| 301 |
if isinstance(r, APIRoute):
|
| 302 |
print(" ", r.path, r.methods)
|
| 303 |
|
| 304 |
-
|
| 305 |
-
|
| 306 |
def main():
|
| 307 |
-
"""Run the application."""
|
| 308 |
try:
|
| 309 |
logger.info(f"Starting server on 8000")
|
| 310 |
logger.info(f"Debug mode: true")
|
| 311 |
|
| 312 |
if Config.DEBUG:
|
| 313 |
-
# Use import string for reload mode
|
| 314 |
uvicorn.run(
|
| 315 |
"main:app",
|
| 316 |
host="localhost",
|
|
@@ -319,7 +279,6 @@ def main():
|
|
| 319 |
log_level="debug"
|
| 320 |
)
|
| 321 |
else:
|
| 322 |
-
# Use app instance for production
|
| 323 |
uvicorn.run(
|
| 324 |
app,
|
| 325 |
host="localhost",
|
|
@@ -332,6 +291,5 @@ def main():
|
|
| 332 |
logger.error(f"Failed to start server: {e}")
|
| 333 |
raise
|
| 334 |
|
| 335 |
-
|
| 336 |
if __name__ == "__main__":
|
| 337 |
-
main()
|
|
|
|
| 74 |
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
| 75 |
]
|
| 76 |
|
|
|
|
| 77 |
class ChatRequest(BaseModel):
|
| 78 |
user_id: Optional[str] = "anonymous"
|
| 79 |
question: str
|
|
|
|
| 85 |
text: str
|
| 86 |
|
| 87 |
system_prompt = """ You are a highly skilled medical practitioner specializing in medical image and document analysis. You will be given either a medical image or a PDF.
|
|
|
|
| 88 |
Your responsibilities are:
|
|
|
|
| 89 |
1. **Extract Text**: If the input is a PDF or image, first extract all the text content (lab values, notes, measurements, etc.). Do not summarize — keep the extracted text verbatim.
|
|
|
|
| 90 |
2. **Detailed Analysis**: Use both the extracted text and the visual features of the image to identify any anomalies, diseases, or health issues.
|
|
|
|
| 91 |
3. **Finding Report**: Document all observed anomalies or signs of disease.
|
| 92 |
- Include any measurements (e.g., triglycerides, HBa1c, HDL) in the format:
|
| 93 |
`{"findings": "Condition only if risky: measurement type -- value with unit(current range)"}`
|
| 94 |
- Simplify the finding in **3 words** at the beginning when helpful.
|
|
|
|
| 95 |
4. **Checking for Past**: If a disease is family history or previously recovered, mark severity as:
|
| 96 |
`"severity": "severity of anomaly (Past Anomaly but Still Under Risk)"`
|
|
|
|
| 97 |
5. **Recommendations and Next Steps**: Provide detailed recommendations (tests, follow-ups, consultations).
|
|
|
|
| 98 |
6. **Treatment Suggestions**: Offer preliminary treatments or interventions.
|
|
|
|
| 99 |
7. **Output Format**: Always return a JSON object containing both the raw extracted text and the structured analysis, like this:
|
|
|
|
| 100 |
```json
|
| 101 |
{
|
| 102 |
"ocr_text": "<<<FULL VERBATIM TEXT FROM THE PDF/IMAGE>>>",
|
|
|
|
| 117 |
}
|
| 118 |
]
|
| 119 |
}
|
|
|
|
| 120 |
Important Notes:
|
| 121 |
1. Scope of Response: Only respond if the image pertains to a human health issue.
|
| 122 |
2. Clarity of Image: Ensure the image is clear and suitable for accurate analysis.
|
|
|
|
| 125 |
5. Completely UPPERCASE the main concern in the finding """
|
| 126 |
|
| 127 |
system_prompt_chat = """
|
| 128 |
+
*** Role: Medical Chat Assistant ***
|
| 129 |
+
You are a concise and empathetic medical chatbot. Your job is to give clear, short answers (max 3-4 sentences) based only on the provided medical report text.
|
| 130 |
+
|
| 131 |
+
Rules:
|
| 132 |
+
- Avoid repeating the entire report; focus only on what is directly relevant to the user’s question.
|
| 133 |
+
- Give top 2 actionable steps if needed.
|
| 134 |
+
- If condition is serious, suggest consulting a doctor immediately.
|
| 135 |
+
- Always end with: "Check with your physician before acting."
|
| 136 |
+
|
| 137 |
+
Input:
|
| 138 |
+
Report Text: {document_text}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
User Question: {user_question}
|
| 140 |
+
Response:
|
| 141 |
"""
|
| 142 |
|
|
|
|
| 143 |
model = genai.GenerativeModel(model_name="gemini-2.5-flash-lite")
|
| 144 |
|
| 145 |
async def _call_model_blocking(request_inputs, generation_cfg, safety_cfg):
|
|
|
|
| 146 |
fn = functools.partial(
|
| 147 |
model.generate_content,
|
| 148 |
request_inputs,
|
|
|
|
| 152 |
loop = asyncio.get_event_loop()
|
| 153 |
return await loop.run_in_executor(None, fn)
|
| 154 |
|
|
|
|
| 155 |
async def analyze_image(image_bytes: bytes, mime_type: str, prompt: Optional[str] = None) -> Any:
|
| 156 |
base64_img = base64.b64encode(image_bytes).decode("utf-8")
|
| 157 |
text_prompt = (prompt or system_prompt).strip()
|
|
|
|
| 185 |
match = re.search(r"(\[.*\]|\{.*\})", clean, re.DOTALL)
|
| 186 |
if match:
|
| 187 |
try:
|
|
|
|
| 188 |
parsed = json.loads(match.group(1)), None
|
| 189 |
ocr_text = parsed["ocr_text"]
|
| 190 |
analysis = parsed["analysis"]
|
|
|
|
| 204 |
history_text = "No past reports found for this user."
|
| 205 |
return history_text
|
| 206 |
|
|
|
|
| 207 |
@app.post("/chat/", response_model=ChatResponse)
|
| 208 |
async def chat_endpoint(request: ChatRequest):
|
| 209 |
global result
|
| 210 |
print(f"Received chat request for user: {request.user_id}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
full_document_text = get_past_reports_from_sqllite(request.user_id.strip())
|
| 212 |
|
| 213 |
full_document_text = EXTRACTED_TEXT_CACHE+"\n\n" + "PAST REPORTS:\n" + full_document_text
|
|
|
|
| 215 |
if not full_document_text:
|
| 216 |
raise HTTPException(status_code=400, detail="No past reports or current data exists for this user")
|
| 217 |
|
|
|
|
| 218 |
try:
|
| 219 |
full_prompt = system_prompt_chat.format(
|
| 220 |
document_text=full_document_text,
|
|
|
|
| 229 |
|
| 230 |
@app.post("/analyze")
|
| 231 |
async def analyze_endpoint(file: UploadFile = File(...), prompt: str = Form(None)):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
global result,EXTRACTED_TEXT_CACHE
|
| 233 |
|
| 234 |
filename = file.filename.lower()
|
| 235 |
print(f"Received analyze request for file {filename}")
|
| 236 |
+
contents = await file.read()
|
| 237 |
mime = file.content_type or "image/png"
|
| 238 |
|
|
|
|
| 239 |
try:
|
| 240 |
result, ocr_text = await analyze_image(contents, mime, prompt)
|
| 241 |
EXTRACTED_TEXT_CACHE = ocr_text
|
|
|
|
| 265 |
if isinstance(r, APIRoute):
|
| 266 |
print(" ", r.path, r.methods)
|
| 267 |
|
|
|
|
|
|
|
| 268 |
def main():
|
|
|
|
| 269 |
try:
|
| 270 |
logger.info(f"Starting server on 8000")
|
| 271 |
logger.info(f"Debug mode: true")
|
| 272 |
|
| 273 |
if Config.DEBUG:
|
|
|
|
| 274 |
uvicorn.run(
|
| 275 |
"main:app",
|
| 276 |
host="localhost",
|
|
|
|
| 279 |
log_level="debug"
|
| 280 |
)
|
| 281 |
else:
|
|
|
|
| 282 |
uvicorn.run(
|
| 283 |
app,
|
| 284 |
host="localhost",
|
|
|
|
| 291 |
logger.error(f"Failed to start server: {e}")
|
| 292 |
raise
|
| 293 |
|
|
|
|
| 294 |
if __name__ == "__main__":
|
| 295 |
+
main()
|