{"record":{"id":"7591d698d9c125d6","repo":"MemPalace/mempalace","slug":"unexpected-response-shape-e","errorCode":null,"errorMessage":"Unexpected response shape: {e}","messagePattern":"Unexpected response shape: (.+?)","errorType":"exception","errorClass":"LLMError","httpStatus":null,"severity":"error","filePath":"mempalace/llm_client.py","lineNumber":358,"sourceCode":"    ) -> LLMResponse:\n        body: dict = {\n            \"model\": self.model,\n            \"messages\": [\n                {\"role\": \"system\", \"content\": system},\n                {\"role\": \"user\", \"content\": user},\n            ],\n            \"temperature\": 0.1,\n        }\n        if json_mode:\n            body[\"response_format\"] = {\"type\": \"json_object\"}\n        headers = {}\n        if self.api_key:\n            headers[\"Authorization\"] = f\"Bearer {self.api_key}\"\n        data = _http_post_json(self._resolve_url(), body, headers=headers, timeout=self.timeout)\n        try:\n            text = data[\"choices\"][0][\"message\"][\"content\"]\n        except (KeyError, IndexError, TypeError) as e:\n            raise LLMError(f\"Unexpected response shape: {e}\") from e\n        if not text:\n            raise LLMError(f\"Empty response from {self.name} (model={self.model})\")\n        return LLMResponse(text=text, model=self.model, provider=self.name, raw=data)\n\n\n# ==================== ANTHROPIC ====================\n\n\nclass AnthropicProvider(LLMProvider):\n    name = \"anthropic\"\n    DEFAULT_ENDPOINT = \"https://api.anthropic.com\"\n    API_VERSION = \"2023-06-01\"\n\n    def __init__(\n        self,\n        model: str,\n        api_key: Optional[str] = None,\n        endpoint: Optional[str] = None,","sourceCodeStart":340,"sourceCodeEnd":376,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/llm_client.py#L340-L376","documentation":"LLMError raised by OpenAICompatProvider.classify() when the response JSON lacks the expected choices[0].message.content path (KeyError, IndexError, or TypeError during extraction). The server answered 200 with valid JSON, but the shape is not an OpenAI chat completion.","triggerScenarios":"Pointing --llm-endpoint at a server that returns a different schema: an Ollama native API response ({message: {...}} without choices), a completions-style response, or an error object with HTTP 200.","commonSituations":"Using the Ollama base URL (localhost:11434) with the openai-compat provider instead of its /v1 compatibility layer; mixing up completions vs chat endpoints; server versions that change response fields; gateway wrapping responses in {data: ...}.","solutions":["Inspect the raw body (it is in e.__cause__ context or reproduce with curl) to see the actual schema","For Ollama, either use the ollama provider or its OpenAI-compatible /v1 route","Ensure the URL resolves to a true /v1/chat/completions endpoint","Update or pin a server version whose response format matches OpenAI's"],"exampleFix":"# before\nprovider = build_provider(\"openai-compat\", model=\"llama3\", endpoint=\"http://localhost:11434\")\n# server returns native Ollama schema {\"message\": ...} -> Unexpected response shape: 'choices'\n\n# after\nprovider = build_provider(\"ollama\", model=\"llama3\", endpoint=\"http://localhost:11434\")","handlingStrategy":"validation","validationCode":"import json, urllib.request\n\ndef looks_like_openai_chat(url, body, timeout=10):\n    req = urllib.request.Request(url, data=json.dumps(body).encode(), headers={\"Content-Type\": \"application/json\"})\n    with urllib.request.urlopen(req, timeout=timeout) as r:\n        data = json.loads(r.read())\n    return isinstance(data.get(\"choices\"), list) and data[\"choices\"]","typeGuard":null,"tryCatchPattern":"from mempalace.llm_client import LLMError\n\ntry:\n    resp = provider.classify(s, u)\nexcept LLMError as e:\n    if \"Unexpected response shape\" in str(e):\n        # endpoint is not speaking OpenAI chat schema — switch provider or fix URL\n        raise\n    raise","preventionTips":["Smoke-test a new endpoint with one classify() call before batch runs","Match provider name to server type: ollama for native Ollama, openai-compat only for true /v1/chat/completions servers"],"tags":["llm","openai-compat","response-shape","http"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}