Provider-agnostic model backend. Subclasses must override: - class constants ``_API_KEY_FIELD``, ``_ENV_VAR``, ``_LOG_SOURCE``, ``_DEFAULT_CONFIG_CLASS`` (and optionally ``_MAX_RATE_LIMIT_RETRIES``, ``_MAX_TRANSIENT_RETRIES``) - ``_request_headers``, ``_post_url``
| 223 | |
| 224 | |
| 225 | class BaseModel: |
| 226 | """Provider-agnostic model backend. |
| 227 | |
| 228 | Subclasses must override: |
| 229 | - class constants ``_API_KEY_FIELD``, ``_ENV_VAR``, ``_LOG_SOURCE``, |
| 230 | ``_DEFAULT_CONFIG_CLASS`` (and optionally ``_MAX_RATE_LIMIT_RETRIES``, |
| 231 | ``_MAX_TRANSIENT_RETRIES``) |
| 232 | - ``_request_headers``, ``_post_url`` |
| 233 | - ``_build_payload``, ``_request_metrics_input`` |
| 234 | - ``_extract_text``, ``_usage_metrics_from_payload`` |
| 235 | |
| 236 | Optionally override ``_rate_limit_backoff`` / ``_transient_backoff`` for |
| 237 | custom retry timing. |
| 238 | """ |
| 239 | |
| 240 | _API_KEY_FIELD: str = "" |
| 241 | _ENV_VAR: str = "" |
| 242 | _LOG_SOURCE: str = "" |
| 243 | _MAX_RATE_LIMIT_RETRIES: int = 5 |
| 244 | _MAX_TRANSIENT_RETRIES: int = 5 |
| 245 | _DEFAULT_CONFIG_CLASS: type = BaseModelConfig |
| 246 | |
| 247 | def __init__(self, *, config_class: type | None = None, **kwargs): |
| 248 | self.config = (config_class or self._DEFAULT_CONFIG_CLASS)(**kwargs) |
| 249 | self._last_request_metrics: dict[str, int] = {k: 0 for k in _REQUEST_METRIC_KEYS} |
| 250 | self._last_usage_metrics: dict[str, int] = {k: 0 for k in _USAGE_METRIC_KEYS} |
| 251 | self._cumulative_request_metrics: dict[str, int] = dict(self._last_request_metrics) |
| 252 | self._cumulative_usage_metrics: dict[str, int] = dict(self._last_usage_metrics) |
| 253 | |
| 254 | if self._API_KEY_FIELD: |
| 255 | if not getattr(self.config, self._API_KEY_FIELD, ""): |
| 256 | setattr(self.config, self._API_KEY_FIELD, os.environ.get(self._ENV_VAR, "")) |
| 257 | if not getattr(self.config, self._API_KEY_FIELD, ""): |
| 258 | raise RuntimeError(f"Missing {self._ENV_VAR}.") |
| 259 | |
| 260 | # ---- subclass extension points ------------------------------------------------ |
| 261 | |
| 262 | def _request_headers(self) -> dict[str, str]: |
| 263 | raise NotImplementedError |
| 264 | |
| 265 | def _post_url(self) -> str: |
| 266 | raise NotImplementedError |
| 267 | |
| 268 | def _build_payload(self, messages: list[dict[str, Any]]) -> dict[str, Any]: |
| 269 | raise NotImplementedError |
| 270 | |
| 271 | def _build_text_payload(self, messages: list[dict[str, Any]]) -> dict[str, Any]: |
| 272 | return self._build_payload(messages) |
| 273 | |
| 274 | def _request_metrics_input(self, payload: dict[str, Any]) -> list[dict[str, Any]]: |
| 275 | raise NotImplementedError |
| 276 | |
| 277 | def _extract_text(self, payload: dict[str, Any]) -> str: |
| 278 | raise NotImplementedError |
| 279 | |
| 280 | def _usage_metrics_from_payload(self, payload: dict[str, Any]) -> dict[str, int]: |
| 281 | raise NotImplementedError |
| 282 |
nothing calls this directly
no outgoing calls
no test coverage detected