feat: pin the embedding model to the installation, not the release

EMBEDDINGS_NAME has a code-level default, and moving that default re-points an
existing index at a different vector space without anything noticing: mpnet and
granite are both 768-dimensional, so no width check fires and retrieval simply
gets worse. Which model an index was built with is a property of the
installation, not of the release it happens to be running.

So it is resolved once at boot and stored in app_metadata, which already exists
for exactly this kind of one-off state and needs no migration. An installation
that already has sources is pinned to the legacy model it has been using all
along and told, once, how to move; an empty one is pinned to the current
recommendation. An explicit EMBEDDINGS_NAME in the environment still wins over
both, and an unreachable database falls back to the code default.

This also closes a gap in what "new installs get granite" meant: it held for
setup.sh and for anyone copying .env-template, but a hand-written .env plus
docker compose fell through to the settings default and quietly got mpnet --
English-only, and a 384-token window against a 1250-token chunk default.

Emptiness is counted in sources rather than vector rows so the answer is the
same for every vector store, including the FAISS ones whose vectors are not in
this database at all. Concurrent workers converge through the same
INSERT ... ON CONFLICT DO NOTHING the instance id already uses.
This commit is contained in:
Alex committed 2026-08-28 13:14:35 +01:00
1 parent 47e53a71c4
commit 3242d68fd2
6 files changed
+380 -4

No files matched your search

@@ -265,3 +265,64 @@ class TestBootDoesNotLoadTheModel:
vector_schema = self._run(None, loader)
loader.assert_called_once()
assert vector_schema.call_args.kwargs["dimension"] == 1024
@pytest.mark.unit
class TestUnknownWidthIsProbed:
"""A remote server's width is only knowable by asking it.
``RemoteEmbeddings`` reports ``None`` until its first call, so sizing the
table from the attribute alone fell back to 768 and skipped the mismatch
check — the silent ``vector(768)`` column this hook exists to prevent.
"""
def _run(self, remote, table_dimension=1024):
conn = MagicMock()
conn.cursor.return_value = MagicMock()
with patch("psycopg.connect", return_value=conn), patch(
"application.vectorstore.model_registry.dimension_for", return_value=None
), patch(
"application.vectorstore.base.build_local_embeddings", return_value=remote
), patch(
"application.vectorstore.pgvector.PGVectorStore.create_schema"
) as vector_schema, patch(
"application.vectorstore.pgvector.PGVectorStore.table_dimension",
return_value=table_dimension,
):
try:
ensure_vector_schema()
raised = False
except RuntimeError:
raised = True
return vector_schema, raised
@staticmethod
def _remote(width=None, error=None):
remote = MagicMock()
remote.dimension = None
remote.embed_query.side_effect = error or (lambda _text: [0.0] * width)
return remote
def test_the_table_is_sized_from_the_probe(self, vector_settings):
remote = self._remote(width=1024)
vector_schema, _ = self._run(remote, table_dimension=1024)
remote.embed_query.assert_called_once()
assert vector_schema.call_args.kwargs["dimension"] == 1024
def test_the_probe_restores_the_mismatch_check(self, vector_settings):
_, raised = self._run(self._remote(width=768), table_dimension=1024)
assert raised, "a 768-dim model against a vector(1024) table must fail loudly"
def test_an_unreachable_server_does_not_block_boot(self, vector_settings):
vector_schema, raised = self._run(
self._remote(error=ConnectionError("server down")), table_dimension=1024
)
assert not raised
assert vector_schema.call_args.kwargs["dimension"] == 768
def test_a_model_that_knows_its_width_is_not_probed(self, vector_settings):
local = MagicMock()
local.dimension = 384
vector_schema, _ = self._run(local, table_dimension=384)
local.embed_query.assert_not_called()
assert vector_schema.call_args.kwargs["dimension"] == 384