HKUDS/DeepTutor · error · LLMAPIError

Anthropic API error: unexpected response payload

Error message

Anthropic API error: unexpected response payload

What it means

After a 200 response, _anthropic_complete walks result['content'][0]['text'] through a chain of isinstance/Mapping casts; if any level is missing or not a string, LLMAPIError 'unexpected response payload' is raised with the (successful) status code. It means the endpoint returned 200 JSON that does not match Anthropic's Messages schema.

Source

Thrown at deeptutor/services/llm/cloud_provider.py:721

        async with session.post(url, headers=headers, json=data) as response:
            if response.status != 200:
                error_text = await response.text()
                raise LLMAPIError(
                    f"Anthropic API error: {error_text}",
                    status_code=response.status,
                    provider="anthropic",
                )

            result = cast(dict[str, object], await response.json())
            content_items = result.get("content")
            if isinstance(content_items, list) and content_items:
                content_list = cast(list[object], content_items)
                first_item = content_list[0]
                if isinstance(first_item, Mapping):
                    text = cast(Mapping[str, object], first_item).get("text")
                    if isinstance(text, str):
                        return text
            raise LLMAPIError(
                "Anthropic API error: unexpected response payload",
                status_code=response.status,
                provider="anthropic",
            )


async def _anthropic_stream(
    model: str,
    prompt: str,
    system_prompt: str,
    api_key: str | None,
    base_url: str | None,
    messages: list[dict[str, object]] | None = None,
    max_tokens: int | None = None,
    temperature: float | None = None,
) -> AsyncGenerator[str, None]:
    """Anthropic (Claude) API streaming."""
    import json

View on GitHub (pinned to 3e82f13042)

Solutions

  1. Log the full response JSON once to compare against the documented Messages schema.
  2. Fix mock/proxy fixtures to include content: [{type: 'text', text: '...'}].
  3. If hitting the real API, confirm base_url is not pointed at a non-Anthropic endpoint.
  4. Report/patch if a provider revision changed the payload shape.

Example fix

// before
# mock server
{"content": [{"type": "text", "body": "hi"}]}

# after
{"content": [{"type": "text", "text": "hi"}]}
Defensive patterns

Strategy: type-guard

Validate before calling

# Not applicable (server-side payload); pin conforming endpoints only

Type guard

def is_anthropic_messages_payload(result: dict) -> bool:
    content = result.get("content")
    return (
        isinstance(content, list)
        and bool(content)
        and isinstance(content[0], dict)
        and isinstance(content[0].get("text"), str)
    )

Try / catch

try:
    out = await complete(prompt=p, binding="anthropic", model=m, api_key=k)
except LLMAPIError as e:
    if "unexpected response payload" in str(e):
        log.error("nonstandard Anthropic endpoint; raw body needed")
    raise

Prevention

When it happens

Trigger: A mock, proxy, or Anthropic-compatible gateway returning {"content": []} or content items without a text field; empty completion where Anthropic returns an empty content array (possible with certain stop/tool configurations); schema drift after an API revision.

Common situations: Testing against stub servers with hand-rolled response fixtures; middleboxes rewriting the body; very rare real-API schema changes or empty model outputs.

Related errors


AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27). Data as JSON: /api/errors/941b6912440e5614. Report an issue: GitHub.