Migrate to a new embedding model
|
Available on NAMS: No. The procedure on this page needs the Bolt backend. With the hosted NAMS backend its operations are unavailable: depending on the call, the client raises |
Change the embedding model for an existing Bolt database while preserving the source data needed to regenerate vectors. A model change requires a backfill even when the old and new models have the same number of dimensions: their vectors are not interchangeable.
Prerequisites
-
A database backup or disposable test copy, plus the original model identifier and settings.
-
Source text for every vector you intend to preserve, and access to the new embedding provider.
-
A maintenance window that pauses writes and vector searches until backfill and validation finish.
-
The Python SDK with the extra for the new embedding provider, for example
pip install 'neo4j-agent-memory[openai]==0.7.0'. This runbook uses public Bolt graph/schema APIs; it is not a hosted workspace migration tool.
1. Inspect the existing indexes and choose a migration path
Run in Neo4j Query or through a connected Bolt client’s graph.execute_read:
SHOW VECTOR INDEXES YIELD name, state, options
RETURN name, state, options
The library-managed vector index names are message_embedding_idx,
entity_embedding_idx, preference_embedding_idx, fact_embedding_idx,
task_embedding_idx, and step_embedding_idx.
MemoryClient.connect() sets up schema and validates existing managed vector
index dimensions. A mismatch raises EmbeddingDimensionMismatchError with
expected_dimensions, actual_dimensions, and index_name. The message lists
all mismatches; the latter two attributes describe the first one. Validation
compares dimensions, not model identity or every stored vector.
| Situation | Action |
|---|---|
Keep the original model |
Restore its settings and retain the existing vectors and indexes. |
Disposable data |
Re-import source data into a fresh database configured for the new model. |
Preserve existing entities and IDs |
Backfill vectors in a paused database or a separate copy, following the remaining steps. |
Missing source text or unknown original model |
Recover that information before claiming equivalent retrieval. Rebuilding indexes alone cannot reconstruct vectors. |
2. Remove only the indexes that must change dimension
Keep the application paused. Use a Neo4j administration connection, or connect a Bolt client with the old embedding settings so dimension validation succeeds. Do not connect with the new model and expect to drop old indexes from inside that context: connection fails before reaching the body.
For a migration of all six managed vector indexes, execute these statements individually in Neo4j Query:
DROP INDEX message_embedding_idx IF EXISTS;
DROP INDEX entity_embedding_idx IF EXISTS;
DROP INDEX preference_embedding_idx IF EXISTS;
DROP INDEX fact_embedding_idx IF EXISTS;
DROP INDEX task_embedding_idx IF EXISTS;
DROP INDEX step_embedding_idx IF EXISTS;
Dropping an index does not remove node embedding properties. Old vectors remain until you overwrite or explicitly remove them. For a same-dimension model change, index recreation is not required, but the complete backfill is.
Avoid client.schema.drop_all() for a vector-only migration: it removes
constraints and indexes selected by library name prefixes, including
conversation_, entity_, and message_. A user-created schema element sharing
a prefix can also be selected.
3. Connect with the new model and backfill from source text
Configure the new provider as in Configure an embedding provider. After the old incompatible indexes are gone, connecting with the new settings creates missing indexes at the new dimensions. Keep traffic paused: this step has not regenerated existing vectors.
Use a separate provider instance with the same configuration for the backfill.
The following helper covers entities only and expects an already connected
Bolt client and an EmbeddingProvider named embedder. Adapt it for each
populated memory layer in the table below before resuming traffic.
async def reembed_entities(client, embedder, batch_size=200):
if batch_size < 1:
raise ValueError("batch_size must be positive")
after_id = ""
updated = 0
while True:
rows = await client.graph.execute_read(
"MATCH (e:Entity) WHERE e.id > $after_id AND e.name IS NOT NULL "
"RETURN e.id AS id, e.name AS text ORDER BY e.id LIMIT $limit",
{"after_id": after_id, "limit": batch_size},
)
if not rows:
return updated
vectors = await embedder.embed([row["text"] for row in rows])
if len(vectors) != len(rows) or any(
len(vector) != embedder.dimensions for vector in vectors
):
raise ValueError("Embedding provider returned an unexpected shape")
await client.graph.execute_write(
"UNWIND $rows AS row MATCH (e:Entity {id: row.id}) "
"SET e.embedding = row.vector",
{"rows": [{"id": row["id"], "vector": vector}
for row, vector in zip(rows, vectors)]},
)
updated += len(rows)
after_id = rows[-1]["id"]
The helper awaits each batch write. It uses stable ID ordering, requires non-null string IDs, and can be restarted from the beginning with the same model. It is not a resumable migration service. Record progress and preserve the backup if a provider call or database write fails.
Match the source text used by the normal write APIs:
| Node | Property | Text to embed |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Nonempty thought, action, and observation values prefixed with |
Inspect records with missing source text rather than silently counting them as migrated. If you choose different text construction for your application, apply it consistently to future writes and validate retrieval quality.
memory.write_mode="buffered" does not defer these graph.execute_write
calls. An explicit buffered migration would need separate submission, draining,
and error handling; synchronous batch writes make completion easier to verify.
4. Verify the migration before restoring traffic
-
Compare expected and updated node counts for every populated layer. Audit missing IDs and missing source text separately.
-
Check vector lengths and missing-vector counts. For example:
MATCH (e:Entity) RETURN size(e.embedding) AS dimensions, count(*) AS entities ORDER BY dimensions -
Confirm all six managed indexes that your database uses are
ONLINEwith the intended dimension inSHOW VECTOR INDEXES. -
With the new provider, run
await client.schema.validate_vector_index_dimensions(embedder.dimensions). A successful connection alone is not evidence that every old vector was replaced. -
Run labeled retrieval cases from Evaluate memory quality, including older records, before restoring traffic.
The dimension validator skips validation if the server rejects the SHOW VECTOR
INDEXES syntax. Other query failures propagate. Do not treat a skipped check
as proof that the schema or migration is correct.
If validation fails, keep traffic paused and restore the database backup with the original model settings. Reverting settings alone after a partial backfill would leave mixed-model vectors.