Coverage for src \ truenex_memory \ core \ memory_service.py: 94%
54 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-19 10:21 +0200
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-19 10:21 +0200
1"""High-level local memory service used by CLI and adapters."""
3from __future__ import annotations
5from pathlib import Path
7from truenex_memory.core.config import ensure_project_dirs, resolve_project_config
8from truenex_memory.core.indexer import index_path
9from truenex_memory.retrieval.semantic import HashingEmbedder
10from truenex_memory.store.models import MemoryNode, RetrievalLog, SearchHit
11from truenex_memory.store.qdrant_store import QdrantVectorStore, VectorStoreUnavailable
12from truenex_memory.store.repository import MemoryRepository
15class MemoryService:
16 """Facade around local configuration, indexing and repository operations."""
18 def __init__(self, project_root: Path | str = ".") -> None:
19 self.config = resolve_project_config(project_root)
20 self.embedder = HashingEmbedder()
21 self.vector_store_status: dict[str, object] = {
22 "backend": self.config.vector_backend,
23 "active_backend": "sqlite",
24 "qdrant_url": self.config.qdrant_url,
25 "qdrant_collection": self.config.qdrant_collection,
26 "available": False,
27 "error": None,
28 }
29 vector_store = None
30 if self.config.vector_backend == "qdrant":
31 try:
32 vector_store = QdrantVectorStore(
33 collection_name=self.config.qdrant_collection,
34 dimensions=self.embedder.dimensions,
35 url=self.config.qdrant_url,
36 )
37 vector_store.initialize()
38 self.vector_store_status.update({"active_backend": "qdrant", "available": True})
39 except VectorStoreUnavailable as exc:
40 self.vector_store_status.update({"error": str(exc)})
41 self.repository = MemoryRepository(
42 self.config.db_path,
43 embedder=self.embedder,
44 vector_store=vector_store,
45 )
47 def init_project(self) -> None:
48 ensure_project_dirs(self.config)
49 self.repository.initialize()
51 def add(self, content: str, *, memory_type: str = "note") -> str:
52 self.init_project()
53 return self.repository.add_memory(content, memory_type=memory_type)
55 def index(self, path: Path | str) -> int:
56 self.init_project()
57 return index_path(Path(path), project_root=self.config.project_root, repository=self.repository)
59 def search(self, query: str, *, top_k: int = 5, include_inactive: bool = False) -> list[SearchHit]:
60 self.init_project()
61 return self.repository.search(query, top_k=top_k, include_inactive=include_inactive)
63 def list_memory_nodes(self, *, status: str | None = None) -> list[MemoryNode]:
64 self.init_project()
65 return self.repository.list_memory_nodes(status=status)
67 def set_memory_status(self, memory_id: str, status: str) -> None:
68 self.init_project()
69 self.repository.set_memory_status(memory_id, status)
71 def stats(self) -> dict[str, int]:
72 self.init_project()
73 return self.repository.stats()
75 def list_retrieval_logs(self, *, limit: int = 20) -> list[RetrievalLog]:
76 self.init_project()
77 return self.repository.list_retrieval_logs(limit=limit)
79 def get_retrieval_log(self, trace_id: str) -> RetrievalLog | None:
80 self.init_project()
81 return self.repository.get_retrieval_log(trace_id)
83 @property
84 def last_trace_id(self) -> str | None:
85 return self.repository.last_trace_id
87 def vector_status(self) -> dict[str, object]:
88 """Return vector backend status without exposing project content."""
90 return dict(self.vector_store_status)