shak3008's picture
Deploy CDSS Multi-Agent Biomedical System
82d7112
Raw History Blame Contribute Delete
4.18 kB
"""
FastAPI application for the Vector Retrieval Service.
Provides a REST endpoint to search the Qdrant medical knowledge base.
"""
import logging
from typing import Optional, List, Dict, Any
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from search_engine import MedicalSearchEngine
# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(name)s | %(levelname)s | %(message)s")
logger = logging.getLogger(__name__)
app = FastAPI(
title="Vector Retrieval Service",
description="Search the medical knowledge Qdrant vector database using BioBERT embeddings.",
version="1.0.0",
)
# Initialize search engine at startup (loads BioBERT model)
search_engine: Optional[MedicalSearchEngine] = None
@app.on_event("startup")
def startup():
"""Load the BioBERT model and connect to Qdrant on startup."""
global search_engine
logger.info("Starting Vector Retrieval Service...")
try:
search_engine = MedicalSearchEngine()
logger.info("Vector Retrieval Service ready.")
except Exception as e:
logger.error("Failed to initialize search engine: %s", e)
raise
@app.on_event("shutdown")
def shutdown():
"""Close the Qdrant connection on shutdown."""
if search_engine:
search_engine.close()
# ── Request / Response Models ──────────────────────────────────────────────
class SearchRequest(BaseModel):
"""Request body for the /search endpoint."""
query: str = Field(..., description="The search query text", min_length=1)
top_k: int = Field(5, description="Number of results to return", ge=1, le=50)
source_type: Optional[str] = Field(None, description="Filter by source type (e.g., 'medical-paper')")
disease_category: Optional[str] = Field(None, description="Filter by disease category")
min_year: Optional[int] = Field(None, description="Minimum publication year")
max_year: Optional[int] = Field(None, description="Maximum publication year")
class SearchResult(BaseModel):
"""A single search result."""
score: float
raw_score: Optional[float] = None
text: str
source_id: str
source_type: str
publication_year: Optional[int] = None
disease_category: str = ""
evidence_level: str = ""
section_title: str = ""
journal: str = ""
class SearchResponse(BaseModel):
"""Response body for the /search endpoint."""
query: str
top_k: int
total_found: int
fallback_level_used: int
results: List[SearchResult]
# ── Endpoints ──────────────────────────────────────────────────────────────
@app.get("/")
def root():
"""Health check."""
return {"service": "Vector Retrieval Service", "status": "running"}
@app.post("/search", response_model=SearchResponse)
def search(request: SearchRequest):
"""
Search the medical knowledge base using BioBERT semantic search.
Uses multi-level fallback:
- Level 1: Cosine similarity >= 0.45
- Level 2: Cosine similarity >= 0.30
- Level 3: Simplified query, no threshold
"""
if search_engine is None:
raise HTTPException(status_code=503, detail="Search engine not initialized")
# Build optional filters
filter_kwargs = {}
if request.source_type:
filter_kwargs["source_type"] = request.source_type
if request.disease_category:
filter_kwargs["disease_category"] = request.disease_category
if request.min_year:
filter_kwargs["min_year"] = request.min_year
if request.max_year:
filter_kwargs["max_year"] = request.max_year
result = search_engine.search_with_fallback(
query=request.query,
limit=request.top_k,
**filter_kwargs,
)
return SearchResponse(
query=request.query,
top_k=request.top_k,
total_found=result["total_found"],
fallback_level_used=result["fallback_level_used"],
results=result["results"],
)