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
Showing
1 changed file
with
10 additions
and
6 deletions
| ... | @@ -10,9 +10,6 @@ from fastapi import FastAPI, File, HTTPException, UploadFile | ... | @@ -10,9 +10,6 @@ from fastapi import FastAPI, File, HTTPException, UploadFile |
| 10 | from pydantic import BaseModel | 10 | from pydantic import BaseModel |
| 11 | 11 | ||
| 12 | from src.data.voice_chunker import voice_to_chunks | 12 | from src.data.voice_chunker import voice_to_chunks |
| 13 | from src.engines.chromaprint_matcher import ChromaprintMatcher | ||
| 14 | from src.engines.ecapa_embedder import ECAPAEmbedder | ||
| 15 | from src.engines.hybrid_engine import HybridEngine | ||
| 16 | from src.service.settings import ServiceSettings | 13 | from src.service.settings import ServiceSettings |
| 17 | from src.utils.context_exporter import export_match_context, find_best_matching_window | 14 | from src.utils.context_exporter import export_match_context, find_best_matching_window |
| 18 | 15 | ||
| ... | @@ -35,7 +32,7 @@ class BuildIndexRequest(BaseModel): | ... | @@ -35,7 +32,7 @@ class BuildIndexRequest(BaseModel): |
| 35 | 32 | ||
| 36 | app = FastAPI(title='ACR Service', version='0.4.0') | 33 | app = FastAPI(title='ACR Service', version='0.4.0') |
| 37 | settings = ServiceSettings() | 34 | settings = ServiceSettings() |
| 38 | _engine_cache: dict[tuple[str, str, str, str], HybridEngine] = {} | 35 | _engine_cache: dict[tuple[str, str, str, str], object] = {} |
| 39 | _cache_lock = Lock() | 36 | _cache_lock = Lock() |
| 40 | 37 | ||
| 41 | 38 | ||
| ... | @@ -67,7 +64,14 @@ def _readiness_snapshot(data_dir: str, model_path: str, index_prefix: str) -> di | ... | @@ -67,7 +64,14 @@ def _readiness_snapshot(data_dir: str, model_path: str, index_prefix: str) -> di |
| 67 | return {'ready': all(item['exists'] for item in files.values()), 'files': files, 'manifests': manifest_candidates} | 64 | return {'ready': all(item['exists'] for item in files.values()), 'files': files, 'manifests': manifest_candidates} |
| 68 | 65 | ||
| 69 | 66 | ||
| 70 | def _load_engine_uncached(data_dir: str, model_path: str, index_prefix: str, device: str) -> HybridEngine: | 67 | def _load_engine_uncached(data_dir: str, model_path: str, index_prefix: str, device: str): |
| 68 | try: | ||
| 69 | from src.engines.chromaprint_matcher import ChromaprintMatcher | ||
| 70 | from src.engines.ecapa_embedder import ECAPAEmbedder | ||
| 71 | from src.engines.hybrid_engine import HybridEngine | ||
| 72 | except Exception as exc: | ||
| 73 | raise HTTPException(status_code=500, detail=f"Engine dependencies unavailable: {exc}") | ||
| 74 | |||
| 71 | matcher = ChromaprintMatcher() | 75 | matcher = ChromaprintMatcher() |
| 72 | chroma_path = str(Path(index_prefix).parent / 'chromaprint.pkl') | 76 | chroma_path = str(Path(index_prefix).parent / 'chromaprint.pkl') |
| 73 | if not Path(chroma_path).exists(): | 77 | if not Path(chroma_path).exists(): |
| ... | @@ -93,7 +97,7 @@ def _load_engine_uncached(data_dir: str, model_path: str, index_prefix: str, dev | ... | @@ -93,7 +97,7 @@ def _load_engine_uncached(data_dir: str, model_path: str, index_prefix: str, dev |
| 93 | return engine | 97 | return engine |
| 94 | 98 | ||
| 95 | 99 | ||
| 96 | def _load_engine(data_dir: str, model_path: str, index_prefix: str, device: str) -> tuple[HybridEngine, bool]: | 100 | def _load_engine(data_dir: str, model_path: str, index_prefix: str, device: str): |
| 97 | key = (str(Path(data_dir).resolve()), str(Path(model_path).resolve()), str(Path(index_prefix).resolve()), device) | 101 | key = (str(Path(data_dir).resolve()), str(Path(model_path).resolve()), str(Path(index_prefix).resolve()), device) |
| 98 | with _cache_lock: | 102 | with _cache_lock: |
| 99 | cached = _engine_cache.get(key) | 103 | cached = _engine_cache.get(key) | ... | ... |
-
Please register or sign in to post a comment