Initial commit
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
"""Shared retry and cached-failure detection for the live profiling stages."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
def _positive_int(name: str, default: int) -> int:
|
||||
try:
|
||||
return max(1, int(os.getenv(name, default)))
|
||||
except ValueError:
|
||||
return default
|
||||
|
||||
|
||||
def _positive_float(name: str, default: float) -> float:
|
||||
try:
|
||||
return max(0.0, float(os.getenv(name, default)))
|
||||
except ValueError:
|
||||
return default
|
||||
|
||||
|
||||
MAX_REQUEST_ATTEMPTS = _positive_int("PROFILE_MAX_REQUEST_ATTEMPTS", 5)
|
||||
RETRY_BASE_SECONDS = _positive_float("PROFILE_RETRY_BASE_SECONDS", 15.0)
|
||||
RETRY_MAX_SECONDS = _positive_float("PROFILE_RETRY_MAX_SECONDS", 120.0)
|
||||
REQUEST_INTERVAL_SECONDS = _positive_float("PROFILE_REQUEST_INTERVAL_SECONDS", 1.0)
|
||||
REQUEST_TIMEOUT_SECONDS = _positive_float("PROFILE_REQUEST_TIMEOUT_SECONDS", 90.0)
|
||||
STREAM_HEARTBEAT_SECONDS = _positive_float("PROFILE_STREAM_HEARTBEAT_SECONDS", 15.0)
|
||||
|
||||
|
||||
def retry_delay_seconds(attempt: int) -> float:
|
||||
"""Return capped exponential backoff for a one-based failed attempt."""
|
||||
delay = RETRY_BASE_SECONDS
|
||||
for _ in range(max(0, attempt - 1)):
|
||||
if delay >= RETRY_MAX_SECONDS:
|
||||
return RETRY_MAX_SECONDS
|
||||
delay *= 2
|
||||
return min(RETRY_MAX_SECONDS, delay)
|
||||
|
||||
|
||||
def response_is_retryable_failure(response: str) -> bool:
|
||||
"""Identify API-failure and no-credential simulation response sentinels."""
|
||||
normalized = response.lstrip().lower()
|
||||
return normalized.startswith("error: api call failed for ") or (
|
||||
normalized.startswith("this is a simulated response from ")
|
||||
and "because no provider api key was provided" in normalized
|
||||
)
|
||||
|
||||
|
||||
def evaluation_is_retryable_failure(evaluation: dict) -> bool:
|
||||
"""Identify evaluator failures and old zero scores produced from API errors."""
|
||||
score = evaluation.get("score")
|
||||
if score is None or (
|
||||
isinstance(score, str)
|
||||
and score in {"error", "evaluator_error", "simulated"}
|
||||
):
|
||||
return True
|
||||
if isinstance(score, str) and score.upper() not in {
|
||||
"E", "I", "S", "N", "T", "F", "J", "P"
|
||||
}:
|
||||
try:
|
||||
float(score)
|
||||
except ValueError:
|
||||
return True
|
||||
elif not isinstance(score, (int, float)) or isinstance(score, bool):
|
||||
return True
|
||||
details = " ".join(
|
||||
str(evaluation.get(key, "")) for key in ("justification", "raw_response")
|
||||
).lower()
|
||||
return bool(re.search(r"rate limit|tpm limit|api error message", details))
|
||||
Reference in New Issue
Block a user