Commit fa5b6147 fa5b61472888ffd6c44865480cc764e35e97de20 by cnb.bofCdSsphPA

Let the ACR API start before heavy model dependencies load

Constraint: service health and config endpoints should stay reachable even when training-time dependencies like torch are not installed
Rejected: importing retrieval engines at module load | it makes the whole API crash before reporting dependency gaps clearly
Confidence: high
Scope-risk: narrow
Directive: keep runtime dependency checks inside request-time engine loading so infra can health-check the service independently of model installation state
Tested: /usr/local/miniconda3/bin/python -m unittest discover -s acr-engine/tests -v; /usr/local/miniconda3/bin/python -m uvicorn src.service.app:app --host 127.0.0.1 --port 8000 with successful /health response; POST /recognize/voice currently returns a clear 500 dependency error when torch is missing
Not-tested: successful end-to-end /recognize/voice inference without torch installed
1 parent bd66c06b
......@@ -10,9 +10,6 @@ from fastapi import FastAPI, File, HTTPException, UploadFile
from pydantic import BaseModel
from src.data.voice_chunker import voice_to_chunks
from src.engines.chromaprint_matcher import ChromaprintMatcher
from src.engines.ecapa_embedder import ECAPAEmbedder
from src.engines.hybrid_engine import HybridEngine
from src.service.settings import ServiceSettings
from src.utils.context_exporter import export_match_context, find_best_matching_window
......@@ -35,7 +32,7 @@ class BuildIndexRequest(BaseModel):
app = FastAPI(title='ACR Service', version='0.4.0')
settings = ServiceSettings()
_engine_cache: dict[tuple[str, str, str, str], HybridEngine] = {}
_engine_cache: dict[tuple[str, str, str, str], object] = {}
_cache_lock = Lock()
......@@ -67,7 +64,14 @@ def _readiness_snapshot(data_dir: str, model_path: str, index_prefix: str) -> di
return {'ready': all(item['exists'] for item in files.values()), 'files': files, 'manifests': manifest_candidates}
def _load_engine_uncached(data_dir: str, model_path: str, index_prefix: str, device: str) -> HybridEngine:
def _load_engine_uncached(data_dir: str, model_path: str, index_prefix: str, device: str):
try:
from src.engines.chromaprint_matcher import ChromaprintMatcher
from src.engines.ecapa_embedder import ECAPAEmbedder
from src.engines.hybrid_engine import HybridEngine
except Exception as exc:
raise HTTPException(status_code=500, detail=f"Engine dependencies unavailable: {exc}")
matcher = ChromaprintMatcher()
chroma_path = str(Path(index_prefix).parent / 'chromaprint.pkl')
if not Path(chroma_path).exists():
......@@ -93,7 +97,7 @@ def _load_engine_uncached(data_dir: str, model_path: str, index_prefix: str, dev
return engine
def _load_engine(data_dir: str, model_path: str, index_prefix: str, device: str) -> tuple[HybridEngine, bool]:
def _load_engine(data_dir: str, model_path: str, index_prefix: str, device: str):
key = (str(Path(data_dir).resolve()), str(Path(model_path).resolve()), str(Path(index_prefix).resolve()), device)
with _cache_lock:
cached = _engine_cache.get(key)
......