HKUDS/DeepTutor · critical · ValueError
KeyPool requires at least one non-empty key
Error message
KeyPool requires at least one non-empty key
What it means
KeyPool is the key-rotation helper that cools a provider key after two HTTP 429 responses. Its constructor strips whitespace from every supplied key and refuses to build a pool from an empty result set, because rotation logic has nothing to rotate without at least one usable key. This is a fail-fast configuration guard, not a runtime failure.
Source
Thrown at deeptutor/services/keypool.py:15
"""Thread-safe round-robin API key rotation with rate-limit cooldowns."""
from __future__ import annotations
from threading import Lock
from time import monotonic
class KeyPool:
"""Rotate keys and cool a key after two HTTP 429 responses."""
def __init__(self, keys: list[str], cooldown_s: int = 60) -> None:
self._keys = [str(key).strip() for key in keys if str(key).strip()]
if not self._keys:
raise ValueError("KeyPool requires at least one non-empty key")
self._cooldown_s = max(0, cooldown_s)
self._next_index = 0
self._strikes = {key: 0 for key in self._keys}
self._cooldown_until = {key: 0.0 for key in self._keys}
self._lock = Lock()
def next(self) -> str:
"""Return the next key, preferring one that is not cooling down.
When every key is cooling we still return the one that recovers
soonest instead of refusing to serve. The pool spreads load across
keys; it is not a circuit breaker. Raising here would convert a
retryable provider 429 into an unretryable application error for the
whole cooldown window — and for the common single-key setup that means
every LLM and embedding call failing for a full minute, which is
strictly worse than letting the provider's own 429 surface and be
retried. The caller (``_KeyRotatingCompletions.create``) already marks
the strike and re-raises the real 429 on its second attempt.View on GitHub (pinned to 3e82f13042)
Solutions
- Check the input before construction: log len of the raw keys list and each key's stripped length.
- Fix the source of the key list (env var name, settings file, secret manager path).
- Filter blanks explicitly and raise a clearer domain error if nothing remains: keys = [k.strip() for k in raw if k.strip()].
- Add a startup settings validation step so the app fails with a readable message before KeyPool is built.
Example fix
// before
pool = KeyPool(os.getenv("PROVIDER_KEYS", "").split(","))
# after
raw = [k for k in os.getenv("PROVIDER_KEYS", "").split(",") if k.strip()]
if not raw:
raise RuntimeError("PROVIDER_KEYS env var is empty or unset")
pool = KeyPool(raw) Defensive patterns
Strategy: validation
Validate before calling
raw = os.getenv("PROVIDER_KEYS", "").split(",")
keys = [k.strip() for k in raw if k.strip()]
if not keys:
raise RuntimeError("PROVIDER_KEYS must contain at least one non-empty key")
pool = KeyPool(keys) Type guard
def is_valid_key_list(keys: object) -> bool:
return isinstance(keys, list) and bool(keys) and all(
isinstance(k, str) and k.strip() for k in keys
) Try / catch
try:
pool = KeyPool(keys)
except ValueError as e:
if "non-empty key" in str(e):
raise SystemExit("Configure PROVIDER_KEYS before starting") from e
raise Prevention
- Validate key lists at startup, not lazily on first request.
- Log the count (never the values) of loaded keys during boot.
- Fail fast in CI with a config smoke test that constructs KeyPool.
When it happens
Trigger: Instantiating KeyPool(keys=[]) , with a list of empty/whitespace strings like [' ', ''], or with a list produced by splitting an unset env var (e.g. (os.getenv('KEYS') or '').split(',')) where every element strips to ''.
Common situations: Env var name typo so the keys variable is empty; comma-separated key string with trailing commas producing blank entries; a secrets file loaded but parsed into empty strings in CI/test environments; passing keys before they were read from settings.
Related errors
- Model is required for cloud LLM provider
- Model not configured for agent {self.agent_name}. Please act
- Render failed because local LaTeX is missing. Please avoid T
- Document not found
- Unsupported import source: {value!r}
AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27).
Data as JSON: /api/errors/f133f1c05e33f823.
Report an issue: GitHub.