""" Base API module for handling different API providers. This module provides a unified interface for interacting with various API providers like Anthropic, OpenAI, Google Gemini and Together AI. """ 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 ) # Check if this is the reasoning model response if self.model == "deepseek-reasoner" and hasattr(response.choices[0].message, "reasoning_content"): # Include both reasoning and answer reasoning = response.choices[0].message.reasoning_content answer = response.choices[0].message.content return f"Reasoning:\n{reasoning}\n\nAnswer:\n{answer}" else: # Regular model response 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}") # Check if this is the reasoning model (qwq-plus) if self.model == "qwq-plus": # For qwq-plus model, we need to use streaming reasoning_content = "" answer_content = "" is_answering = False response = self.client.chat.completions.create( model=self.model, messages=[ {"role": "user", "content": formatted_prompt} ], max_tokens=max_tokens, stream=True # qwq-plus only supports streaming output ) for chunk in response: if not chunk.choices: continue delta = chunk.choices[0].delta # Collect reasoning process if hasattr(delta, 'reasoning_content') and delta.reasoning_content is not None: reasoning_content += delta.reasoning_content # Collect answer content elif hasattr(delta, 'content') and delta.content is not None: answer_content += delta.content is_answering = True # Return combined reasoning and answer return f"Reasoning:\n{reasoning_content}\n\nAnswer:\n{answer_content}" else: # Regular model response (non-streaming) 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}")