From 6f7e3efd6edb9fc4c7eed78becd72038b7635c7c Mon Sep 17 00:00:00 2001 From: Xavier Date: Wed, 7 Oct 2026 14:31:46 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20ajout=20du=20syst=C3=A8me=20de=20rotati?= =?UTF-8?q?on=20de=20logs=20(7j),=20correction=20du=20mod=C3=A8le=20g?= =?UTF-8?q?=C3=A9n=C3=A9rique=20OpenRouter,=20cr=C3=A9ation=20du=20Wiki=20?= =?UTF-8?q?et=20pr=C3=A9paration=20BDD=20mod=C3=A8les/finances?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WIKI.md | 20 +++++++++++++++ backend/ai.py | 63 ++++++++++++++++++----------------------------- backend/logger.py | 24 ++++++++++++++++++ backend/models.py | 27 +++++++++++++++++++- 4 files changed, 94 insertions(+), 40 deletions(-) create mode 100644 WIKI.md create mode 100644 backend/logger.py diff --git a/WIKI.md b/WIKI.md new file mode 100644 index 0000000..1a91622 --- /dev/null +++ b/WIKI.md @@ -0,0 +1,20 @@ +# 📚 WIKI AETHAS38 - Configuration des Fournisseurs IA + +Ce document détaille la procédure pour obtenir les clés API avec les droits complets (Inférence + Accès financier) nécessaires au fonctionnement de l'Orchestrateur AETHAS38. + +## 1. OpenRouter (Prioritaire) +OpenRouter agit comme un agrégateur. C'est le fournisseur recommandé car il permet d'accéder à presque tous les modèles (y compris Gemini, Claude, Llama) avec un seul portefeuille prépayé. +* **Obtenir la clé API :** Allez sur [OpenRouter Keys](https://openrouter.ai/keys). Créez une clé standard sans limite de crédit pour permettre à AETHAS38 de requêter les modèles. +* **Accès Financier (Management Key) :** Pour que AETHAS38 lise votre solde en temps réel, vous devez générer une clé spécifique d'administration (Management Key) sur la même page, ou accorder les droits `read_balance` à votre clé API principale selon les dernières mises à jour de leur interface. + +## 2. Google Gemini Studio +* **Obtenir la clé API :** Rendez-vous sur [Google AI Studio](https://aistudio.google.com/app/apikey). +* **Levée des quotas :** Par défaut, la clé est gratuite mais soumise à des limites strictes (ex: 15 requêtes/minute) qui bloqueront le travail en parallèle d'AETHAS38. Pour un accès total, liez votre projet AI Studio à un compte de facturation Google Cloud Platform (GCP). + +## 3. DeepSeek & Groq +* **DeepSeek :** [DeepSeek Platform](https://platform.deepseek.com/api_keys). Facturation à l'usage (Token). Il faut provisionner le compte par carte bancaire (Top-up). +* **Groq :** [Groq Console](https://console.groq.com/keys). Actuellement en phase gratuite avec quotas de requêtes très élevés, idéal pour les travailleurs de base de l'orchestrateur. + +## 4. Cloudflare Workers AI & HuggingFace +* **Cloudflare :** [Cloudflare Dash](https://dash.cloudflare.com/profile/api-tokens). Créez un token personnalisé avec la permission `Workers AI : Read/Write`. L'ID de compte (Account ID) est visible dans la barre latérale droite de l'aperçu de la zone. +* **HuggingFace :** [HF Settings](https://huggingface.co/settings/tokens). Générez un token *Fine-Grained* et cochez les permissions liées à l'`Inference API` pour permettre l'exécution des modèles serverless. \ No newline at end of file diff --git a/backend/ai.py b/backend/ai.py index 9e8cf23..1e9b4d4 100644 --- a/backend/ai.py +++ b/backend/ai.py @@ -1,66 +1,52 @@ from openai import OpenAI from fastapi import HTTPException from .models import SystemSettings +from .logger import system_logger def get_ai_response(messages: list, settings: SystemSettings) -> str: - """ - Route la conversation vers le premier fournisseur IA disponible configuré par l'admin. - """ + """Route la conversation vers le premier fournisseur IA disponible, priorité à Gemini Direct.""" if not settings: + system_logger.error("Configuration système introuvable lors de l'appel IA.") raise HTTPException(status_code=500, detail="Configuration système introuvable.") - # Formatage de l'historique pour l'API (OpenAI compatible) formatted_messages = [{"role": msg.role, "content": msg.content} for msg in messages] try: - # 1. Test OpenRouter - if settings.openrouter_api_key: + # 1. Priorité absolue : Test Gemini direct (Rapide, sans intermédiaire) + if settings.gemini_api_key: client = OpenAI( - base_url="https://openrouter.ai/api/v1", - api_key=settings.openrouter_api_key, + base_url="https://generativelanguage.googleapis.com/v1beta/openai/", + api_key=settings.gemini_api_key ) - model = "google/gemini-pro" + # Utilisation de l'identifiant standard API pour la version Flash + model = "gemini-1.5-flash" + system_logger.info(f"Appel IA via Google Gemini direct (Modèle: {model})") + response = client.chat.completions.create(model=model, messages=formatted_messages) + + # 2. Test OpenRouter (Fallback) + elif settings.openrouter_api_key: + client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=settings.openrouter_api_key) + model = "google/gemini-1.5-pro" # Identifiant mis à jour et valide + system_logger.info(f"Appel IA via OpenRouter (Modèle: {model})") response = client.chat.completions.create( model=model, messages=formatted_messages, extra_headers={"HTTP-Referer": "https://aethas38.duckdns.org", "X-Title": "AETHAS38"} ) - - # 2. Test Gemini direct (Google propose désormais une API compatible OpenAI !) - elif settings.gemini_api_key: - client = OpenAI( - base_url="https://generativelanguage.googleapis.com/v1beta/openai/", - api_key=settings.gemini_api_key, - ) - model = "gemini-1.5-flash" - response = client.chat.completions.create( - model=model, - messages=formatted_messages - ) # 3. Test DeepSeek elif settings.deepseek_api_key: - client = OpenAI( - base_url="https://api.deepseek.com/v1", - api_key=settings.deepseek_api_key, - ) + client = OpenAI(base_url="https://api.deepseek.com/v1", api_key=settings.deepseek_api_key) model = "deepseek-chat" - response = client.chat.completions.create( - model=model, - messages=formatted_messages - ) + system_logger.info(f"Appel IA via DeepSeek (Modèle: {model})") + response = client.chat.completions.create(model=model, messages=formatted_messages) # 4. Test Groq elif settings.groq_api_key: - client = OpenAI( - base_url="https://api.groq.com/openai/v1", - api_key=settings.groq_api_key, - ) + client = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=settings.groq_api_key) model = "llama3-8b-8192" - response = client.chat.completions.create( - model=model, - messages=formatted_messages - ) + system_logger.info(f"Appel IA via Groq (Modèle: {model})") + response = client.chat.completions.create(model=model, messages=formatted_messages) else: raise HTTPException(status_code=400, detail="Aucune clé API IA n'est configurée.") @@ -68,6 +54,5 @@ def get_ai_response(messages: list, settings: SystemSettings) -> str: return response.choices[0].message.content except Exception as e: - # On capture l'erreur exacte et on la renvoie au frontend pour comprendre le blocage - print(f"Erreur détaillée : {str(e)}") + system_logger.error(f"Erreur API IA : {str(e)}") raise HTTPException(status_code=502, detail=f"Détail du fournisseur : {str(e)}") \ No newline at end of file diff --git a/backend/logger.py b/backend/logger.py new file mode 100644 index 0000000..e623973 --- /dev/null +++ b/backend/logger.py @@ -0,0 +1,24 @@ +import logging +from logging.handlers import TimedRotatingFileHandler +import os + +# Création du dossier logs à la racine du projet +log_dir = os.path.join(os.getcwd(), "logs") +os.makedirs(log_dir, exist_ok=True) + +# Configuration du logger +system_logger = logging.getLogger("AETHAS38_Logger") +system_logger.setLevel(logging.INFO) + +# Rotation tous les jours à minuit, conservation sur 7 jours +handler = TimedRotatingFileHandler( + filename=os.path.join(log_dir, "aethas38_system.log"), + when="midnight", + interval=1, + backupCount=7, + encoding="utf-8" +) + +formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', datefmt='%d/%m/%Y %H:%M:%S') +handler.setFormatter(formatter) +system_logger.addHandler(handler) \ No newline at end of file diff --git a/backend/models.py b/backend/models.py index 41b0eab..a3af3a6 100644 --- a/backend/models.py +++ b/backend/models.py @@ -2,6 +2,8 @@ from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, T from sqlalchemy.orm import relationship from datetime import datetime, timezone from .database import Base +from sqlalchemy import Float, DateTime +from datetime import datetime, timezone class User(Base): __tablename__ = "users" @@ -61,4 +63,27 @@ class SystemSettings(Base): mistral_api_key = Column(String, nullable=True) cloudflare_account_id = Column(String, nullable=True) cloudflare_api_token = Column(String, nullable=True) - huggingface_api_key = Column(String, nullable=True) \ No newline at end of file + huggingface_api_key = Column(String, nullable=True) + + class AIModel(Base): + """Stocke la liste des modèles extraits depuis les fournisseurs.""" + __tablename__ = "ai_models" + + id = Column(Integer, primary_key=True, index=True) + provider = Column(String, index=True) # ex: openrouter, groq + model_id = Column(String, unique=True, index=True) # ex: google/gemini-1.5-pro + name = Column(String) + context_length = Column(Integer) + pricing_prompt = Column(Float) # Coût pour 1M tokens + pricing_completion = Column(Float) + last_updated = Column(DateTime, default=lambda: datetime.now(timezone.utc)) + +class FinancialLog(Base): + """Suivi financier par fournisseur.""" + __tablename__ = "financial_logs" + + id = Column(Integer, primary_key=True, index=True) + provider = Column(String, index=True) + balance = Column(Float) # Crédit restant + total_usage = Column(Float) # Consommation totale + checked_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) \ No newline at end of file