RyanCodrai/turbovec · error · ValueError

expected 2D embedding batch, got {vectors.ndim}D

Error message

expected 2D embedding batch, got {vectors.ndim}D

What it means

add() converts node embeddings to a numpy float32 batch and requires a 2D (N, dim) array. ndim != 2 means the embeddings are ragged, scalar, or empty, so the batch cannot be interpreted as a matrix of vectors.

Source

Thrown at turbovec-python/python/turbovec/llama_index.py:363

        # back to one of them — the earlier handles become orphans that
        # `query` later resolves through the duplicate node_id, returning
        # the second node's payload attached to the first node's vector.
        # Caller's job to deduplicate before calling add.
        node_ids = [n.node_id for n in nodes]
        try:
            resolve_duplicates(node_ids, DuplicatePolicy.REJECT)
        except ValueError:
            seen: set[str] = set()
            dup = next(nid for nid in node_ids if nid in seen or seen.add(nid))
            raise ValueError(
                f"duplicate node_id {dup!r} appears multiple times "
                "in the input batch; deduplicate before calling add()"
            ) from None

        embeddings = [node.get_embedding() for node in nodes]
        vectors = np.asarray(embeddings, dtype=np.float32)
        if vectors.ndim != 2:
            raise ValueError(
                f"expected 2D embedding batch, got {vectors.ndim}D"
            )
        # A batch of empty per-node embeddings has shape (N, 0) — 2D, so
        # it passes the ndim guard, then dies deep in the index kernel
        # with an opaque buffer-length error. Name the real cause instead.
        if vectors.shape[1] == 0:
            raise ValueError(
                "nodes have empty embeddings (dim 0); check the embed "
                "model that produced them"
            )
        # Build every side-car payload BEFORE mutating any state, so a
        # payload failure (e.g. non-serializable metadata) leaves the
        # store untouched. `metadata` and `ref_doc_id` are kept at top
        # level for fast filter / doc-id lookup (queries hit these on
        # every hit; parsing _node_content per hit would be wasteful).
        # `node_dict` is the framework's canonical metadata representation
        # (`_node_content` + `_node_type` + original metadata keys),
        # which `metadata_dict_to_node` reconstructs into a full

View on GitHub (pinned to ccab9f325e)

Solutions

  1. Ensure all nodes were embedded by the same model so every embedding has the same length
  2. Embed nodes before adding: call embed_model.get_text_embedding_batch([n.get_content() for n in nodes]) and set each node's embedding
  3. Check vectors.shape before calling add()

Example fix

// before
store.add(nodes)  # nodes not embedded
// after
embeddings = embed_model.get_text_embedding_batch([n.get_content() for n in nodes])
for n, e in zip(nodes, embeddings):
    n.embedding = e
store.add(nodes)
Defensive patterns

Strategy: validation

Validate before calling

import numpy as np
vecs = np.asarray([n.get_embedding() for n in nodes], dtype=np.float32)
assert vecs.ndim == 2, f"embedding batch is {vecs.ndim}D"

Type guard

def embeddings_are_2d(nodes) -> bool:
    import numpy as np
    return np.asarray([n.get_embedding() for n in nodes]).ndim == 2

Try / catch

try:
    store.add(nodes)
except ValueError as e:
    if "expected 2D embedding batch" in str(e):
        raise RuntimeError("nodes are inconsistently embedded; re-run embed model") from e
    raise

Prevention

When it happens

Trigger: Calling add() with nodes whose get_embedding() returns lists of unequal length (ragged), a single scalar, or an empty list across all nodes causing a non-2D array from np.asarray.

Common situations: Mixing embeddings from different embed models with different dimensions; nodes never embedded (None/empty embeddings); passing a single node's 1D embedding wrapped incorrectly.

Related errors


AI-assisted analysis of RyanCodrai/turbovec@ccab9f325e (2026-09-06). Data as JSON: /api/errors/ad0d77191a78bc6a. Report an issue: GitHub.