HKUDS/DeepTutor · error · LLMConfigError

OpenAI API key is not configured. Set it in Settings > Catal

Error message

OpenAI API key is not configured. Set it in Settings > Catalog, or select a local provider such as Ollama.

What it means

The SDK executor path (sdk_complete/sdk_stream) validates credentials before handing off to the OpenAI python SDK: provider openai + official endpoint (empty base_url or exactly https://api.openai.com/v1) + placeholder key raises LLMConfigError. It mirrors the config-layer check but runs where the SDK client is constructed, catching direct calls that bypass get_llm_config.

Source

Thrown at deeptutor/services/llm/executors.py:35

from .config import get_token_limit_kwargs
from .exceptions import LLMConfigError
from .utils import extract_response_content

logger = logging.getLogger(__name__)


def _validate_openai_sdk_credentials(
    *, provider_name: str | None, api_key: str | None, base_url: str | None
) -> None:
    """Keep placeholder keys from reaching the official OpenAI API."""

    provider = (provider_name or "").lower()
    endpoint = (base_url or "").rstrip("/")
    official_openai = not endpoint or endpoint == "https://api.openai.com/v1"
    placeholder = api_key in {None, "", "no-key", "sk-no-key-required"}
    if provider == "openai" and official_openai and placeholder:
        raise LLMConfigError(
            "OpenAI API key is not configured. Set it in Settings > Catalog, "
            "or select a local provider such as Ollama."
        )


def _is_unsupported_response_format_error(exc: BaseException) -> bool:
    """Detect whether a BadRequestError stems from an unsupported ``response_format``.

    Examples seen in the wild:
    - LM Studio + Gemma: ``"'response_format.type' must be 'json_schema' or 'text'"``
    - DashScope + various models: ``"'response_format.type' specified ... not valid: 'json_object' is not supported by this model"``
    """
    text = str(exc).lower()
    if "response_format" not in text and "response format" not in text:
        return False
    return (
        "json_object" in text
        or "json_schema" in text

View on GitHub (pinned to 3e82f13042)

Solutions

  1. Supply a real OpenAI key when hitting the official endpoint.
  2. For local servers, pass their actual base_url (e.g. http://localhost:11434/v1) so official_openai is False and placeholder keys are allowed.
  3. Gate calls: if provider is openai and endpoint is official, require a real key upfront.
  4. Sync profile resolution so api_key reaches sdk_complete.

Example fix

// before
await sdk_complete(prompt=p, model=m, api_key="sk-no-key-required", base_url=None, provider_name="openai")

# after
await sdk_complete(prompt=p, model=m, api_key=os.environ["OPENAI_API_KEY"], base_url=None, provider_name="openai")
# or for local servers:
await sdk_complete(prompt=p, model=m, api_key="no-key", base_url="http://localhost:11434/v1", provider_name="openai")
Defensive patterns

Strategy: validation

Validate before calling

PLACEHOLDERS = {None, "", "no-key", "sk-no-key-required"}
official = not (base_url or "").rstrip("/") or (base_url or "").rstrip("/") == "https://api.openai.com/v1"
if (provider_name or "").lower() == "openai" and official and api_key in PLACEHOLDERS:
    raise RuntimeError("A real OpenAI key is required for the official endpoint")
await sdk_complete(prompt=p, model=m, api_key=api_key, base_url=base_url, provider_name=provider_name)

Type guard

def needs_real_openai_key(api_key, base_url, provider_name) -> bool:
    endpoint = (base_url or "").rstrip("/")
    official = not endpoint or endpoint == "https://api.openai.com/v1"
    placeholder = (api_key or "") in {"", "no-key", "sk-no-key-required"} or api_key is None
    return (provider_name or "").lower() == "openai" and official and placeholder

Try / catch

try:
    out = await sdk_complete(prompt=p, model=m, api_key=k, base_url=u, provider_name=prov)
except LLMConfigError as e:
    if "OpenAI API key is not configured" in str(e):
        raise RuntimeError("Provide OPENAI_API_KEY or point base_url at your local server") from e
    raise

Prevention

When it happens

Trigger: Calling sdk_complete(api_key=None, base_url=None, provider_name='openai'); passing the literal placeholders 'no-key'/'sk-no-key-required' (common with local-server defaults) while still targeting the official OpenAI endpoint; profile fields not propagated to the SDK wrapper.

Common situations: Reusing local-server call patterns (which use placeholder keys) against the real OpenAI endpoint; SDK wrapper initialized before settings load; key resolution returning None due to a missing profile key.

Understand the failure class

Background: "API key is required" / "API key not found" / "No API key was set": the missing-api-key error family across 16 libraries — this error's family across 16 libraries.

Related errors


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