Spaces:
Sleeping
Sleeping
File size: 8,984 Bytes
5ad56ab 4b7dfdf 5ad56ab 5cf89a3 5ad56ab 674b3ef 5ad56ab 4b7dfdf 5ad56ab 25758e2 5ad56ab 5cf89a3 5ad56ab 5cf89a3 d08d597 674b3ef 5cf89a3 5ad56ab d08d597 4b7dfdf d08d597 674b3ef 5ad56ab 674b3ef 5ad56ab 6a059ac 5cf89a3 4b7dfdf 674b3ef cae8c1d 674b3ef 5cf89a3 674b3ef 5cf89a3 674b3ef 5ad56ab 674b3ef 4b7dfdf 674b3ef 4b7dfdf 674b3ef 5ad56ab 2f2eb08 b62e12f 02d3c7e 2f2eb08 b62e12f 674b3ef 2f2eb08 b62e12f 674b3ef fc85e52 674b3ef fc85e52 2f2eb08 674b3ef 2f2eb08 674b3ef 2f2eb08 fc85e52 2f2eb08 674b3ef 8f35bfa 674b3ef 5b8f2b6 b62e12f d06ee78 5b8f2b6 d06ee78 5b8f2b6 d06ee78 5b8f2b6 d06ee78 b62e12f 674b3ef b62e12f 674b3ef 8f35bfa b62e12f 21292e2 cf9223c 21292e2 cf9223c 21292e2 cf9223c 5ad56ab 674b3ef 5ad56ab 674b3ef 5ad56ab 4b7dfdf 674b3ef d08d597 4b7dfdf 674b3ef 5ad56ab 4b7dfdf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | from __future__ import annotations
import json, os, re, time, asyncio
from pathlib import Path
from typing import Any, Dict, List, Optional
from datetime import datetime
# Load .env
_env_file = Path(__file__).resolve().parent.parent / ".env"
if _env_file.exists():
for _line in _env_file.read_text().splitlines():
_line = _line.strip()
if _line and not _line.startswith("#") and "=" in _line:
_k, _, _v = _line.partition("=")
os.environ.setdefault(_k.strip(), _v.strip())
from fastapi import FastAPI, HTTPException, UploadFile, File, Body, Header, Request, Response, Form
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, FileResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles
import requests as _requests
_KEY = os.getenv("gemini_api_key", "") or os.getenv("GEMINI_API_KEY", "")
BASE_DIR = Path(__file__).resolve().parent
def _load_patients() -> List[Dict[str, Any]]:
p = BASE_DIR / "sample_patients.json"
if p.exists():
try:
data = json.loads(p.read_text())
return data["patients"] if isinstance(data, dict) else data
except: return []
return []
PATIENTS: List[Dict[str, Any]] = _load_patients()
app = FastAPI(title="CodeBlue Context API", version="3.1.2")
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
import traceback
return JSONResponse(status_code=500, content={"detail": str(exc), "traceback": traceback.format_exc()})
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
# ββ Gemini helper βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def _gemini(prompt: str, retries: int = 4, override_key: str = None) -> Optional[Dict]:
key = (override_key or _KEY).strip()
if not key: return None
payload = {"contents": [{"parts": [{"text": prompt}]}], "generationConfig": {"temperature": 0.1, "maxOutputTokens": 2048}}
for model in ["gemini-2.5-flash-lite"]:
for attempt in range(retries):
try:
url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={key}"
resp = _requests.post(url, json=payload, timeout=25)
if resp.status_code == 200:
text = resp.json()["candidates"][0]["content"]["parts"][0]["text"].strip()
text = re.sub(r"^```(?:json)?\s*", "", text); text = re.sub(r"\s*```$", "", text)
try: return json.loads(text)
except:
m = re.search(r"\{.*\}", text, re.DOTALL)
if m: return json.loads(m.group(0))
time.sleep(1)
except: time.sleep(1)
return None
# ββ Agents ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def agent_triage(r, k=None):
p = f"Triage PATIENT: {json.dumps(r)}\nReturn JSON: {{'severity_label':'string','critical_risks':[]}}"
return _gemini(p, override_key=k) or {"severity_label":"URGENT","critical_risks":[]}
def agent_action(r, t, k=None):
p = f"Attending. PATIENT: {json.dumps(r)}\nTRIAGE: {json.dumps(t)}\nReturn JSON: {{'primary_directive':'string','clinical_summary':'string'}}"
return _gemini(p, override_key=k) or {"primary_directive":"Stabilize.","clinical_summary":"Offline."}
async def process_synthesis(text: str, key: str):
record = {"raw_text": text}
t = agent_triage(record, k=key)
a = agent_action(record, t, k=key)
return {"triage": t, "action": a}
# ββ MCP Standard (Optimized Discovery & Handshake) ββββββββββββββββββββββββββ
@app.api_route("/mcp", methods=["GET", "POST", "OPTIONS"])
async def mcp_discovery(req: Request):
if req.method == "OPTIONS": return Response(status_code=204)
# Discovery should point to a relative path for the SSE stream
return {
"transport": "sse",
"endpoint": "/mcp/sse",
"tools": [{"name": "analyze_clinical_record", "description": "Synthesize clinical records."}]
}
@app.get("/mcp/sse")
async def mcp_sse_stream(req: Request):
"""Handshake with manual CORS and Proxy Fixes"""
async def event_generator():
# Immediate endpoint event
yield f"event: endpoint\ndata: /mcp/invoke\n\n"
while True:
await asyncio.sleep(20)
yield ": keep-alive\n\n"
response = StreamingResponse(event_generator(), media_type="text/event-stream")
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Cache-Control"] = "no-cache"
response.headers["Connection"] = "keep-alive"
response.headers["X-Accel-Buffering"] = "no" # Fix for Hugging Face/Nginx buffering
return response
@app.post("/mcp/invoke")
async def mcp_invoke(req: Request, x_gemini_key: str = Header(None)):
try: body = await req.json()
except: return {"error": "Invalid JSON"}
method = body.get("method")
params = body.get("params", {})
msg_id = body.get("id")
if method == "initialize" or method == "list_tools" or method == "notifications/initialized":
return {
"jsonrpc": "2.0",
"id": msg_id,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": {"listChanged": False},
"resources": {"subscribe": False, "listChanged": False},
"prompts": {"listChanged": False}
},
"serverInfo": {
"name": "CodeBlue-Context",
"version": "1.0.0"
},
"tools": [
{
"name": "analyze_clinical_record",
"description": "Performs a 5-agent clinical synthesis of fragmented patient records to identify life-threatening risks.",
"input_schema": {
"type": "object",
"properties": {
"text": {"type": "string", "description": "Raw clinical data or patient history."}
},
"required": ["text"]
}
}
]
}
}
if method == "call_tool":
args = params.get("arguments", {})
res = await process_synthesis(args.get("text", ""), x_gemini_key)
return {
"jsonrpc": "2.0", "id": msg_id,
"result": {"content": [{"type": "text", "text": f"DIRECTIVE: {res['action']['primary_directive']}"}]}
}
return {"jsonrpc": "2.0", "result": {}, "id": msg_id}
# ββ A2A Standard (Strict Prompt Opinion Compliance) ββββββββββββββββββ
@app.get("/.well-known/agent-card.json")
def get_agent_card():
"""Official Prompt Opinion Discovery Card"""
return {
"name": "CodeBlue Context",
"version": "1.0.0",
"description": "ER Clinical Intelligence Synthesis Agent.",
"capabilities": ["clinical_synthesis"],
"endpoints": {
"invoke": "https://anil988-codeblue-context.hf.space/a2a/invoke"
},
"protocols": ["A2A", "MCP"],
"data_standards": ["FHIR-R4"]
}
@app.post("/a2a/invoke")
async def a2a_invoke(request: Dict[str, Any], x_gemini_key: str = Header(None)):
p = request.get("params", {})
res = await process_synthesis(p.get("text", ""), x_gemini_key)
return {"jsonrpc": "2.0", "result": {"directive": res["action"]["primary_directive"]}, "id": request.get("id")}
# ββ App Endpoints βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@app.get("/patients")
def get_patients(): return PATIENTS
@app.post("/patients/upload")
async def upload_patient(file: UploadFile = File(...)):
return {"id": "UP-" + str(int(time.time()) % 1000), "raw_text": (await file.read()).decode()}
@app.post("/analyze")
async def analyze_patient(data: Dict[str, Any], x_gemini_key: str = Header(None)):
res = await process_synthesis(data.get("raw_text", ""), x_gemini_key)
return {"triage": res["triage"], "action": res["action"], "contraindication": {"treatment_verdict":"SAFE"}, "labs": {"critical_labs":[]}, "differentials": {"differentials":[]}}
if os.path.exists("frontend/dist"):
app.mount("/", StaticFiles(directory="frontend/dist", html=True), name="frontend")
|