diff --git a/headroom/cache/dynamic_detector.py b/headroom/cache/dynamic_detector.py index 5030862e9..3c5117f83 100644 --- a/headroom/cache/dynamic_detector.py +++ b/headroom/cache/dynamic_detector.py @@ -777,20 +777,23 @@ class SemanticDetector: return [], "numpy not installed. Install with: pip install numpy" sentence_texts = [s[0] for s in sentences] - if self._model is None or self._exemplar_embeddings is None: + # `is_available` only guarantees `_model` is set. Guard each piece + # separately and *before* encoding so a None never reaches `.T` (a + # real crash), mypy can narrow the `Any | None` attributes, and the + # caller gets a warning that names the actual missing piece — the + # model vs. the exemplar matrix. (Folding both into one guard, as a + # prior change did, returned the generic "semantic detector" message + # even when only the exemplars were missing.) + if self._model is None: return [], self._load_error or "semantic detector is not initialized" + if self._exemplar_embeddings is None: + return [], "exemplar embeddings not initialized" sentence_embeddings = self._model.encode( sentence_texts, convert_to_numpy=True, ) - # Compute similarities. `is_available` only guarantees `_model` is - # set; guard the exemplar matrix explicitly so a None never reaches - # `.T` (real crash) and mypy can narrow the `Any | None` attribute. - if self._exemplar_embeddings is None: - return [], "exemplar embeddings not initialized" - similarities = np.dot(sentence_embeddings, self._exemplar_embeddings.T) for i, (text, start, end) in enumerate(sentences): diff --git a/tests/test_cache/test_dynamic_detector.py b/tests/test_cache/test_dynamic_detector.py index 154a309d9..309efa66f 100644 --- a/tests/test_cache/test_dynamic_detector.py +++ b/tests/test_cache/test_dynamic_detector.py @@ -486,7 +486,9 @@ class TestSemanticDetector: spans, warning = detector.detect("The current stock price changes every minute.") assert spans == [] - assert warning == "semantic detector is not initialized" + # Model present but exemplar matrix missing → the warning names the + # actual missing piece (matches TestSemanticDetectorGuards below). + assert warning == "exemplar embeddings not initialized" class TestIntegrationWithAllTiers: