Files
Priceflow/app/services/openrouter_service.py
T
Claude d6c39252f2 feat: Rebranding PriceFlow + OpenRouter + i18n FR + Docker
Modifications majeures:

1. Rebranding: Pricecious → PriceFlow
   - Mise à jour du titre, logo et noms de projet

2. Intégration OpenRouter
   - Ajout comme provider IA avec filtres (chat, vision, code, reasoning, free)
   - Récupération dynamique des modèles et coûts
   - Service et router dédiés (/api/openrouter/models)

3. Internationalisation (i18n)
   - Installation i18next + react-i18next
   - Application entièrement traduite en français
   - Fichier de traduction: fr.json

4. Configuration Docker
   - docker-compose.yml avec réseau nginx_default (external)
   - PostgreSQL sur port 5488:5432
   - Browserless sur port 3012:3000
   - init.sql pour initialisation de la base de données
   - .env.example avec variables documentées

5. Favicon
   - Documentation ajoutée dans index.html pour faciliter le remplacement
2025-11-22 09:22:39 +00:00

161 lines
5.3 KiB
Python

"""
Service pour interagir avec l'API OpenRouter.
Récupère la liste des modèles disponibles avec leurs coûts et catégories.
"""
import logging
from typing import Any
import requests
logger = logging.getLogger(__name__)
OPENROUTER_API_URL = "https://openrouter.ai/api/v1"
class OpenRouterService:
@staticmethod
def get_models(api_key: str | None = None) -> list[dict[str, Any]]:
"""
Récupère la liste des modèles disponibles sur OpenRouter.
Args:
api_key: Clé API OpenRouter (optionnelle pour la liste des modèles)
Returns:
Liste des modèles avec leurs informations (id, nom, coûts, catégories)
"""
try:
headers = {"Content-Type": "application/json"}
if api_key:
headers["Authorization"] = f"Bearer {api_key}"
response = requests.get(
f"{OPENROUTER_API_URL}/models",
headers=headers,
timeout=30
)
response.raise_for_status()
data = response.json()
models = data.get("data", [])
# Transformer et enrichir les données des modèles
processed_models = []
for model in models:
processed_model = {
"id": model.get("id", ""),
"name": model.get("name", model.get("id", "")),
"description": model.get("description", ""),
"context_length": model.get("context_length", 0),
"pricing": {
"prompt": float(model.get("pricing", {}).get("prompt", 0)),
"completion": float(model.get("pricing", {}).get("completion", 0)),
},
"top_provider": model.get("top_provider", {}),
"architecture": model.get("architecture", {}),
}
# Déterminer les catégories du modèle
categories = OpenRouterService._categorize_model(model)
processed_model["categories"] = categories
processed_models.append(processed_model)
# Trier par nom
processed_models.sort(key=lambda x: x["name"].lower())
logger.info(f"Récupéré {len(processed_models)} modèles OpenRouter")
return processed_models
except requests.RequestException as e:
logger.error(f"Erreur lors de la récupération des modèles OpenRouter: {e}")
return []
except Exception as e:
logger.error(f"Erreur inattendue: {e}")
return []
@staticmethod
def _categorize_model(model: dict[str, Any]) -> list[str]:
"""
Détermine les catégories d'un modèle basé sur ses capacités.
Categories possibles: chat, vision, code, reasoning, free
"""
categories = []
model_id = model.get("id", "").lower()
architecture = model.get("architecture", {})
# Tous les modèles supportent le chat de base
categories.append("chat")
# Détection Vision
modality = architecture.get("modality", "")
if "image" in modality or "vision" in model_id or "4o" in model_id:
categories.append("vision")
# Détection Code
code_keywords = ["code", "codestral", "deepseek-coder", "starcoder", "wizard-coder"]
if any(kw in model_id for kw in code_keywords):
categories.append("code")
# Détection Reasoning (modèles avec capacités de raisonnement avancé)
reasoning_keywords = ["o1", "o3", "reasoning", "think", "r1"]
if any(kw in model_id for kw in reasoning_keywords):
categories.append("reasoning")
# Modèles gratuits
pricing = model.get("pricing", {})
prompt_price = float(pricing.get("prompt", 1))
completion_price = float(pricing.get("completion", 1))
if prompt_price == 0 and completion_price == 0:
categories.append("free")
return categories
@staticmethod
def get_models_by_category(
api_key: str | None = None,
category: str | None = None
) -> list[dict[str, Any]]:
"""
Récupère les modèles filtrés par catégorie.
Args:
api_key: Clé API OpenRouter
category: Catégorie à filtrer (chat, vision, code, reasoning, free)
Returns:
Liste des modèles filtrés
"""
models = OpenRouterService.get_models(api_key)
if not category:
return models
return [m for m in models if category.lower() in m.get("categories", [])]
@staticmethod
def format_price(price_per_token: float) -> str:
"""
Formate le prix par token en format lisible.
Args:
price_per_token: Prix par token (généralement très petit)
Returns:
Prix formaté (ex: "$0.50 / 1M tokens")
"""
if price_per_token == 0:
return "Gratuit"
# Prix par million de tokens
price_per_million = price_per_token * 1_000_000
if price_per_million < 0.01:
return f"${price_per_million:.4f} / 1M"
elif price_per_million < 1:
return f"${price_per_million:.3f} / 1M"
else:
return f"${price_per_million:.2f} / 1M"