Spaces:
Running
Running
File size: 5,570 Bytes
3ca1d38 9659593 3ca1d38 9659593 3ca1d38 9659593 3ca1d38 9659593 3ca1d38 696f787 3ca1d38 696f787 3ca1d38 696f787 3ca1d38 696f787 3ca1d38 696f787 3ca1d38 9659593 3ca1d38 9659593 3ca1d38 9659593 3ca1d38 696f787 3ca1d38 696f787 3ca1d38 696f787 3ca1d38 9659593 3ca1d38 696f787 3ca1d38 696f787 3ca1d38 696f787 3ca1d38 696f787 3ca1d38 696f787 3ca1d38 696f787 3ca1d38 9659593 3ca1d38 696f787 3ca1d38 696f787 3ca1d38 9659593 3ca1d38 9659593 3ca1d38 696f787 3ca1d38 | 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 | """
MediGuard AI — Retriever Factory
Auto-selects the best available retriever backend:
1. OpenSearch (production) if OPENSEARCH_* env vars are set
2. FAISS (local) if vector store exists at data/vector_stores/
3. Raises error if neither is available
Usage:
from src.services.retrieval import get_retriever
retriever = get_retriever() # Auto-selects best backend
results = retriever.retrieve("What are normal glucose levels?")
"""
from __future__ import annotations
import logging
import os
from functools import lru_cache
from pathlib import Path
from src.services.retrieval.interface import BaseRetriever
logger = logging.getLogger(__name__)
# Detection flags
_OPENSEARCH_AVAILABLE = bool(os.environ.get("OPENSEARCH__HOST") or os.environ.get("OPENSEARCH_HOST"))
_FAISS_PATH = Path(os.environ.get("FAISS_VECTOR_STORE", "data/vector_stores"))
def _detect_backend() -> str:
"""
Detect the best available retriever backend.
Returns:
"opensearch" or "faiss"
Raises:
RuntimeError: If no backend is available
"""
# Priority 1: OpenSearch (production)
if _OPENSEARCH_AVAILABLE:
try:
from src.services.opensearch.client import make_opensearch_client
client = make_opensearch_client()
if client.ping():
logger.info("Auto-detected backend: OpenSearch (cluster reachable)")
return "opensearch"
else:
logger.warning("OpenSearch configured but not reachable, checking FAISS...")
except Exception as exc:
logger.warning("OpenSearch init failed (%s), checking FAISS...", exc)
# Priority 2: FAISS (local/HuggingFace)
faiss_index = _FAISS_PATH / "medical_knowledge.faiss"
if faiss_index.exists():
logger.info("Auto-detected backend: FAISS (index found at %s)", faiss_index)
return "faiss"
# Check alternative locations
alt_paths = [
Path("huggingface/data/vector_stores/medical_knowledge.faiss"),
Path("vector_stores/medical_knowledge.faiss"),
]
for alt in alt_paths:
if alt.exists():
logger.info("Auto-detected backend: FAISS (index found at %s)", alt)
return "faiss"
# No backend found
raise RuntimeError(
"No retriever backend available. Either:\n"
" - Set OPENSEARCH__HOST for OpenSearch\n"
" - Ensure data/vector_stores/medical_knowledge.faiss exists for FAISS\n"
"Run: python -m src.pdf_processor to create the FAISS index."
)
def make_retriever(
backend: str | None = None,
*,
embedding_model=None,
vector_store_path: str | None = None,
opensearch_client=None,
embedding_service=None,
) -> BaseRetriever:
"""
Create a retriever instance.
Args:
backend: "faiss", "opensearch", or None for auto-detect
embedding_model: Embedding model for FAISS
vector_store_path: Path to FAISS index directory
opensearch_client: OpenSearch client instance
embedding_service: Embedding service for OpenSearch vector search
Returns:
Configured BaseRetriever implementation
Raises:
RuntimeError: If the requested backend is unavailable
"""
if backend is None:
backend = _detect_backend()
backend = backend.lower()
if backend == "faiss":
from src.services.retrieval.faiss_retriever import FAISSRetriever
if embedding_model is None:
from src.llm_config import get_embedding_model
embedding_model = get_embedding_model()
path = vector_store_path or str(_FAISS_PATH)
# Try multiple paths
paths_to_try = [
path,
"huggingface/data/vector_stores",
"data/vector_stores",
]
for p in paths_to_try:
try:
return FAISSRetriever.from_local(p, embedding_model)
except FileNotFoundError:
continue
raise RuntimeError(f"FAISS index not found in any of: {paths_to_try}")
elif backend == "opensearch":
from src.services.retrieval.opensearch_retriever import OpenSearchRetriever
if opensearch_client is None:
from src.services.opensearch.client import make_opensearch_client
opensearch_client = make_opensearch_client()
return OpenSearchRetriever(
opensearch_client,
embedding_service=embedding_service,
)
else:
raise ValueError(f"Unknown retriever backend: {backend}")
@lru_cache(maxsize=1)
def get_retriever() -> BaseRetriever:
"""
Get a cached retriever instance (auto-detected backend).
This is the recommended way to get a retriever in most cases.
Uses LRU cache to avoid repeated initialization.
Returns:
Cached BaseRetriever implementation
"""
return make_retriever()
# Environment hints for deployment
def print_backend_info() -> None:
"""Print information about the detected retriever backend."""
try:
backend = _detect_backend()
retriever = make_retriever(backend)
print(f"Retriever Backend: {retriever.backend_name}")
print(f" Health: {'OK' if retriever.health() else 'DEGRADED'}")
print(f" Documents: {retriever.doc_count():,}")
except Exception as exc:
print("Retriever Backend: NOT AVAILABLE")
print(f" Error: {exc}")
if __name__ == "__main__":
# Quick diagnostic
logging.basicConfig(level=logging.INFO)
print_backend_info()
|