{"record":{"id":"7eabafee9bf80e81","repo":"MemPalace/mempalace","slug":"embedding-api-at-self-url-returned-non-vector-e","errorCode":null,"errorMessage":"Embedding API at {self._url} returned non-vector embeddings (shape {arr.shape})","messagePattern":"Embedding API at (.+?) returned non-vector embeddings \\(shape (.+?)\\)","errorType":"exception","errorClass":"EmbeddingAPIError","httpStatus":null,"severity":"error","filePath":"mempalace/embedding.py","lineNumber":615,"sourceCode":"            rows = sorted(rows, key=lambda d: d.get(\"index\", -1))\n            indices = [r.get(\"index\") for r in rows]\n        except AttributeError as e:\n            raise EmbeddingAPIError(\n                f\"Embedding API at {self._url} returned non-object rows: {e}\"\n            ) from e\n        if indices != list(range(n)):\n            raise EmbeddingAPIError(\n                f\"Embedding API at {self._url} returned non-contiguous or duplicate \"\n                f\"'index' values; cannot align embeddings with inputs\"\n            )\n        try:\n            arr = np.asarray([r[\"embedding\"] for r in rows], dtype=np.float32)\n        except (KeyError, TypeError, ValueError) as e:\n            raise EmbeddingAPIError(\n                f\"Embedding API at {self._url} returned malformed embeddings: {e}\"\n            ) from e\n        if arr.ndim != 2:\n            raise EmbeddingAPIError(\n                f\"Embedding API at {self._url} returned non-vector embeddings (shape {arr.shape})\"\n            )\n        # L2-normalize so cosine == dot product (collection uses\n        # hnsw:space=cosine), matching EmbeddinggemmaONNX above.\n        norms = np.linalg.norm(arr, axis=1, keepdims=True) + 1e-12\n        return (arr / norms).tolist()\n\n\ndef get_embedding_function(device: Optional[str] = None, model: Optional[str] = None):\n    \"\"\"Return a cached embedding function for the requested device + model.\n\n    ``device=None`` reads :attr:`MempalaceConfig.embedding_device`;\n    ``model=None`` reads :attr:`MempalaceConfig.embedding_model`.\n    The returned function is shared across calls with the same resolved\n    provider list + model so we only pay model-load cost once per process.\n    \"\"\"\n    if device is None or model is None:\n        from .config import MempalaceConfig","sourceCodeStart":597,"sourceCodeEnd":633,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/embedding.py#L597-L633","documentation":"Raised by _vectors_from_response when the assembled float32 array is not 2-dimensional (arr.ndim != 2). After the row checks pass, this catches payloads where per-row 'embedding' values collapse into a scalar or inflate into extra dimensions (e.g. every value is itself a nested list), which the shape in the message reveals. Vectors must be a clean (n, dim) matrix before L2 normalization, or the cosine-space store would receive garbage.","triggerScenarios":"All rows have scalar embeddings (\"embedding\": 0.5), each 'embedding' is a nested list ([[...]]) producing ndim=3, or an empty-batch edge where the array degenerates. Shape in the message distinguishes these: (3,) means scalars; (3, 2, 768) means doubly-nested.","commonSituations":"Stub servers returning a single float per text; servers wrapping the vector in an extra list level; quantized endpoints returning per-value objects; JSON middleware that transforms arrays.","solutions":["Read the reported shape: (n,) => scalars, add a dimension server-side; (n, k, d) => extra nesting, flatten one level","Fix the server to return exactly a flat list of floats per row: \"embedding\": [f1, f2, ..., fd]","Validate with curl that one row's embedding is a flat numeric JSON array","Confirm the model actually produces dense vectors (a reranker/score endpoint does not)"],"exampleFix":"# stub — before\nreturn {\"data\": [{\"index\": 0, \"embedding\": score}]}      # scalar\n# or\nreturn {\"data\": [{\"index\": 0, \"embedding\": [vec]}]}     # nested\n# after\nreturn {\"data\": [{\"index\": 0, \"embedding\": vec.tolist()}]}  # flat floats","handlingStrategy":"validation","validationCode":"import numpy as np\narr = np.asarray([r[\"embedding\"] for r in resp[\"data\"]], dtype=np.float32)\nassert arr.ndim == 2, f\"expected 2-D (n, dim), got shape {arr.shape}\"","typeGuard":"def embeddings_form_matrix(data) -> bool:\n    try:\n        arr = np.asarray([r[\"embedding\"] for r in data[\"data\"]], dtype=np.float32)\n        return arr.ndim == 2 and arr.shape[1] > 0\n    except (KeyError, TypeError, ValueError):\n        return False","tryCatchPattern":"try:\n    vecs = ef(texts)\nexcept EmbeddingAPIError as e:\n    if \"non-vector embeddings\" in str(e):\n        log.error(\"shape %s — server emits scalars or nested vectors\", e)  # read shape from message\n    raise","preventionTips":["Do not point mempalace at score/rerank endpoints — they return scalars, not vectors","Stub servers must return a flat float list per text"],"tags":["embedding","api","validation","numpy","shape"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}