{"record":{"id":"55e39160e901cdcb","repo":"MemPalace/mempalace","slug":"embedding-api-at-self-url-returned-a-non-object","errorCode":null,"errorMessage":"Embedding API at {self._url} returned a non-object response: {data}","messagePattern":"Embedding API at (.+?) returned a non-object response: (.+?)","errorType":"exception","errorClass":"EmbeddingAPIError","httpStatus":null,"severity":"error","filePath":"mempalace/embedding.py","lineNumber":580,"sourceCode":"                ) from e\n            out.extend(self._vectors_from_response(data, len(batch)))\n        return out\n\n    def _vectors_from_response(self, data, n: int) -> list:\n        \"\"\"Validate one ``/v1/embeddings`` response and return L2-normed vectors.\n\n        Guards every way a non-conformant server could corrupt the store\n        silently: a missing/short ``data`` array, response ``index`` values\n        that aren't the contiguous ``0..n-1`` batch positions (sorting then\n        zipping positionally would otherwise misalign vectors with texts), and\n        malformed / ragged / base64 embedding payloads. All failures raise\n        :class:`EmbeddingAPIError` naming the endpoint rather than a cryptic\n        numpy error — a silent wrong result would break the 100%-recall promise.\n        \"\"\"\n        import numpy as np\n\n        if not isinstance(data, dict):\n            raise EmbeddingAPIError(\n                f\"Embedding API at {self._url} returned a non-object response: {data}\"\n            )\n        rows = data.get(\"data\")\n        if not isinstance(rows, list):\n            raise EmbeddingAPIError(\n                f\"Embedding API at {self._url} returned no 'data' array: {data.get('error', data)}\"\n            )\n        if len(rows) != n:\n            raise EmbeddingAPIError(\n                f\"Embedding API at {self._url} returned {len(rows)} embeddings for {n} inputs\"\n            )\n        # The endpoint may return rows out of order — sort by index, then\n        # require the indices to be exactly 0..n-1 so positional alignment is\n        # provably correct (a server using absolute or duplicate indices would\n        # otherwise pass the count check yet map vectors to the wrong texts).\n        try:\n            rows = sorted(rows, key=lambda d: d.get(\"index\", -1))\n            indices = [r.get(\"index\") for r in rows]","sourceCodeStart":562,"sourceCodeEnd":598,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/embedding.py#L562-L598","documentation":"Raised by _vectors_from_response when the parsed JSON body of an /v1/embeddings response is not a top-level object (dict). A conformant server returns {\"data\": [...]}; anything else — a bare list, a string, a number, null — cannot be validated further and would otherwise crash later with a cryptic KeyError or numpy error. The whole offending payload is echoed in the message for diagnosis.","triggerScenarios":"The configured openai-compat endpoint returns a JSON array, a plain string, or an error page parsed as JSON — e.g. pointing MEMPALACE_EMBEDDING_API_URL at a non-OpenAI REST service, a misrouted reverse proxy that returns a status payload, or a mock/stub server used in tests.","commonSituations":"URL points at the wrong API (e.g. a models list endpoint or a management API); a gateway (nginx traefik) returns a JSON status object on error paths; hand-rolled stub servers during development that return [{...}] instead of {\"data\": [...]}","solutions":["Curl the endpoint manually and confirm the body shape is a JSON object containing a 'data' array","Point MEMPALACE_EMBEDDING_API_URL at a real OpenAI-compatible /v1/embeddings endpoint","If writing a stub server, wrap results: {\"data\": [{\"index\": 0, \"embedding\": [...]}, ...]}","Check for a proxy or gateway rewriting responses"],"exampleFix":"# stub server — before\nreturn JsonResponse([{\"index\": 0, \"embedding\": vec}])\n# after\nreturn JsonResponse({\"data\": [{\"index\": 0, \"embedding\": vec}]})","handlingStrategy":"validation","validationCode":"import json, urllib.request\n\ndef probe_embeddings_endpoint(url: str, model: str) -> bool:\n    payload = json.dumps({\"model\": model, \"input\": [\"probe\"], \"encoding_format\": \"float\"}).encode()\n    req = urllib.request.Request(url.rstrip('/') , data=payload, headers={\"Content-Type\": \"application/json\"})\n    with urllib.request.urlopen(req, timeout=10) as r:\n        return isinstance(json.loads(r.read()), dict)  # must be a top-level object","typeGuard":"def is_embeddings_response_shape(data) -> bool:\n    return isinstance(data, dict) and isinstance(data.get(\"data\"), list)","tryCatchPattern":"try:\n    vecs = ef(texts)\nexcept EmbeddingAPIError as e:\n    if \"non-object response\" in str(e):\n        log.error(\"endpoint %s is not OpenAI-compatible\", url)  # config error: fix the URL\n    raise","preventionTips":["Point mempalace only at endpoints implementing POST /v1/embeddings","Add a one-request probe at startup of your pipeline to fail fast","When writing stub servers, return the full {\"data\": [...]} envelope"],"tags":["embedding","api","validation","response-format"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}