- 4 provider adapters: OpenAI (SDK), Anthropic (SDK), Google (google-genai), Mistral (direct HTTP) - Core router with automatic failover + exponential backoff - Flask blueprint with /api/v1/ai/* endpoints - Auth via token-broker verify endpoint - DB models for ai_providers, ai_model_mapping, ai_router_log - /health endpoint (parallel provider check), /usage stats - 21 unit tests (all passing)
45 lines
1.4 KiB
Python
45 lines
1.4 KiB
Python
from abc import ABC, abstractmethod
|
|
from typing import Optional
|
|
|
|
|
|
class AIProvider(ABC):
|
|
@property
|
|
@abstractmethod
|
|
def name(self) -> str:
|
|
"""Provider identifier (openai, anthropic, google, mistral)."""
|
|
|
|
@abstractmethod
|
|
def chat(self, messages: list, model: str, **kwargs) -> dict:
|
|
"""Send a chat completion request. Returns dict with at least:
|
|
{
|
|
"content": str,
|
|
"model": str,
|
|
"provider": self.name,
|
|
"usage": {"prompt_tokens": int, "completion_tokens": int, "total_tokens": int}
|
|
}
|
|
"""
|
|
|
|
@abstractmethod
|
|
def models(self) -> list:
|
|
"""Return list of available models from this provider."""
|
|
|
|
@abstractmethod
|
|
def check_health(self) -> dict:
|
|
"""Check provider connectivity. Returns {"status": "ok"|"error", "details": str}"""
|
|
|
|
def get_api_key(self, db_config: Optional[dict] = None) -> Optional[str]:
|
|
"""Resolve API key: DB override > env var."""
|
|
provider_env_map = {
|
|
"openai": "OPENAI_API_KEY",
|
|
"anthropic": "ANTHROPIC_API_KEY",
|
|
"google": "GOOGLE_API_KEY",
|
|
"mistral": "MISTRAL_API_KEY",
|
|
}
|
|
if db_config and db_config.get("api_key"):
|
|
return db_config["api_key"]
|
|
import os
|
|
env_var = provider_env_map.get(self.name)
|
|
if env_var:
|
|
return os.environ.get(env_var)
|
|
return None
|