fix: support qdrant-only vector maintenance
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from tht.cli import app
|
||||
from tht.memory import MemoryRecord, save_registry
|
||||
|
||||
|
||||
class _FakeEmbedder:
|
||||
def embed_documents(self, documents):
|
||||
return [[0.1] * 4 for _ in documents]
|
||||
|
||||
|
||||
class _FakeVectorStore:
|
||||
def __init__(self):
|
||||
self.upserts = []
|
||||
self.deleted = []
|
||||
|
||||
def existing_hashes(self, collection, kinds):
|
||||
return {}
|
||||
|
||||
def upsert(self, collection, records):
|
||||
self.upserts.append((collection, records))
|
||||
return len(records)
|
||||
|
||||
def delete_kinds(self, collection, kinds):
|
||||
self.deleted.append((collection, list(kinds)))
|
||||
return 3
|
||||
|
||||
|
||||
def _qdrant_runtime_config(tmp_path: Path) -> Path:
|
||||
cfg = tmp_path / "workspace.yaml"
|
||||
cfg.write_text(
|
||||
f"""
|
||||
runtime_identity:
|
||||
workspace_id: psd-clinical
|
||||
workspace_revision: {'a' * 40}
|
||||
dwh:
|
||||
type: postgres_direct
|
||||
connection: {{database: analytics, schema: mart, user: reader, password: secret}}
|
||||
vectors:
|
||||
type: qdrant
|
||||
base_url: http://qdrant:6333
|
||||
collection: psd-clinical
|
||||
roots:
|
||||
sessions: {tmp_path / 'sessions'}
|
||||
artifacts: {tmp_path / 'artifacts'}
|
||||
indexes: {tmp_path / 'indexes'}
|
||||
embeddings:
|
||||
provider: ollama_internal
|
||||
base_url: http://embedding:11434
|
||||
model: qwen3-embedding:0.6b
|
||||
dim: 1024
|
||||
"""
|
||||
)
|
||||
return cfg
|
||||
|
||||
|
||||
def _write_schema_artifacts(tmp_path: Path) -> None:
|
||||
(tmp_path / "artifacts" / "mschema").mkdir(parents=True, exist_ok=True)
|
||||
(tmp_path / "artifacts" / "mschema" / "physical.yaml").write_text(
|
||||
"""
|
||||
database: analytics
|
||||
schema: mart
|
||||
introspected_at: 2026-01-01T00:00:00+00:00
|
||||
tables:
|
||||
fact_patient:
|
||||
comment: Patients
|
||||
columns:
|
||||
id:
|
||||
type: bigint
|
||||
"""
|
||||
)
|
||||
(tmp_path / "artifacts" / "mschema" / "annotations.yaml").write_text(
|
||||
"tables: {}\n"
|
||||
)
|
||||
|
||||
|
||||
def _memory_record() -> MemoryRecord:
|
||||
return MemoryRecord(
|
||||
id="mem-0001",
|
||||
ts=datetime(2026, 1, 1, tzinfo=UTC),
|
||||
session_id="s1",
|
||||
decision_seq=7,
|
||||
type="concept_clarified",
|
||||
subject="paziente attivo",
|
||||
detail="flag_attivo = TRUE",
|
||||
rationale="r",
|
||||
question_context="dammi i pazienti attivi",
|
||||
tables=[],
|
||||
concepts=["paziente attivo"],
|
||||
)
|
||||
|
||||
|
||||
def test_vector_index_schema_accepts_qdrant_only_runtime_config(tmp_path, monkeypatch):
|
||||
cfg = _qdrant_runtime_config(tmp_path)
|
||||
_write_schema_artifacts(tmp_path)
|
||||
store = _FakeVectorStore()
|
||||
|
||||
monkeypatch.setattr("tht.adapters.factory.build_vector_store", lambda cfg, require_write: store)
|
||||
monkeypatch.setattr("tht.cli.vector_cmd.make_embedder", lambda _: _FakeEmbedder())
|
||||
|
||||
res = CliRunner().invoke(app, ["vector", "index-schema", "-c", str(cfg)])
|
||||
|
||||
assert res.exit_code == 0, res.output
|
||||
assert store.upserts
|
||||
|
||||
|
||||
def test_memory_promote_accepts_qdrant_only_runtime_config(tmp_path, monkeypatch):
|
||||
cfg = _qdrant_runtime_config(tmp_path)
|
||||
store = _FakeVectorStore()
|
||||
promoted = [_memory_record()]
|
||||
snapshot = SimpleNamespace(manifest=SimpleNamespace(id="s1"))
|
||||
|
||||
monkeypatch.setattr("tht.cli.memory_cmd.load_snapshot_or_exit", lambda cfg, session: snapshot)
|
||||
monkeypatch.setattr("tht.memory.promote_snapshot", lambda *args, **kwargs: promoted)
|
||||
monkeypatch.setattr("tht.memory.load_registry", lambda path: promoted)
|
||||
monkeypatch.setattr("tht.adapters.factory.build_vector_store", lambda cfg, require_write: store)
|
||||
monkeypatch.setattr("tht.cli.vector_cmd.make_embedder", lambda _: _FakeEmbedder())
|
||||
|
||||
res = CliRunner().invoke(
|
||||
app,
|
||||
["memory", "promote", "--session", "s1", "--decision", "7", "--json", "-c", str(cfg)],
|
||||
)
|
||||
|
||||
assert res.exit_code == 0, res.output
|
||||
assert json.loads(res.stdout)["indexed"] is True
|
||||
assert store.upserts
|
||||
|
||||
|
||||
def test_memory_index_accepts_qdrant_only_runtime_config(tmp_path, monkeypatch):
|
||||
cfg = _qdrant_runtime_config(tmp_path)
|
||||
store = _FakeVectorStore()
|
||||
records = [_memory_record()]
|
||||
|
||||
save_registry(records, tmp_path / "artifacts" / "memory" / "registry.jsonl")
|
||||
monkeypatch.setattr("tht.adapters.factory.build_vector_store", lambda cfg, require_write: store)
|
||||
monkeypatch.setattr("tht.cli.vector_cmd.make_embedder", lambda _: _FakeEmbedder())
|
||||
|
||||
res = CliRunner().invoke(app, ["memory", "index", "-c", str(cfg)])
|
||||
|
||||
assert res.exit_code == 0, res.output
|
||||
assert "OK:" in res.output
|
||||
assert store.upserts
|
||||
|
||||
|
||||
def test_memory_clear_accepts_qdrant_only_runtime_config(tmp_path, monkeypatch):
|
||||
cfg = _qdrant_runtime_config(tmp_path)
|
||||
store = _FakeVectorStore()
|
||||
records = [_memory_record()]
|
||||
registry = tmp_path / "artifacts" / "memory" / "registry.jsonl"
|
||||
save_registry(records, registry)
|
||||
|
||||
monkeypatch.setattr("tht.adapters.factory.build_vector_store", lambda cfg, require_write: store)
|
||||
|
||||
res = CliRunner().invoke(app, ["memory", "clear", "--yes", "-c", str(cfg)])
|
||||
|
||||
assert res.exit_code == 0, res.output
|
||||
assert not registry.exists()
|
||||
Reference in New Issue
Block a user