fix: support qdrant-only vector maintenance

This commit is contained in:
2026-08-08 18:14:03 +02:00
parent 5e39cfa347
commit 32cd2165eb
9 changed files with 261 additions and 28 deletions
+27 -2
View File
@@ -3,8 +3,10 @@
import json
import re
from psycopg2 import Error as PsycopgError
from psycopg2 import sql
from sqlalchemy import Engine
from sqlalchemy.exc import SQLAlchemyError
from tht.config import DatabaseConfig
from tht.db.connection import make_engine
@@ -19,7 +21,6 @@ from tht.ports.vector import (
)
from tht.vectorstore.store import VectorHit, hit_from_metadata
COLLECTION_KINDS = {
"schema_records": {"schema_table", "schema_column"},
"evidence": {"evidence"},
@@ -243,7 +244,7 @@ class PgVectorStore:
return True, None, dimensions
finally:
raw.close()
except Exception as exc:
except (AttributeError, TypeError, ValueError, PsycopgError, SQLAlchemyError) as exc:
return False, f"vector database probe failed: {type(exc).__name__}", set()
def health(self) -> VectorHealth:
@@ -471,6 +472,30 @@ class PgVectorStore:
if raw is not None:
raw.close()
def delete_kinds(self, collection: str, kinds: list[str]) -> int:
_collection(self._schema, collection)
_validate_collection_kinds(collection, kinds)
raw = None
try:
raw = self._require_writer().raw_connection()
with raw.cursor() as cursor:
cursor.execute(
sql.SQL("DELETE FROM {} WHERE kind = ANY(%s)").format(
_collection(self._schema, collection)
),
(kinds,),
)
count = cursor.rowcount
raw.commit()
return count
except Exception as exc:
if raw is not None:
raw.rollback()
raise VectorWriteUnavailable("Vector kind cleanup unavailable") from exc
finally:
if raw is not None:
raw.close()
def list_evidence_generations(self, collection: str, workspace_id: str) -> list[str]:
if collection != "evidence":
raise VectorStoreError("Only exact Evidence generations may be listed")
+15
View File
@@ -212,6 +212,21 @@ class QdrantVectorStore:
)
return len(records)
def delete_kinds(self, collection: str, kinds: list[str]) -> int:
_collection("vectors", collection)
_validate_collection_kinds(collection, kinds)
must = [
*self._workspace_filter(),
{"key": "record_kind", "match": {"any": sorted(kinds)}},
]
before = len(self._scroll(must))
self._call(
"POST",
f"/collections/{self._collection}/points/delete?wait=true",
{"filter": {"must": must}},
)
return before
def delete_generation(self, collection: str, generation: str, workspace_id: str) -> int:
if collection != "evidence" or _GENERATION.fullmatch(generation) is None:
raise VectorStoreError("Only exact Evidence generations may be deleted")
+14 -6
View File
@@ -2,6 +2,11 @@
import re
from tht.adapters.vector.pgvector import (
_collection,
_validate_collection_kinds,
_validate_known_kinds,
)
from tht.ports.vector import (
VectorCapabilities,
VectorHealth,
@@ -14,11 +19,6 @@ from tht.ports.vector import (
)
from tht.vectorstore.rest_client import VectorRestClient, VectorRestError
from tht.vectorstore.store import hit_from_metadata
from tht.adapters.vector.pgvector import (
_collection,
_validate_collection_kinds,
_validate_known_kinds,
)
def _merge(hits: list[VectorHit], limit: int) -> list[VectorHit]:
@@ -85,7 +85,7 @@ class ThothHttpVectorStore:
return None, None, []
try:
return True, None, client.list_tables()
except Exception as exc:
except (RuntimeError, VectorRestError) as exc:
return False, str(exc), []
def search(
@@ -155,6 +155,14 @@ class ThothHttpVectorStore:
except VectorRestError as exc:
raise VectorStoreError(str(exc)) from exc
def delete_kinds(self, collection: str, kinds: list[str]) -> int:
_collection("vectors", collection)
_validate_collection_kinds(collection, kinds)
try:
return self._require_writer().delete_kinds(collection, kinds)
except VectorRestError as exc:
raise VectorStoreError(str(exc)) from exc
def delete_generation(self, collection: str, generation: str, workspace_id: str) -> int:
if collection != "evidence" or re.fullmatch(r"gen:[0-9a-f]{32}", generation) is None:
raise VectorStoreError("Only exact Evidence generations may be deleted")