mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-28 07:35:27 -04:00
fix(health): report unhealthy memory vector store as degraded
Keep an unhealthy MemoryVectorStore instance available for health reporting instead of discarding it as disabled. This lets health checks report a degraded/down vector-store state while preserving focused regression coverage for initializer behavior.
This commit is contained in:
@@ -68,8 +68,10 @@ def initialize_managers(base_dir: str, rag_manager=None) -> Dict[str, Any]:
|
|||||||
logger.info(f"Rebuilt memory vector index from {len(existing)} existing entries")
|
logger.info(f"Rebuilt memory vector index from {len(existing)} existing entries")
|
||||||
logger.info("MemoryVectorStore initialized")
|
logger.info("MemoryVectorStore initialized")
|
||||||
else:
|
else:
|
||||||
|
# Keep the unhealthy object (do NOT reset to None): consumers gate on
|
||||||
|
# `.healthy`, and service_health.chromadb_health() needs a present
|
||||||
|
# object to report DEGRADED/DOWN instead of DISABLED ("not configured").
|
||||||
logger.warning("MemoryVectorStore DEGRADED: ChromaDB vector memory unavailable")
|
logger.warning("MemoryVectorStore DEGRADED: ChromaDB vector memory unavailable")
|
||||||
memory_vector = None
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"MemoryVectorStore DEGRADED: {e}")
|
logger.warning(f"MemoryVectorStore DEGRADED: {e}")
|
||||||
memory_vector = None
|
memory_vector = None
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
"""Regression: a present-but-unhealthy MemoryVectorStore must survive initialization.
|
||||||
|
|
||||||
|
When MemoryVectorStore._initialize() fails (ChromaDB unavailable / embeddings not
|
||||||
|
installed) it swallows the exception and leaves `.healthy == False` — the object
|
||||||
|
exists but is unhealthy. app_initializer.initialize_managers() previously reset that
|
||||||
|
object to ``None`` in the ``else`` branch, so service_health.chromadb_health() saw
|
||||||
|
``memory_vector is None`` and reported the vector memory as DISABLED ("not
|
||||||
|
configured") instead of DEGRADED/DOWN ("initialization failed") — losing the
|
||||||
|
diagnostic distinction the /api/diagnostics/services probe is built to surface.
|
||||||
|
|
||||||
|
This test fails before the fix (memory_vector is None) and passes after it.
|
||||||
|
"""
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
import src.app_initializer as app_init
|
||||||
|
import src.memory_vector as memory_vector_mod
|
||||||
|
import src.service_health as sh
|
||||||
|
|
||||||
|
|
||||||
|
class _UnhealthyVectorStore:
|
||||||
|
"""Stand-in for a MemoryVectorStore whose init failed: present but inert."""
|
||||||
|
healthy = False
|
||||||
|
|
||||||
|
def count(self):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def search(self, *a, **k):
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def _neutralize_collaborators(monkeypatch):
|
||||||
|
"""Stub out everything initialize_managers() builds except the vector store,
|
||||||
|
so the test isolates the memory_vector health-handling branch."""
|
||||||
|
for name in [
|
||||||
|
"MemoryManager", "SkillsManager", "SessionManager", "UploadHandler",
|
||||||
|
"PersonalDocsManager", "APIKeyManager", "PresetManager",
|
||||||
|
"MemoryProviderRegistry", "NativeMemoryProvider", "ChatProcessor",
|
||||||
|
"ResearchHandler", "ChatHandler", "ModelDiscovery",
|
||||||
|
]:
|
||||||
|
monkeypatch.setattr(app_init, name, lambda *a, **k: MagicMock())
|
||||||
|
monkeypatch.setattr(app_init, "set_session_manager", lambda *a, **k: None)
|
||||||
|
monkeypatch.setattr(app_init, "update_search_config", lambda *a, **k: None)
|
||||||
|
monkeypatch.setattr(app_init, "create_directories", lambda: None)
|
||||||
|
|
||||||
|
|
||||||
|
def test_failed_memory_vector_init_is_kept_not_discarded(monkeypatch, tmp_path):
|
||||||
|
_neutralize_collaborators(monkeypatch)
|
||||||
|
# initialize_managers does `from src.memory_vector import MemoryVectorStore`
|
||||||
|
# at call time, so patch it on the source module.
|
||||||
|
monkeypatch.setattr(
|
||||||
|
memory_vector_mod, "MemoryVectorStore",
|
||||||
|
lambda *a, **k: _UnhealthyVectorStore(),
|
||||||
|
)
|
||||||
|
|
||||||
|
result = app_init.initialize_managers(str(tmp_path), rag_manager=None)
|
||||||
|
|
||||||
|
mv = result["memory_vector"]
|
||||||
|
assert mv is not None, "unhealthy MemoryVectorStore was discarded (reported as DISABLED, not DEGRADED/DOWN)"
|
||||||
|
assert mv.healthy is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_chromadb_health_reports_down_for_unhealthy_vector_store():
|
||||||
|
# Pins the downstream taxonomy the fix feeds: a present-but-unhealthy vector
|
||||||
|
# store (rag absent) is DOWN, not DISABLED; with a healthy rag it is DEGRADED;
|
||||||
|
# only when both are absent is it DISABLED.
|
||||||
|
store = _UnhealthyVectorStore()
|
||||||
|
healthy_rag = MagicMock(healthy=True)
|
||||||
|
|
||||||
|
assert sh.chromadb_health(None, None)["status"] == sh.DISABLED
|
||||||
|
assert sh.chromadb_health(None, store)["status"] == sh.DOWN
|
||||||
|
assert sh.chromadb_health(healthy_rag, store)["status"] == sh.DEGRADED
|
||||||
Reference in New Issue
Block a user