Managed Embeddings (Cypher API)¶
NornicDB can generate embeddings server-side and store them on nodes for you. From the consumer's point of view this is a Cypher feature: a WITH EMBEDDING clause on writes, and a db.index.vector.embed procedure for ad-hoc embedding.
Two storage slots, both readable¶
Every node has two embedding slots:
ChunkEmbeddings— managed by NornicDB.[][]float32— one entry per chunk;ChunkEmbeddings[0]is the primary embedding. Built by the embedding worker or byWITH EMBEDDING. Internal metadata (model,dimensions,chunk_count,embedded_at) is stored separately so it does not polluteProperties.NamedEmbeddings— client-managed.map[string][]float32, keyed by vector name (e.g."default","openai-small"). Set viadb.create.setNodeVectorPropertyor the Qdrant gRPC compatibility layer.
Vector index lookup order is NamedEmbeddings[indexProperty] → node.Properties[indexProperty] → ChunkEmbeddings. You usually want a vector index on a label whose nodes have managed embeddings, in which case nothing extra is needed.
Enabling managed embeddings¶
export NORNICDB_EMBEDDING_ENABLED=true
export NORNICDB_EMBEDDING_PROVIDER=ollama # ollama | openai | local
export NORNICDB_EMBEDDING_MODEL=mxbai-embed-large
export NORNICDB_EMBEDDING_API_URL=http://localhost:11434
export NORNICDB_EMBEDDING_DIMENSIONS=1024
YAML form:
embedding:
enabled: true
provider: ollama # ollama | openai | local
model: mxbai-embed-large
url: http://localhost:11434
dimensions: 1024
The OpenAI provider also reads embedding.api_key (or NORNICDB_EMBEDDING_API_KEY). The local provider resolves the model file inside NORNICDB_MODELS_DIR.
Defaults shipped with NornicDB: provider=local, model=bge-m3, dimensions=1024.
Provider configuration¶
| Provider | Required env | Notes |
|---|---|---|
ollama | NORNICDB_EMBEDDING_API_URL=http://host:11434 | ollama pull <model> first; works offline |
openai | NORNICDB_EMBEDDING_API_KEY=sk-... | Models: text-embedding-3-small (1536), text-embedding-3-large (3072) |
local | NORNICDB_MODELS_DIR=./models | Resolves ${NORNICDB_MODELS_DIR}/${NORNICDB_EMBEDDING_MODEL}.gguf; set NORNICDB_EMBEDDING_GPU_LAYERS=-1 for auto-GPU |
Embedding text — which properties contribute¶
By default the embedding worker builds the text from all node properties + node labels (excluding internal metadata). To restrict it:
# Embed only specific properties (and labels)
export NORNICDB_EMBEDDING_PROPERTIES_INCLUDE=content,title
# Always strip these properties from embedding text
export NORNICDB_EMBEDDING_PROPERTIES_EXCLUDE=internal_id,raw_html
# Drop labels from the embedding text entirely
export NORNICDB_EMBEDDING_INCLUDE_LABELS=false
YAML equivalents under embedding_worker: — properties_include, properties_exclude, include_labels. If both include and exclude are set, exclude wins per-key.
WITH EMBEDDING — embed during writes¶
WITH EMBEDDING is a NornicDB extension that embeds (or re-embeds) every node touched by the preceding mutation. Append it to the end of any write statement:
-- Single create + embed
CREATE (d:Doc {id: 'd1', content: 'hello world'})
WITH EMBEDDING
RETURN count(d) AS created
-- Multiple creates in one transaction
CREATE
(a:Doc {id: 'a1', content: 'alpha'}),
(b:Doc {id: 'b1', content: 'beta'})
WITH EMBEDDING
RETURN count(*) AS created
-- Update content and re-embed
MATCH (d:Doc {id: 'd1'})
SET d.content = 'updated content'
WITH EMBEDDING
RETURN d.id
-- Bulk import + embed
UNWIND $items AS item
MERGE (h:Item {id: item.id})
SET h.text = item.text
WITH EMBEDDING
RETURN count(h) AS processed
Behavior: - Runs inside the same implicit transaction as the mutation. If embedding fails, the whole write rolls back. - Builds embedding text using the current properties_include / properties_exclude / include_labels config. - Chunks long text, generates one vector per chunk, and writes them all into ChunkEmbeddings. The first chunk is the primary embedding. - Stores model name, dimensions, chunk count, and timestamp in internal EmbedMeta. - Errors: - WITH EMBEDDING requires configured embedder — set NORNICDB_EMBEDDING_ENABLED=true and pick a provider. - WITH EMBEDDING requires transaction-capable storage — running on a legacy storage engine; not common in modern deployments.
Embedding ad-hoc text¶
Use it to feed a vector into a downstream search step, or to compute query embeddings client-side without leaving Cypher:
CALL db.index.vector.embed('zero-trust authentication patterns')
YIELD embedding
CALL db.index.vector.queryNodes('docEmbeddings', 10, embedding)
YIELD node, score
RETURN node.title, score
Setting client-managed vectors¶
For pre-computed embeddings (e.g. from a different model), use the Neo4j-compatible procedure to write into NamedEmbeddings:
CALL db.create.setNodeVectorProperty(
'nornic:abc-123',
'embedding', -- this becomes the property name; index on it the same way
[0.7, 0.2, 0.05, 0.05]
)
Or just SET a property containing a list of floats:
A vector index whose property matches will pick up either form.
Inspecting embedding state¶
-- Coverage check
MATCH (d:Doc) RETURN count(d) AS total, count(d.has_embedding) AS embedded
-- Pending background work (when embedding is async)
-- HTTP: GET /status returns .embeddings { pending, processed_total, errors }
-- Embedded-text-side tuning has no Cypher diagnostic — re-run WITH EMBEDDING to refresh
Common patterns¶
Re-embed after a property edit¶
Bulk re-embed after changing the embedding model¶
UNWIND $batch AS row
MATCH (d:Doc {id: row.id})
SET d.touchedAt = timestamp()
WITH EMBEDDING
RETURN count(d) AS reembedded
(Run in batches of e.g. 200 to keep memory usage bounded.)
Skip embedding for some labels¶
There is no per-label opt-out in WITH EMBEDDING — every node touched by the mutation is re-embedded. Structure the statement so it only matches the labels you want to embed.
Gotchas¶
WITH EMBEDDINGis a NornicDB extension, not standard Cypher. It will not parse on Neo4j.- The embedder must be configured before the mutation starts; otherwise the whole transaction fails.
- Dimensions are model-specific. Switching models requires re-embedding (the search service compares vector lengths against the index).
NORNICDB_EMBEDDING_DIMENSIONSis used to validate provider responses. It does not silently truncate or pad.- The local provider resolves the model file as
${NORNICDB_MODELS_DIR}/${NORNICDB_EMBEDDING_MODEL}.gguf. Setting onlyNORNICDB_EMBEDDING_MODEL=...without putting the matching.ggufin the models dir will fail at first use. - Cache (
NORNICDB_EMBEDDING_CACHE_SIZE, default 10000) is per-process and not persisted. Restarts re-embed identical text on the next call. - If you write
n.embedding = [...]directly and also rely onWITH EMBEDDING, the managedChunkEmbeddingsslot is what the vector index sees by default — your manual property may be ignored. Pick one path per label.
See also¶
nornicdb-vector-search— the consumer of the vectors you generate here.nornicdb-rag-procedures—db.retrieve,db.rerank,db.inferfor end-to-end RAG.