unclecode/crawl4ai · error · HTTPException

str(e)

Error message

str(e)

What it means

HTTP 400 raised in process_qa (deploy/docker/api.py:183) when LLMProviderNotAllowed escapes — the requested LLM provider string is not on the server's allow-list (provider allowlisting exists because base_url/api_token are server-derived). str(e) carries the specific disallowed-provider reason. This is a client configuration error: change the request, not the server.

Source

Thrown at deploy/docker/api.py:183

        # Provider by name only; base_url/api_token are server-derived. A
        # request-supplied base_url is ignored (it was the key-exfil vector).
        from llm_broker import resolve_llm
        llm = resolve_llm(config, provider)
        response = perform_completion_with_backoff(
            provider=llm["provider"],
            prompt_with_variables=prompt,
            api_token=llm["api_token"],
            temperature=temperature or llm["temperature"],
            base_url=llm["base_url"],
            base_delay=config["llm"].get("backoff_base_delay", 2),
            max_attempts=config["llm"].get("backoff_max_attempts", 3),
            exponential_factor=config["llm"].get("backoff_exponential_factor", 2)
        )

        return response.choices[0].message.content
    except LLMProviderNotAllowed as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error(f"QA processing error: {str(e)}", exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=str(e)
        )
    finally:
        if crawler:
            await release_crawler(crawler)

async def process_llm_extraction(
    redis: aioredis.Redis,
    config: dict,
    task_id: str,
    url: str,
    instruction: str,
    schema: Optional[str] = None,
    cache: str = "0",

View on GitHub (pinned to 7e80152142)

Solutions

  1. Read the 400 detail — it names the provider not allowed and typically the allowed set.
  2. Send an allow-listed provider name exactly as configured (case-sensitive).
  3. If the provider should be available, add it to the server's LLM config allow-list and restart.
  4. Pre-validate provider names against a fetched/configured list before submitting jobs.

Example fix

# before
body = {"provider": "GPT4", "query": q, "url": u}  # 400

# after
body = {"provider": "openai/gpt-4o-mini", "query": q, "url": u}  # exact allow-listed name
r = requests.post(f"{base}/q", json=body)
r.raise_for_status()
Defensive patterns

Strategy: validation

Validate before calling

ALLOWED_PROVIDERS = {"openai/gpt-4o-mini", "gemini/gemini-1.5-pro", "deepseek/deepseek-chat"}

def provider_allowed(provider: str) -> bool:
    return provider in ALLOWED_PROVIDERS  # keep in sync with server config

Type guard

def is_allowed_provider(provider: str | None) -> bool:
    return isinstance(provider, str) and provider in ALLOWED_PROVIDERS

Try / catch

r = await client.post("/q", json=body)
if r.status_code == 400:
    detail = r.json().get("detail", "")
    if "not allowed" in detail.lower():
        body["provider"] = DEFAULT_PROVIDER  # fall back to known-good
        r = await client.post("/q", json=body)

Prevention

When it happens

Trigger: POST /qa with provider="some-unknown-provider" or a provider name not present in the server's llm config allow-list; case mismatch ("OpenAI" vs "openai"); config file updated server-side but client still sending old provider names.

Common situations: Deploying the Docker server with a restricted LLM config and clients assuming all upstream providers are available; version drift between client examples and server config schema.

Related errors


AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14). Data as JSON: /api/errors/8902a2de6feeaee4. Report an issue: GitHub.