{"record":{"id":"ad0d77191a78bc6a","repo":"RyanCodrai/turbovec","slug":"expected-2d-embedding-batch-got-vectors-ndim-d-ad0d77","errorCode":null,"errorMessage":"expected 2D embedding batch, got {vectors.ndim}D","messagePattern":"expected 2D embedding batch, got (.+?)D","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"turbovec-python/python/turbovec/llama_index.py","lineNumber":363,"sourceCode":"        # back to one of them — the earlier handles become orphans that\n        # `query` later resolves through the duplicate node_id, returning\n        # the second node's payload attached to the first node's vector.\n        # Caller's job to deduplicate before calling add.\n        node_ids = [n.node_id for n in nodes]\n        try:\n            resolve_duplicates(node_ids, DuplicatePolicy.REJECT)\n        except ValueError:\n            seen: set[str] = set()\n            dup = next(nid for nid in node_ids if nid in seen or seen.add(nid))\n            raise ValueError(\n                f\"duplicate node_id {dup!r} appears multiple times \"\n                \"in the input batch; deduplicate before calling add()\"\n            ) from None\n\n        embeddings = [node.get_embedding() for node in nodes]\n        vectors = np.asarray(embeddings, dtype=np.float32)\n        if vectors.ndim != 2:\n            raise ValueError(\n                f\"expected 2D embedding batch, got {vectors.ndim}D\"\n            )\n        # A batch of empty per-node embeddings has shape (N, 0) — 2D, so\n        # it passes the ndim guard, then dies deep in the index kernel\n        # with an opaque buffer-length error. Name the real cause instead.\n        if vectors.shape[1] == 0:\n            raise ValueError(\n                \"nodes have empty embeddings (dim 0); check the embed \"\n                \"model that produced them\"\n            )\n        # Build every side-car payload BEFORE mutating any state, so a\n        # payload failure (e.g. non-serializable metadata) leaves the\n        # store untouched. `metadata` and `ref_doc_id` are kept at top\n        # level for fast filter / doc-id lookup (queries hit these on\n        # every hit; parsing _node_content per hit would be wasteful).\n        # `node_dict` is the framework's canonical metadata representation\n        # (`_node_content` + `_node_type` + original metadata keys),\n        # which `metadata_dict_to_node` reconstructs into a full","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/RyanCodrai/turbovec/blob/ccab9f325e6ce2a270a87daf01ae4e443bcf2d49/turbovec-python/python/turbovec/llama_index.py#L345-L381","documentation":"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.","triggerScenarios":"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.","commonSituations":"Mixing embeddings from different embed models with different dimensions; nodes never embedded (None/empty embeddings); passing a single node's 1D embedding wrapped incorrectly.","solutions":["Ensure all nodes were embedded by the same model so every embedding has the same length","Embed nodes before adding: call embed_model.get_text_embedding_batch([n.get_content() for n in nodes]) and set each node's embedding","Check vectors.shape before calling add()"],"exampleFix":"// before\nstore.add(nodes)  # nodes not embedded\n// after\nembeddings = embed_model.get_text_embedding_batch([n.get_content() for n in nodes])\nfor n, e in zip(nodes, embeddings):\n    n.embedding = e\nstore.add(nodes)","handlingStrategy":"validation","validationCode":"import numpy as np\nvecs = np.asarray([n.get_embedding() for n in nodes], dtype=np.float32)\nassert vecs.ndim == 2, f\"embedding batch is {vecs.ndim}D\"","typeGuard":"def embeddings_are_2d(nodes) -> bool:\n    import numpy as np\n    return np.asarray([n.get_embedding() for n in nodes]).ndim == 2","tryCatchPattern":"try:\n    store.add(nodes)\nexcept ValueError as e:\n    if \"expected 2D embedding batch\" in str(e):\n        raise RuntimeError(\"nodes are inconsistently embedded; re-run embed model\") from e\n    raise","preventionTips":["Embed all nodes with one model before add()","Validate embedding lengths are uniform in pipeline tests","Never mix embedding sources in a single batch"],"tags":["python","numpy","shape-mismatch","embedding"],"backgroundTag":"shape-mismatch","analyzedSha":"ccab9f325e6ce2a270a87daf01ae4e443bcf2d49","analyzedAt":"2026-09-06T08:39:18.516Z","contentChangedAt":"2026-09-06T08:39:18.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}