HKUDS/DeepTutor · error · LLMAPIError

Local LLM error: {error_text}

Error message

Local LLM error: {error_text}

What it means

The local LLM server returned a non-200 HTTP status and the response body is surfaced verbatim in an LLMAPIError with status_code and provider='local'. This is the local backend rejecting the request (bad model name, malformed payload, server overload, etc.).

Source

Thrown at deeptutor/services/llm/local_provider.py:219

    }

    # Add optional parameters
    if kwargs.get("max_tokens"):
        data["max_tokens"] = kwargs["max_tokens"]
    if isinstance(kwargs.get("response_format"), dict):
        data["response_format"] = kwargs["response_format"]

    timeout_value = kwargs.get("timeout", DEFAULT_TIMEOUT)
    timeout_seconds = (
        float(timeout_value) if isinstance(timeout_value, (int, float)) else DEFAULT_TIMEOUT
    )
    timeout = aiohttp.ClientTimeout(total=timeout_seconds)

    async with aiohttp.ClientSession(timeout=timeout) as session:
        async with session.post(url, json=data, headers=headers) as response:
            if response.status != 200:
                error_text = await response.text()
                raise LLMAPIError(
                    f"Local LLM error: {error_text}",
                    status_code=response.status,
                    provider="local",
                )

            result = await response.json()
            content = _extract_message_from_payload(result)
            content = clean_thinking_tags(content)
            if content:
                return content

            logger.warning("Local LLM returned no choices: %s", result)
            return ""


async def stream(
    prompt: str,
    system_prompt: str = "You are a helpful assistant.",

View on GitHub (pinned to 3e82f13042)

Solutions

  1. Read the error_text embedded in the message — it names the exact server-side cause.
  2. If 404/model-not-found, pull the model (ollama pull <model>) or fix the model name.
  3. Verify base_url path: OpenAI-compatible servers usually need /v1 suffix.
  4. Restart or inspect the local server logs if 500/503.
Defensive patterns

Strategy: retry

Validate before calling

import aiohttp

async def server_ok(base_url: str) -> bool:
    try:
        async with aiohttp.ClientSession() as s, s.get(base_url + '/models') as r:
            return r.status == 200
    except aiohttp.ClientError:
        return False

Try / catch

try:
    resp = await provider.complete(prompt)
except LLMAPIError as e:
    if e.status_code == 404:
        fix_model_and_retry()
    elif e.status_code >= 500:
        await asyncio.sleep(2); retry()

Prevention

When it happens

Trigger: complete() posts to {base_url}/chat/completions and the server returns 404 (wrong model or path), 400 (malformed messages), 500 (model crashed/OOM), or 503 (model loading).

Common situations: Requesting a model that isn't pulled/loaded in Ollama; pointing base_url at a server without an OpenAI-compatible /chat/completions route; local server out of memory.

Related errors


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