from __future__ import annotations from abc import ABC, abstractmethod from typing import Any import httpx from app.exceptions import ProviderConfigurationError from app.models import LLMRequest, LLMResponse class LLMProviderClient(ABC): """Base class for provider-specific chat completion clients.""" name: str api_key_env: str | None = None supports_stream: bool = False def __init__(self, api_key: str | None): if self.api_key_env and not api_key: raise ProviderConfigurationError( f"Provider '{self.name}' requires environment variable '{self.api_key_env}'." ) self.api_key = api_key or "" @abstractmethod async def chat( self, request: LLMRequest, client: httpx.AsyncClient ) -> LLMResponse: """Execute a chat completion call.""" @staticmethod def merge_payload(base: dict[str, Any], extra: dict[str, Any] | None) -> dict[str, Any]: """Merge provider payload with optional extra params, ignoring None values.""" merged = {k: v for k, v in base.items() if v is not None} if extra: merged.update({k: v for k, v in extra.items() if v is not None}) return merged def ensure_stream_supported(self, stream_requested: bool) -> None: if stream_requested and not self.supports_stream: raise ProviderConfigurationError( f"Provider '{self.name}' does not support streaming mode." )