quick commit
Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Failing after 6m9s
CI/CD Pipeline / Security Scanning (push) Successful in 26s
CI/CD Pipeline / Tests (3.11) (push) Failing after 5m24s
CI/CD Pipeline / Tests (3.12) (push) Failing after 5m23s
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Deploy to Staging (push) Has been skipped
CI/CD Pipeline / Deploy to Production (push) Has been skipped
CI/CD Pipeline / Notification (push) Successful in 1s

This commit is contained in:
2026-01-17 20:24:43 +01:00
parent 95cc3cdb8f
commit 831eed8dbc
82 changed files with 8860 additions and 167 deletions

View File

@@ -1,9 +1,12 @@
"""Base classes for AI providers."""
import asyncio
import logging
from abc import ABC, abstractmethod
from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field
from enum import Enum
from typing import Literal
from typing import Literal, TypeVar
class ContentCategory(str, Enum):
@@ -20,6 +23,64 @@ class ContentCategory(str, Enum):
MISINFORMATION = "misinformation"
_T = TypeVar("_T")
@dataclass(frozen=True)
class RetryConfig:
"""Retry configuration for AI calls."""
retries: int = 3
base_delay: float = 0.25
max_delay: float = 2.0
def parse_categories(values: list[str]) -> list[ContentCategory]:
"""Parse category values into ContentCategory enums."""
categories: list[ContentCategory] = []
for value in values:
try:
categories.append(ContentCategory(value))
except ValueError:
continue
return categories
async def run_with_retries(
operation: Callable[[], Awaitable[_T]],
*,
config: RetryConfig | None = None,
logger: logging.Logger | None = None,
operation_name: str = "AI call",
) -> _T:
"""Run an async operation with retries and backoff."""
retry_config = config or RetryConfig()
delay = retry_config.base_delay
last_error: Exception | None = None
for attempt in range(1, retry_config.retries + 1):
try:
return await operation()
except Exception as error: # noqa: BLE001 - we re-raise after retries
last_error = error
if attempt >= retry_config.retries:
raise
if logger:
logger.warning(
"%s failed (attempt %s/%s): %s",
operation_name,
attempt,
retry_config.retries,
error,
)
await asyncio.sleep(delay)
delay = min(retry_config.max_delay, delay * 2)
if last_error:
raise last_error
raise RuntimeError("Retry loop exited unexpectedly")
@dataclass
class ModerationResult:
"""Result of AI content moderation."""