""" LLM adapter interface for pluggable model providers. Implements abstraction layer for LLM integration, supporting multiple providers (OpenAI, Anthropic, local models, etc.). """ from abc import ABC, abstractmethod from typing import Dict, Any from markitect.prompts.execution.models import RunConfig, LLMResponse class LLMAdapter(ABC): """ Abstract base class for LLM providers. Enables pluggable LLM backends without prescribing implementation. Implementations can wrap OpenAI, Anthropic, or other APIs. """ @abstractmethod def execute_prompt( self, prompt: str, config: RunConfig, ) -> LLMResponse: """ Execute a prompt with the LLM. Args: prompt: Compiled prompt text config: Execution configuration Returns: LLMResponse with generated content Raises: Exception: On LLM API errors """ pass @abstractmethod def validate_config(self, config: RunConfig) -> bool: """ Validate that configuration is supported. Args: config: Configuration to validate Returns: True if valid, False otherwise """ pass class MockLLMAdapter(LLMAdapter): """ Mock LLM adapter for testing. Returns deterministic responses without calling external APIs. """ def __init__(self, mock_response: str = "Mock LLM response"): """ Initialize mock adapter. Args: mock_response: Response to return """ self.mock_response = mock_response self.call_count = 0 self.last_prompt = None self.last_config = None def execute_prompt( self, prompt: str, config: RunConfig, ) -> LLMResponse: """ Return mock response. Args: prompt: Prompt (stored for inspection) config: Config (stored for inspection) Returns: Mock LLMResponse """ self.call_count += 1 self.last_prompt = prompt self.last_config = config return LLMResponse( content=self.mock_response, model=config.model_name, usage={ "prompt_tokens": len(prompt.split()), "completion_tokens": len(self.mock_response.split()), "total_tokens": len(prompt.split()) + len(self.mock_response.split()), }, finish_reason="stop", metadata={"mock": True}, ) def validate_config(self, config: RunConfig) -> bool: """ Mock validation always succeeds. Args: config: Configuration Returns: Always True """ return True def reset(self) -> None: """Reset mock state.""" self.call_count = 0 self.last_prompt = None self.last_config = None class ErrorLLMAdapter(LLMAdapter): """ Mock adapter that always raises an error. Useful for testing error handling. """ def __init__(self, error_message: str = "Mock LLM error"): """ Initialize error adapter. Args: error_message: Error message to raise """ self.error_message = error_message def execute_prompt( self, prompt: str, config: RunConfig, ) -> LLMResponse: """ Raise error. Args: prompt: Prompt config: Config Raises: RuntimeError: Always """ raise RuntimeError(self.error_message) def validate_config(self, config: RunConfig) -> bool: """ Validation succeeds. Args: config: Configuration Returns: True """ return True