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}")