"""Abstract LLM adapter interface; model-agnostic for orchestrator and agents.""" from abc import ABC, abstractmethod from typing import Any class LLMAdapter(ABC): """Abstract adapter for LLM completion. Implementations should handle: - openai/ - OpenAI API (GPT-4, etc.) - anthropic/ - Anthropic API (Claude, etc.) - local/ - Local models (Ollama, etc.) """ @abstractmethod def complete( self, messages: list[dict[str, str]], **kwargs: Any, ) -> str: """Return completion text for the given messages. Args: messages: List of message dicts with 'role' and 'content' keys. **kwargs: Provider-specific options (e.g., temperature, max_tokens). Returns: The model's response text. """ ... def complete_structured( self, messages: list[dict[str, str]], schema: dict[str, Any] | None = None, **kwargs: Any, ) -> Any: """Return structured (JSON) output. Default implementation returns None; subclasses may override to use provider-specific JSON modes (e.g., OpenAI's response_format). Args: messages: List of message dicts with 'role' and 'content' keys. schema: Optional JSON schema for response validation. **kwargs: Provider-specific options. Returns: Parsed JSON response or None if not supported/parsing fails. """ return None async def acomplete( self, messages: list[dict[str, str]], **kwargs: Any, ) -> str: """Async completion — default wraps sync ``complete()`` in a thread. Subclasses with native async support (e.g., httpx-based providers) should override this for true non-blocking I/O. Args: messages: List of message dicts with 'role' and 'content' keys. **kwargs: Provider-specific options. Returns: The model's response text. """ import asyncio loop = asyncio.get_running_loop() return await loop.run_in_executor(None, lambda: self.complete(messages, **kwargs)) async def acomplete_structured( self, messages: list[dict[str, str]], schema: dict[str, Any] | None = None, **kwargs: Any, ) -> Any: """Async structured completion — default wraps sync version. Args: messages: List of message dicts with 'role' and 'content' keys. schema: Optional JSON schema for response validation. **kwargs: Provider-specific options. Returns: Parsed JSON response or None. """ import asyncio loop = asyncio.get_running_loop() return await loop.run_in_executor( None, lambda: self.complete_structured(messages, schema=schema, **kwargs) )