HKUDS/DeepTutor · error · LLMAPIError

Anthropic stream error: {error_text}

Error message

Anthropic stream error: {error_text}

What it means

The initial status check of _anthropic_stream: the POST for the SSE stream returned non-200, so the body is read and wrapped in LLMAPIError with the status and provider before any lines are iterated. The embedded error_text is Anthropic's own error JSON, so the message pinpoints the server-side reason.

Source

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

    temperature_value = temperature if temperature is not None else 0.7
    data: dict[str, object] = {
        "model": model,
        "system": system_content,
        "messages": msg_list,
        "max_tokens": max_tokens_value,
        "temperature": temperature_value,
        "stream": True,
    }

    timeout = aiohttp.ClientTimeout(total=300)
    connector = _get_aiohttp_connector()
    async with aiohttp.ClientSession(
        timeout=timeout, connector=connector, trust_env=True
    ) as session:
        async with session.post(url, headers=headers, json=data) as response:
            if response.status != 200:
                error_text = await response.text()
                raise LLMAPIError(
                    f"Anthropic stream error: {error_text}",
                    status_code=response.status,
                    provider="anthropic",
                )

            async for line in response.content:
                line_str = line.decode("utf-8").strip()
                if not line_str or not line_str.startswith("data:"):
                    continue

                data_str = line_str[5:].strip()
                if not data_str:
                    continue

                try:
                    chunk_data = cast(dict[str, object], json.loads(data_str))
                    event_type = chunk_data.get("type")
                    if event_type == "content_block_delta":

View on GitHub (pinned to 3e82f13042)

Solutions

  1. Decode e.status_code + error_text to find the exact cause (auth vs model vs limit).
  2. 429/529 → back off and retry, or rotate keys via KeyPool.
  3. 401 → refresh the API key in the profile.
  4. 404 → verify the model identifier exists for the account.

Example fix

// before
async for chunk in stream(prompt=p, binding="anthropic", model="claude-3", api_key=k):
    ...

# after
async for chunk in stream(prompt=p, binding="anthropic", model="claude-sonnet-4", api_key=k):
    ...
Defensive patterns

Strategy: retry

Try / catch

try:
    async for chunk in stream(prompt=p, binding="anthropic", model=m, api_key=k):
        ...
except LLMAPIError as e:
    if e.status_code in (429, 529):
        await asyncio.sleep(30)  # then retry
    elif e.status_code == 401:
        raise RuntimeError("invalid Anthropic key") from e
    else:
        raise

Prevention

When it happens

Trigger: Starting a Claude stream with an invalid key (401), unavailable model (404), rate limit (429), or overloaded service (529); proxy gateways rejecting stream:true.

Common situations: Rate-limited Claude accounts during bursts; model name typos; region-blocked endpoints routed via proxy; long-running apps holding stale keys past rotation.

Related errors


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