feat: revue de code, doc CODE_REVIEW, forfaits 2026, traduction LLM, providers avec modèle
Made-with: Cursor
This commit is contained in:
208
services/providers/config.py
Normal file
208
services/providers/config.py
Normal file
@@ -0,0 +1,208 @@
|
||||
"""
|
||||
Provider Configuration - Environment-based settings for translation providers.
|
||||
|
||||
Loads API keys, URLs, and enable/disable flags from environment variables.
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
def _ensure_dotenv_loaded() -> None:
|
||||
"""Load .env file if not already loaded."""
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
_ensure_dotenv_loaded()
|
||||
|
||||
|
||||
class ProviderSettings(BaseModel):
|
||||
"""Settings for a single translation provider."""
|
||||
|
||||
enabled: bool = False
|
||||
api_key: Optional[str] = None
|
||||
base_url: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
|
||||
|
||||
class ProvidersConfig:
|
||||
"""
|
||||
Configuration for all translation providers.
|
||||
|
||||
Loads settings from environment variables with sensible defaults.
|
||||
"""
|
||||
|
||||
# Google Translate (no API key required via deep_translator)
|
||||
GOOGLE_ENABLED: bool = (
|
||||
os.getenv("GOOGLE_TRANSLATE_ENABLED", "true").lower() == "true"
|
||||
)
|
||||
GOOGLE_TRANSLATE_TIMEOUT: int = int(os.getenv("GOOGLE_TRANSLATE_TIMEOUT", "30"))
|
||||
GOOGLE_TRANSLATE_MAX_RETRIES: int = int(
|
||||
os.getenv("GOOGLE_TRANSLATE_MAX_RETRIES", "3")
|
||||
)
|
||||
GOOGLE_TRANSLATE_RETRY_DELAY: float = float(
|
||||
os.getenv("GOOGLE_TRANSLATE_RETRY_DELAY", "1.0")
|
||||
)
|
||||
|
||||
# DeepL
|
||||
DEEPL_ENABLED: bool = os.getenv("DEEPL_ENABLED", "false").lower() == "true"
|
||||
DEEPL_API_KEY: str = os.getenv("DEEPL_API_KEY", "")
|
||||
DEEPL_TIMEOUT: int = int(os.getenv("DEEPL_TIMEOUT", "30"))
|
||||
DEEPL_MAX_RETRIES: int = int(os.getenv("DEEPL_MAX_RETRIES", "3"))
|
||||
DEEPL_RETRY_DELAY: float = float(os.getenv("DEEPL_RETRY_DELAY", "1.0"))
|
||||
|
||||
# OpenAI
|
||||
OPENAI_ENABLED: bool = os.getenv("OPENAI_ENABLED", "false").lower() == "true"
|
||||
OPENAI_API_KEY: str = os.getenv("OPENAI_API_KEY", "")
|
||||
OPENAI_MODEL: str = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
|
||||
OPENAI_TIMEOUT: int = int(os.getenv("OPENAI_TIMEOUT", "60"))
|
||||
OPENAI_MAX_RETRIES: int = int(os.getenv("OPENAI_MAX_RETRIES", "3"))
|
||||
OPENAI_RETRY_DELAY: float = float(os.getenv("OPENAI_RETRY_DELAY", "1.0"))
|
||||
OPENAI_BASE_URL: str = os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1")
|
||||
OPENAI_HEALTH_CHECK_TIMEOUT: int = int(
|
||||
os.getenv("OPENAI_HEALTH_CHECK_TIMEOUT", "5")
|
||||
)
|
||||
|
||||
# Ollama (local LLM) - default model is config-only, no hardcode in provider
|
||||
_DEFAULT_OLLAMA_MODEL: str = "llama3"
|
||||
OLLAMA_ENABLED: bool = os.getenv("OLLAMA_ENABLED", "false").lower() == "true"
|
||||
OLLAMA_BASE_URL: str = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
|
||||
OLLAMA_MODEL: str = os.getenv("OLLAMA_MODEL", _DEFAULT_OLLAMA_MODEL)
|
||||
OLLAMA_VISION_MODEL: str = os.getenv("OLLAMA_VISION_MODEL", "llava")
|
||||
OLLAMA_TIMEOUT: int = int(os.getenv("OLLAMA_TIMEOUT", "120"))
|
||||
OLLAMA_MAX_RETRIES: int = int(os.getenv("OLLAMA_MAX_RETRIES", "2"))
|
||||
OLLAMA_RETRY_DELAY: float = float(os.getenv("OLLAMA_RETRY_DELAY", "2.0"))
|
||||
|
||||
# OpenRouter (multi-model API)
|
||||
OPENROUTER_ENABLED: bool = (
|
||||
os.getenv("OPENROUTER_ENABLED", "false").lower() == "true"
|
||||
)
|
||||
OPENROUTER_API_KEY: str = os.getenv("OPENROUTER_API_KEY", "")
|
||||
OPENROUTER_MODEL: str = os.getenv("OPENROUTER_MODEL", "deepseek/deepseek-chat")
|
||||
|
||||
# Fallback chain configuration
|
||||
# General fallback chain (backward compatibility)
|
||||
FALLBACK_CHAIN: List[str] = [
|
||||
name.strip()
|
||||
for name in os.getenv(
|
||||
"PROVIDER_FALLBACK_CHAIN", "google,deepl,openai,ollama,openrouter"
|
||||
).split(",")
|
||||
if name.strip()
|
||||
]
|
||||
|
||||
# Mode-specific fallback chains
|
||||
# Classic mode: Google Translate -> DeepL
|
||||
FALLBACK_CHAIN_CLASSIC: List[str] = [
|
||||
name.strip()
|
||||
for name in os.getenv("FALLBACK_CHAIN_CLASSIC", "google,deepl").split(",")
|
||||
if name.strip()
|
||||
]
|
||||
|
||||
# LLM mode: Ollama (local) -> OpenAI (cloud)
|
||||
FALLBACK_CHAIN_LLM: List[str] = [
|
||||
name.strip()
|
||||
for name in os.getenv("FALLBACK_CHAIN_LLM", "ollama,openai").split(",")
|
||||
if name.strip()
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_fallback_chain(cls, mode: str = "auto") -> List[str]:
|
||||
"""
|
||||
Get the fallback chain for a specific mode.
|
||||
|
||||
Args:
|
||||
mode: "classic" for Classic providers, "llm" for LLM providers,
|
||||
"auto" or any other value for general fallback chain
|
||||
|
||||
Returns:
|
||||
List of provider names in fallback order
|
||||
"""
|
||||
mode = mode.lower()
|
||||
if mode == "classic":
|
||||
return cls.FALLBACK_CHAIN_CLASSIC
|
||||
elif mode == "llm":
|
||||
return cls.FALLBACK_CHAIN_LLM
|
||||
else:
|
||||
return cls.FALLBACK_CHAIN
|
||||
|
||||
@classmethod
|
||||
def get_provider_settings(cls, provider_name: str) -> ProviderSettings:
|
||||
"""
|
||||
Get settings for a specific provider.
|
||||
|
||||
Args:
|
||||
provider_name: Name of the provider (e.g., "google", "deepl")
|
||||
|
||||
Returns:
|
||||
ProviderSettings for the requested provider
|
||||
"""
|
||||
settings_map = {
|
||||
"google": ProviderSettings(
|
||||
enabled=cls.GOOGLE_ENABLED, api_key=None, base_url=None, model=None
|
||||
),
|
||||
"deepl": ProviderSettings(
|
||||
enabled=cls.DEEPL_ENABLED,
|
||||
api_key=cls.DEEPL_API_KEY if cls.DEEPL_API_KEY else None,
|
||||
base_url=None,
|
||||
model=None,
|
||||
),
|
||||
"openai": ProviderSettings(
|
||||
enabled=cls.OPENAI_ENABLED,
|
||||
api_key=cls.OPENAI_API_KEY if cls.OPENAI_API_KEY else None,
|
||||
base_url=cls.OPENAI_BASE_URL or None,
|
||||
model=cls.OPENAI_MODEL,
|
||||
),
|
||||
"ollama": ProviderSettings(
|
||||
enabled=cls.OLLAMA_ENABLED,
|
||||
api_key=None,
|
||||
base_url=cls.OLLAMA_BASE_URL,
|
||||
model=cls.OLLAMA_MODEL,
|
||||
),
|
||||
"openrouter": ProviderSettings(
|
||||
enabled=cls.OPENROUTER_ENABLED,
|
||||
api_key=cls.OPENROUTER_API_KEY if cls.OPENROUTER_API_KEY else None,
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
model=cls.OPENROUTER_MODEL,
|
||||
),
|
||||
}
|
||||
return settings_map.get(provider_name.lower(), ProviderSettings())
|
||||
|
||||
@classmethod
|
||||
def is_provider_configured(cls, provider_name: str) -> bool:
|
||||
"""
|
||||
Check if a provider is properly configured.
|
||||
|
||||
Args:
|
||||
provider_name: Name of the provider
|
||||
|
||||
Returns:
|
||||
True if the provider is enabled and has required configuration
|
||||
"""
|
||||
settings = cls.get_provider_settings(provider_name)
|
||||
|
||||
if not settings.enabled:
|
||||
return False
|
||||
|
||||
# Providers requiring API keys
|
||||
providers_requiring_key = {"deepl", "openai", "openrouter"}
|
||||
|
||||
if provider_name.lower() in providers_requiring_key:
|
||||
return bool(settings.api_key)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def get_available_providers(cls) -> List[str]:
|
||||
"""
|
||||
Get list of configured and available providers.
|
||||
|
||||
Returns:
|
||||
List of provider names that are ready to use
|
||||
"""
|
||||
return [name for name in cls.FALLBACK_CHAIN if cls.is_provider_configured(name)]
|
||||
|
||||
|
||||
providers_config = ProvidersConfig()
|
||||
Reference in New Issue
Block a user