MemPalace/mempalace · error · EmbeddingAPIError

Embedding API at {self._url} returned non-vector embeddings

Error message

Embedding API at {self._url} returned non-vector embeddings (shape {arr.shape})

What it means

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.

Source

Thrown at mempalace/embedding.py:615

            rows = sorted(rows, key=lambda d: d.get("index", -1))
            indices = [r.get("index") for r in rows]
        except AttributeError as e:
            raise EmbeddingAPIError(
                f"Embedding API at {self._url} returned non-object rows: {e}"
            ) from e
        if indices != list(range(n)):
            raise EmbeddingAPIError(
                f"Embedding API at {self._url} returned non-contiguous or duplicate "
                f"'index' values; cannot align embeddings with inputs"
            )
        try:
            arr = np.asarray([r["embedding"] for r in rows], dtype=np.float32)
        except (KeyError, TypeError, ValueError) as e:
            raise EmbeddingAPIError(
                f"Embedding API at {self._url} returned malformed embeddings: {e}"
            ) from e
        if arr.ndim != 2:
            raise EmbeddingAPIError(
                f"Embedding API at {self._url} returned non-vector embeddings (shape {arr.shape})"
            )
        # L2-normalize so cosine == dot product (collection uses
        # hnsw:space=cosine), matching EmbeddinggemmaONNX above.
        norms = np.linalg.norm(arr, axis=1, keepdims=True) + 1e-12
        return (arr / norms).tolist()


def get_embedding_function(device: Optional[str] = None, model: Optional[str] = None):
    """Return a cached embedding function for the requested device + model.

    ``device=None`` reads :attr:`MempalaceConfig.embedding_device`;
    ``model=None`` reads :attr:`MempalaceConfig.embedding_model`.
    The returned function is shared across calls with the same resolved
    provider list + model so we only pay model-load cost once per process.
    """
    if device is None or model is None:
        from .config import MempalaceConfig

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Read the reported shape: (n,) => scalars, add a dimension server-side; (n, k, d) => extra nesting, flatten one level
  2. Fix the server to return exactly a flat list of floats per row: "embedding": [f1, f2, ..., fd]
  3. Validate with curl that one row's embedding is a flat numeric JSON array
  4. Confirm the model actually produces dense vectors (a reranker/score endpoint does not)

Example fix

# stub — before
return {"data": [{"index": 0, "embedding": score}]}      # scalar
# or
return {"data": [{"index": 0, "embedding": [vec]}]}     # nested
# after
return {"data": [{"index": 0, "embedding": vec.tolist()}]}  # flat floats
Defensive patterns

Strategy: validation

Validate before calling

import numpy as np
arr = np.asarray([r["embedding"] for r in resp["data"]], dtype=np.float32)
assert arr.ndim == 2, f"expected 2-D (n, dim), got shape {arr.shape}"

Type guard

def embeddings_form_matrix(data) -> bool:
    try:
        arr = np.asarray([r["embedding"] for r in data["data"]], dtype=np.float32)
        return arr.ndim == 2 and arr.shape[1] > 0
    except (KeyError, TypeError, ValueError):
        return False

Try / catch

try:
    vecs = ef(texts)
except EmbeddingAPIError as e:
    if "non-vector embeddings" in str(e):
        log.error("shape %s — server emits scalars or nested vectors", e)  # read shape from message
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15). Data as JSON: /api/errors/7eabafee9bf80e81. Report an issue: GitHub.