MemPalace/mempalace · error · LLMError

Unexpected response shape: {e}

Error message

Unexpected response shape: {e}

What it means

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.

Source

Thrown at mempalace/llm_client.py:358

    ) -> LLMResponse:
        body: dict = {
            "model": self.model,
            "messages": [
                {"role": "system", "content": system},
                {"role": "user", "content": user},
            ],
            "temperature": 0.1,
        }
        if json_mode:
            body["response_format"] = {"type": "json_object"}
        headers = {}
        if self.api_key:
            headers["Authorization"] = f"Bearer {self.api_key}"
        data = _http_post_json(self._resolve_url(), body, headers=headers, timeout=self.timeout)
        try:
            text = data["choices"][0]["message"]["content"]
        except (KeyError, IndexError, TypeError) as e:
            raise LLMError(f"Unexpected response shape: {e}") from e
        if not text:
            raise LLMError(f"Empty response from {self.name} (model={self.model})")
        return LLMResponse(text=text, model=self.model, provider=self.name, raw=data)


# ==================== ANTHROPIC ====================


class AnthropicProvider(LLMProvider):
    name = "anthropic"
    DEFAULT_ENDPOINT = "https://api.anthropic.com"
    API_VERSION = "2023-06-01"

    def __init__(
        self,
        model: str,
        api_key: Optional[str] = None,
        endpoint: Optional[str] = None,

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Inspect the raw body (it is in e.__cause__ context or reproduce with curl) to see the actual schema
  2. For Ollama, either use the ollama provider or its OpenAI-compatible /v1 route
  3. Ensure the URL resolves to a true /v1/chat/completions endpoint
  4. Update or pin a server version whose response format matches OpenAI's

Example fix

# before
provider = build_provider("openai-compat", model="llama3", endpoint="http://localhost:11434")
# server returns native Ollama schema {"message": ...} -> Unexpected response shape: 'choices'

# after
provider = build_provider("ollama", model="llama3", endpoint="http://localhost:11434")
Defensive patterns

Strategy: validation

Validate before calling

import json, urllib.request

def looks_like_openai_chat(url, body, timeout=10):
    req = urllib.request.Request(url, data=json.dumps(body).encode(), headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=timeout) as r:
        data = json.loads(r.read())
    return isinstance(data.get("choices"), list) and data["choices"]

Try / catch

from mempalace.llm_client import LLMError

try:
    resp = provider.classify(s, u)
except LLMError as e:
    if "Unexpected response shape" in str(e):
        # endpoint is not speaking OpenAI chat schema — switch provider or fix URL
        raise
    raise

Prevention

When it happens

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

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

Related errors


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