HKUDS/DeepTutor · error · EmbeddingProviderError
Embedding provider remained rate limited after key rotation
Error message
Embedding provider remained rate limited after key rotation (Retry-After: {retry_after:g}s) What it means
The provider returned HTTP 429 and stayed rate limited through 8 retry rounds that already rotated API keys and honored Retry-After windows. This signals a hard, persistent rate/quota condition (typically monthly quota exhaustion or an org-wide limit), not a transient sliding-window throttle.
Source
Thrown at deeptutor/services/embedding/adapters/openai_compatible.py:263
# 滑动窗口 429 是瞬态的:长跑(全库 reindex 数小时)里
# 单次 429 不该报废整跑。最多 8 轮,每轮等窗口滑过
# (Retry-After 优先,无头保守 60s)。月度额度耗尽的
# 429 会连挂 8 轮后仍然 raise,不会无限空转。
if rate_limit_retries < 8:
rate_limit_retries += 1
retry_after = float(response.headers.get("Retry-After", 0))
await asyncio.sleep(max(retry_after, 60))
try:
api_key = self._auth_api_key()
except RuntimeError:
# 池内 key 全在冷却(KeyPool 冷却 60s)。
# 等冷却期过后再取一次;仍取不到才认输。
await asyncio.sleep(65)
api_key = self._auth_api_key()
self._set_auth_header(headers, api_key)
continue
retry_after = float(response.headers.get("Retry-After", 0))
raise EmbeddingProviderError(
"Embedding provider remained rate limited after key rotation"
+ (f" (Retry-After: {retry_after:g}s)" if retry_after else ""),
status=429,
model=model,
url=url,
provider="openai_compat",
)
if response.status_code >= 400:
body_text = response.text
if "encoding_format" not in payload and rejects_absent_encoding_format(
response.status_code, body_text
):
payload["encoding_format"] = "float"
logger.info(
"Gateway requires an explicit `encoding_format`; "
"retrying once with 'float' (%s)",
url,View on GitHub (pinned to 3e82f13042)
Solutions
- Check the provider dashboard: if monthly/quota cap is exhausted, top up or wait for reset — no retry logic will help
- Add more valid API keys to the key pool so rotation has live keys
- Reduce embedding batch size / add spacing between reindex batches
- If it is TPM throttling, lower concurrency or schedule the reindex off-peak
Defensive patterns
Strategy: retry
Validate before calling
null
Type guard
null
Try / catch
try:
resp = await adapter.embed(req)
except EmbeddingProviderError as e:
if e.status == 429 and "remained rate limited" in str(e):
# persistent quota condition: pause the job, notify, do not tight-loop
await schedule_resume_after_quota_window(e)
return None
raise Prevention
- Pool multiple API keys so rotation has live keys
- Chunk large reindex jobs and respect provider TPM limits
- Monitor quota dashboards before multi-hour embedding runs
When it happens
Trigger: 429 responses persisting across 8 retries spaced by max(Retry-After, 60s), with key-pool rotation exhausted — e.g. free-tier monthly quota spent, or all pooled keys simultaneously throttled.
Common situations: Long full-KB reindex jobs burning through token-per-minute limits; all keys in the pool hitting monthly caps; shared org key throttled elsewhere.
Related errors
- 'every' interval must be at least 30 seconds
- Cohere v1 API does not support multimodal `contents`. Use em
- Cohere model '{model_name}' does not support multimodal `con
- Cohere v2 does not support content type '{kind}'
- dashscope SDK not installed. Run `pip install dashscope` (or
AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27).
Data as JSON: /api/errors/e7e0cb6a42da4fda.
Report an issue: GitHub.