feat: index semantic records in qdrant
This commit is contained in:
+78
-1
@@ -152,8 +152,14 @@ class ThothVectorHttpConfig(BaseModel):
|
||||
direct: DatabaseConfig | None = None
|
||||
|
||||
|
||||
class QdrantConfig(BaseModel):
|
||||
type: Literal["qdrant"]
|
||||
base_url: str
|
||||
collection: str = Field(min_length=1)
|
||||
|
||||
|
||||
VectorResourceConfig = Annotated[
|
||||
PgvectorDirectConfig | ThothVectorHttpConfig,
|
||||
PgvectorDirectConfig | ThothVectorHttpConfig | QdrantConfig,
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
@@ -397,6 +403,7 @@ def load_config(path: Path) -> Config:
|
||||
raise ConfigError(f"Configurazione non valida (atteso un mapping YAML): {path}")
|
||||
expanded = _resolve_secret_files(_expand_env(raw))
|
||||
_validate_internal_embedding_contract(expanded, path)
|
||||
_validate_internal_vector_contract(expanded, path)
|
||||
translated, used_legacy = translate_legacy_config(expanded)
|
||||
_populate_legacy_views(translated)
|
||||
try:
|
||||
@@ -455,6 +462,7 @@ def load_config(path: Path) -> Config:
|
||||
else path.resolve().as_posix()
|
||||
)
|
||||
_validate_active_embeddings_config(cfg.embeddings, path)
|
||||
_validate_active_vector_config(cfg.vectors, path)
|
||||
return cfg
|
||||
|
||||
|
||||
@@ -500,6 +508,42 @@ def _validate_internal_embedding_contract(raw: dict[str, Any], path: Path) -> No
|
||||
)
|
||||
|
||||
|
||||
def _validate_internal_vector_contract(raw: dict[str, Any], path: Path) -> None:
|
||||
resources = raw.get("resources")
|
||||
if not isinstance(resources, dict):
|
||||
return
|
||||
vector = resources.get("vector")
|
||||
if not isinstance(vector, dict):
|
||||
return
|
||||
|
||||
engine = vector.get("engine")
|
||||
base_url = vector.get("base_url")
|
||||
collection = vector.get("collection")
|
||||
allowed = {"engine", "base_url", "collection"}
|
||||
unexpected = sorted(set(vector) - allowed)
|
||||
if unexpected:
|
||||
raise ConfigError(
|
||||
f"Configurazione non valida in {path}:\n"
|
||||
f"resources.vector non supporta: {', '.join(unexpected)}"
|
||||
)
|
||||
if engine != "qdrant":
|
||||
raise ConfigError(
|
||||
f"Configurazione non valida in {path}:\n"
|
||||
"resources.vector.engine deve essere 'qdrant'"
|
||||
)
|
||||
if not isinstance(collection, str) or not collection:
|
||||
raise ConfigError(
|
||||
f"Configurazione non valida in {path}:\n"
|
||||
"resources.vector.collection deve essere valorizzato"
|
||||
)
|
||||
if not _is_allowed_internal_qdrant_url(base_url):
|
||||
raise ConfigError(
|
||||
f"Configurazione non valida in {path}:\n"
|
||||
"resources.vector.base_url deve usare http://qdrant:6333 "
|
||||
"oppure un endpoint loopback di sviluppo su porta 6333"
|
||||
)
|
||||
|
||||
|
||||
def _validate_active_embeddings_config(
|
||||
embeddings: "EmbeddingsConfig | None",
|
||||
path: Path,
|
||||
@@ -529,6 +573,20 @@ def _validate_active_embeddings_config(
|
||||
)
|
||||
|
||||
|
||||
def _validate_active_vector_config(
|
||||
vectors: "VectorResourceConfig | None",
|
||||
path: Path,
|
||||
) -> None:
|
||||
if vectors is None or vectors.type != "qdrant":
|
||||
return
|
||||
if not _is_allowed_internal_qdrant_url(vectors.base_url):
|
||||
raise ConfigError(
|
||||
f"Configurazione non valida in {path}:\n"
|
||||
"vectors.base_url deve usare http://qdrant:6333 "
|
||||
"oppure un endpoint loopback di sviluppo su porta 6333"
|
||||
)
|
||||
|
||||
|
||||
def _is_allowed_internal_embedding_url(value: Any) -> bool:
|
||||
if not isinstance(value, str):
|
||||
return False
|
||||
@@ -548,6 +606,25 @@ def _is_allowed_internal_embedding_url(value: Any) -> bool:
|
||||
return host.is_loopback
|
||||
|
||||
|
||||
def _is_allowed_internal_qdrant_url(value: Any) -> bool:
|
||||
if not isinstance(value, str):
|
||||
return False
|
||||
parsed = urlparse(value)
|
||||
if parsed.scheme != "http" or not parsed.hostname or parsed.port != 6333:
|
||||
return False
|
||||
if parsed.params or parsed.query or parsed.fragment:
|
||||
return False
|
||||
if parsed.path not in ("", "/"):
|
||||
return False
|
||||
if parsed.hostname == "qdrant":
|
||||
return True
|
||||
try:
|
||||
host = ip_address(parsed.hostname)
|
||||
except ValueError:
|
||||
return parsed.hostname == "localhost"
|
||||
return host.is_loopback
|
||||
|
||||
|
||||
def _populate_legacy_views(raw: dict[str, Any]) -> None:
|
||||
"""Populate old Config attributes for command compatibility during migration."""
|
||||
dwh = raw.get("dwh")
|
||||
|
||||
Reference in New Issue
Block a user