from abc import ABC, abstractmethod import logging import requests from openai import OpenAI from typing import Optional, Dict, Any, List from dataclasses import dataclass # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) @dataclass class APIResponse: """Standardized API response structure""" text: str raw_response: Any usage: Dict[str, int] model: str class APIError(Exception): """Custom exception for API-related errors""" def __init__(self, message: str, provider: str, status_code: Optional[int] = None): self.message = message self.provider = provider self.status_code = status_code super().__init__(f"{provider} API Error: {message} (Status: {status_code})") class BaseAPI(ABC): """Abstract base class for API interactions""" def __init__(self, api_key: str, model: str): self.api_key = api_key self.model = model self.provider_name = "base" # Override in subclasses @abstractmethod def generate_response(self, prompt: str, max_tokens: int = 1024, prompt_format: Optional[str] = None) -> str: """Generate a response using the API""" pass def _format_prompt(self, question: str, prompt_format: Optional[str] = None) -> str: """Format the prompt using custom format if provided""" if prompt_format: return prompt_format.format(question=question) # Default format if none provided return f"""Please answer the question using the following format, with each step clearly marked: Question: {question} Let's solve this step by step: [First step of reasoning] [Second step of reasoning] [Third step of reasoning] ... (add more steps as needed) [Final answer] Note: 1. Each step must be wrapped in XML tags 2. Each step must have a number attribute 3. The final answer must be wrapped in tags """ def _handle_error(self, error: Exception, context: str = "") -> None: """Standardized error handling""" error_msg = f"{self.provider_name} API error in {context}: {str(error)}" logger.error(error_msg) raise APIError(str(error), self.provider_name) class AnthropicAPI(BaseAPI): """Class to handle interactions with the Anthropic API""" def __init__(self, api_key: str, model: str = "claude-3-opus-20240229"): super().__init__(api_key, model) self.provider_name = "Anthropic" self.base_url = "https://api.anthropic.com/v1/messages" self.headers = { "x-api-key": api_key, "anthropic-version": "2023-06-01", "content-type": "application/json" } def generate_response(self, prompt: str, max_tokens: int = 1024, prompt_format: Optional[str] = None) -> str: """Generate a response using the Anthropic API""" try: formatted_prompt = self._format_prompt(prompt, prompt_format) data = { "model": self.model, "messages": [{"role": "user", "content": formatted_prompt}], "max_tokens": max_tokens } logger.info(f"Sending request to Anthropic API with model {self.model}") response = requests.post(self.base_url, headers=self.headers, json=data) response.raise_for_status() response_data = response.json() return response_data["content"][0]["text"] except requests.exceptions.RequestException as e: self._handle_error(e, "request") except (KeyError, IndexError) as e: self._handle_error(e, "response parsing") except Exception as e: self._handle_error(e, "unexpected") class OpenAIAPI(BaseAPI): """Class to handle interactions with the OpenAI API""" def __init__(self, api_key: str, model: str = "gpt-4-turbo-preview"): super().__init__(api_key, model) self.provider_name = "OpenAI" try: self.client = OpenAI(api_key=api_key) except Exception as e: self._handle_error(e, "initialization") def generate_response(self, prompt: str, max_tokens: int = 1024, prompt_format: Optional[str] = None) -> str: """Generate a response using the OpenAI API""" try: formatted_prompt = self._format_prompt(prompt, prompt_format) logger.info(f"Sending request to OpenAI API with model {self.model}") response = self.client.chat.completions.create( model=self.model, messages=[{"role": "user", "content": formatted_prompt}], max_tokens=max_tokens ) return response.choices[0].message.content except Exception as e: self._handle_error(e, "request or response processing") class GeminiAPI(BaseAPI): """Class to handle interactions with the Google Gemini API""" def __init__(self, api_key: str, model: str = "gemini-2.0-flash"): super().__init__(api_key, model) self.provider_name = "Gemini" try: from google import genai self.client = genai.Client(api_key=api_key) except Exception as e: self._handle_error(e, "initialization") def generate_response(self, prompt: str, max_tokens: int = 1024, prompt_format: Optional[str] = None) -> str: """Generate a response using the Gemini API""" try: from google.genai import types formatted_prompt = self._format_prompt(prompt, prompt_format) logger.info(f"Sending request to Gemini API with model {self.model}") response = self.client.models.generate_content( model=self.model, contents=[formatted_prompt], config=types.GenerateContentConfig( max_output_tokens=max_tokens, temperature=0.7 ) ) if not response.text: raise APIError("Empty response from Gemini API", self.provider_name) return response.text except Exception as e: self._handle_error(e, "request or response processing") class TogetherAPI(BaseAPI): """Class to handle interactions with the Together AI API""" def __init__(self, api_key: str, model: str = "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo"): super().__init__(api_key, model) self.provider_name = "Together" try: from together import Together self.client = Together(api_key=api_key) except Exception as e: self._handle_error(e, "initialization") def generate_response(self, prompt: str, max_tokens: int = 1024, prompt_format: Optional[str] = None) -> str: """Generate a response using the Together AI API""" try: formatted_prompt = self._format_prompt(prompt, prompt_format) logger.info(f"Sending request to Together AI API with model {self.model}") response = self.client.chat.completions.create( model=self.model, messages=[{"role": "user", "content": formatted_prompt}], max_tokens=max_tokens ) # Robust response extraction if hasattr(response, 'choices') and response.choices: return response.choices[0].message.content elif hasattr(response, 'text'): return response.text else: # If response doesn't match expected structures raise APIError("Unexpected response format from Together AI", self.provider_name) except Exception as e: self._handle_error(e, "request or response processing") class DeepSeekAPI(BaseAPI): """Class to handle interactions with the DeepSeek API""" def __init__(self, api_key: str, model: str = "deepseek-chat"): super().__init__(api_key, model) self.provider_name = "DeepSeek" try: self.client = OpenAI(api_key=api_key, base_url="https://api.deepseek.com") except Exception as e: self._handle_error(e, "initialization") def generate_response(self, prompt: str, max_tokens: int = 1024, prompt_format: Optional[str] = None) -> str: """Generate a response using the DeepSeek API""" try: formatted_prompt = self._format_prompt(prompt, prompt_format) logger.info(f"Sending request to DeepSeek API with model {self.model}") response = self.client.chat.completions.create( model=self.model, messages=[ {"role": "user", "content": formatted_prompt} ], max_tokens=max_tokens ) return response.choices[0].message.content except Exception as e: self._handle_error(e, "request or response processing") class QwenAPI(BaseAPI): """Class to handle interactions with the Qwen API""" def __init__(self, api_key: str, model: str = "qwen-plus"): super().__init__(api_key, model) self.provider_name = "Qwen" try: self.client = OpenAI( api_key=api_key, base_url="https://dashscope-intl.aliyuncs.com/compatible-mode/v1" ) except Exception as e: self._handle_error(e, "initialization") def generate_response(self, prompt: str, max_tokens: int = 1024, prompt_format: Optional[str] = None) -> str: """Generate a response using the Qwen API""" try: formatted_prompt = self._format_prompt(prompt, prompt_format) logger.info(f"Sending request to Qwen API with model {self.model}") response = self.client.chat.completions.create( model=self.model, messages=[ {"role": "user", "content": formatted_prompt} ], max_tokens=max_tokens ) return response.choices[0].message.content except Exception as e: self._handle_error(e, "request or response processing") class GrokAPI(BaseAPI): """Class to handle interactions with the Grok API""" def __init__(self, api_key: str, model: str = "grok-2-latest"): super().__init__(api_key, model) self.provider_name = "Grok" try: self.client = OpenAI( api_key=api_key, base_url="https://api.x.ai/v1" ) except Exception as e: self._handle_error(e, "initialization") def generate_response(self, prompt: str, max_tokens: int = 1024, prompt_format: Optional[str] = None) -> str: """Generate a response using the Grok API""" try: formatted_prompt = self._format_prompt(prompt, prompt_format) logger.info(f"Sending request to Grok API with model {self.model}") response = self.client.chat.completions.create( model=self.model, messages=[ {"role": "user", "content": formatted_prompt} ], max_tokens=max_tokens ) return response.choices[0].message.content except Exception as e: self._handle_error(e, "request or response processing") class APIFactory: """Factory class for creating API instances""" _providers = { "anthropic": { "class": AnthropicAPI, "default_model": "claude-3-7-sonnet-20250219" }, "openai": { "class": OpenAIAPI, "default_model": "gpt-4-turbo-preview" }, "google": { "class": GeminiAPI, "default_model": "gemini-2.0-flash" }, "together": { "class": TogetherAPI, "default_model": "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo" }, "deepseek": { "class": DeepSeekAPI, "default_model": "deepseek-chat" }, "qwen": { "class": QwenAPI, "default_model": "qwen-plus" }, "grok": { "class": GrokAPI, "default_model": "grok-2-latest" } } @classmethod def supported_providers(cls) -> List[str]: """Get list of supported providers""" return list(cls._providers.keys()) @classmethod def create_api(cls, provider: str, api_key: str, model: Optional[str] = None) -> BaseAPI: """Factory method to create appropriate API instance""" provider = provider.lower() if provider not in cls._providers: raise ValueError(f"Unsupported provider: {provider}. " f"Supported providers are: {', '.join(cls.supported_providers())}") provider_info = cls._providers[provider] api_class = provider_info["class"] model = model or provider_info["default_model"] logger.info(f"Creating API instance for provider: {provider}, model: {model}") return api_class(api_key=api_key, model=model) def create_api(provider: str, api_key: str, model: Optional[str] = None) -> BaseAPI: """Convenience function to create API instance""" return APIFactory.create_api(provider, api_key, model) # Example usage: if __name__ == "__main__": # Example with Anthropic anthropic_api = create_api("anthropic", "your-api-key") # Example with OpenAI openai_api = create_api("openai", "your-api-key", "gpt-4") # Example with Gemini gemini_api = create_api("gemini", "your-api-key", "gemini-2.0-flash") # Example with Together AI together_api = create_api("together", "your-api-key") # Get supported providers providers = APIFactory.supported_providers() print(f"Supported providers: {providers}")