from dotenv import load_dotenv import os import httpx import base64 load_dotenv() from fastapi import FastAPI, HTTPException, Form from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from typing import List, Dict, Any, Optional from models import Patient, AgentState from agents.intake import run_intake_agent from agents.anamnesis import run_anamnesis_agent from agents.diagnosis import run_diagnosis_agent from agents.planner import run_planner_agent app = FastAPI(title="Medical Multi-Agent POC") # CORS configuration origins = ["*"] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) class IntakeRequest(BaseModel): input: str class AnamnesisRequest(BaseModel): history: List[Dict[str, str]] input: str class DiagnosisRequest(BaseModel): patient_info: Dict[str, Any] symptom_summary: str bio_data: Optional[Dict[str, Any]] = None class PlannerRequest(BaseModel): diagnosis_report: str @app.get("/") async def root(): return {"message": "Medical Multi-Agent POC Backend is running"} @app.post("/api/intake", response_model=Patient) async def intake_endpoint(request: IntakeRequest): return await run_intake_agent(request.input) @app.post("/api/anamnesis") async def anamnesis_endpoint(request: AnamnesisRequest): response = await run_anamnesis_agent(request.history, request.input) return {"response": response} @app.post("/api/diagnosis") async def diagnosis_endpoint(request: DiagnosisRequest): report = await run_diagnosis_agent(request.patient_info, request.symptom_summary, request.bio_data) return {"report": report} @app.post("/api/planner") async def planner_endpoint(request: PlannerRequest): plan = await run_planner_agent(request.diagnosis_report) return {"plan": plan} # --- New Agents Endpoints --- from agents.triage import run_triage_agent from agents.pharmacist import run_pharmacist_agent from agents.imaging import run_imaging_agent from agents.followup import run_followup_agent class TriageRequest(BaseModel): symptoms: str class PharmacistRequest(BaseModel): patient_name: str patient_age: int history: str prescription: str class ImagingRequest(BaseModel): imaging_desc: str clinical_context: str class FollowupRequest(BaseModel): diagnosis: str treatment: str @app.post("/api/triage") async def triage_endpoint(request: TriageRequest): result = await run_triage_agent(request.symptoms) return {"result": result} @app.post("/api/pharmacist") async def pharmacist_endpoint(request: PharmacistRequest): result = await run_pharmacist_agent(request.patient_name, request.patient_age, request.history, request.prescription) return {"result": result} @app.post("/api/imaging") async def imaging_endpoint(request: ImagingRequest): result = await run_imaging_agent(request.imaging_desc, request.clinical_context) return {"result": result} @app.post("/api/followup") async def followup_endpoint(request: FollowupRequest): result = await run_followup_agent(request.diagnosis, request.treatment) return {"result": result} from agents.bio_analysis import run_bio_analysis_agent from agents.radiology import run_radiology_agent from agents.report import generate_report_content, create_pdf_report from fastapi.responses import FileResponse from fastapi import UploadFile, File class ReportRequest(BaseModel): patient_info: Dict[str, Any] diagnosis: str plan: str @app.post("/api/bio-analysis") async def bio_analysis_endpoint(file: UploadFile = File(...)): content = await file.read() analysis = await run_bio_analysis_agent(content, file.content_type) return {"analysis": analysis} @app.post("/api/radiology-analysis") async def radiology_analysis_endpoint( file: UploadFile = File(...), model_type: str = Form(...), question: Optional[str] = Form(None) ): content = await file.read() result = await run_radiology_agent(content, model_type, question) return {"result": result} @app.post("/api/generate-report") async def report_endpoint(request: ReportRequest): content = await generate_report_content(request.patient_info, request.diagnosis, request.plan) patient_name = request.patient_info.get("name", "patient").replace(" ", "_").lower() filename = f"{patient_name}_report.pdf" file_path = create_pdf_report(content, request.patient_info.get("name", "Unknown"), filename) return {"content": content, "pdf_url": f"/reports/{filename}"} @app.get("/reports/{filename}") async def get_report(filename: str): return FileResponse(f"reports/{filename}") import google.generativeai as genai import json @app.post("/api/collab-chat") async def collab_chat_endpoint( text: str = Form(...), history: str = Form(...), file: Optional[UploadFile] = File(None) ): try: # Load chat history chat_history = json.loads(history) # Priority: GLM (VLM) for multimodal medical analysis z_api_key = os.getenv("Z_AI_API_KEY") if not z_api_key: return {"response": "Erreur: Clé API IA Vision (VLM) non configurée dans le fichier .env."} api_url = "https://api.z.ai/api/paas/v4/chat/completions" headers = { "Authorization": f"Bearer {z_api_key}", "Content-Type": "application/json" } # Convert history (Gemini format) to GLM format messages = [] # Add system prompt for medical expertise messages.append({ "role": "system", "content": "Tu es un expert médical SmartDiag. Analyse les documents et réponds aux médecins avec précision clinique." }) for h in chat_history: role = "user" if h["role"] == "user" else "assistant" # Extract text from parts msg_text = "" if isinstance(h["parts"], list) and len(h["parts"]) > 0: msg_text = h["parts"][0].get("text", "") elif isinstance(h["parts"], str): msg_text = h["parts"] if msg_text: messages.append({"role": role, "content": msg_text}) # Prepare current content based on media type if file: current_content = [] extracted_text = "" file_bytes = await file.read() if file.content_type.startswith("image/"): img_b64 = base64.b64encode(file_bytes).decode("utf-8") current_content.append({ "type": "image_url", "image_url": {"url": f"data:{file.content_type};base64,{img_b64}"} }) elif file.content_type == "application/pdf": try: import io from pypdf import PdfReader pdf_file = io.BytesIO(file_bytes) reader = PdfReader(pdf_file) for page in reader.pages: extracted_text += page.extract_text() + "\n" if extracted_text: text = f"{text}\n\n[Contenu du PDF joint ({file.filename})]:\n{extracted_text}" else: return {"response": "Impossible d'extraire le texte de ce PDF (scan/image). Merci d'envoyer une capture d'écran."} except Exception as pdf_err: print(f"PDF Error: {pdf_err}") return {"response": "Erreur lecture PDF."} # For VLM, content is a list current_content.append({"type": "text", "text": text}) messages.append({"role": "user", "content": current_content}) model_name = "glm-4.6v" else: # Text only: Use GLM-4.6v as well for consistency, but with text-only payload if not text.strip(): return {"response": "Veuillez saisir un message."} # GLM-4V expects list content for consistency if we use that model messages.append({ "role": "user", "content": [{"type": "text", "text": text}] }) model_name = "glm-4.6v" payload = { "model": model_name, "messages": messages, "temperature": 0.7, "top_p": 0.9, "stream": False } async with httpx.AsyncClient(timeout=120.0) as client: response = await client.post(api_url, headers=headers, json=payload) if response.status_code == 200: result = response.json() return {"response": result["choices"][0]["message"]["content"]} else: error_msg = response.text print(f"GLM API Error: {error_msg}") return {"response": f"Erreur VLM : Impossible d'analyser le document ({response.status_code})."} except Exception as e: print(f"Critical error in collab-chat: {str(e)}") import traceback traceback.print_exc() return {"response": f"Désolé, une erreur technique est survenue : {str(e)}"}