diff --git a/modules/ai_huggingface.py b/modules/ai_huggingface.py index c46b879..cf96a7d 100644 --- a/modules/ai_huggingface.py +++ b/modules/ai_huggingface.py @@ -1,16 +1,34 @@ import os +import json import requests from modules.logger import log_event -def interroger_huggingface(prompt: str, modele_cible="mistralai/Mistral-7B-Instruct-v0.3"): +CATALOGUE_FILE = "/DATA/AppData/MULTI-IA-AETHAS38/catalogue_dynamique.json" + +def interroger_huggingface(prompt: str, modele_cible=None): """ - Connecteur pour l'API Serverless gratuite de Hugging Face. - S'intègre nativement dans la boucle de repli de ai_engine.py. + Connecteur pour l'API Serverless de Hugging Face. + Sélectionne dynamiquement le modèle le plus populaire scanné par l'Updater. """ api_key = os.getenv("HUGGINGFACE_API_KEY") if not api_key: raise Exception("Clé API HuggingFace manquante dans le .env") + # Extraction dynamique du meilleur modèle Hugging Face gratuit disponible + if not modele_cible: + try: + if os.path.exists(CATALOGUE_FILE): + with open(CATALOGUE_FILE, "r", encoding="utf-8") as f: + data = json.load(f) + hf_models = [m.replace("huggingface/", "") for m in data.get("free_models", []) if m.startswith("huggingface/")] + if hf_models: + modele_cible = hf_models[0] # Modèle le plus téléchargé selon l'Updater + except Exception: + pass + + if not modele_cible: + modele_cible = "mistralai/Mistral-7B-Instruct-v0.3" + url = f"https://router.huggingface.co/hf-inference/models/{modele_cible}" headers = { "Authorization": f"Bearer {api_key}", @@ -33,10 +51,9 @@ def interroger_huggingface(prompt: str, modele_cible="mistralai/Mistral-7B-Instr data = resp.json() if isinstance(data, list) and len(data) > 0 and "generated_text" in data[0]: texte = data[0]["generated_text"].strip() - # Renvoie la signature exacte attendue par ai_engine (texte, couleur, fournisseur, modele) return texte, "#ffb000", "HuggingFace", modele_cible raise Exception(f"Erreur {resp.status_code} : {resp.text}") except Exception as e: - log_event("WARNING", "HUGGINGFACE", f"Échec de la requête : {e}") + log_event("WARNING", "HUGGINGFACE", f"Échec de la requête pour {modele_cible} : {e}") raise e \ No newline at end of file diff --git a/modules/ai_openrouter.py b/modules/ai_openrouter.py index adfff05..8a9a891 100644 --- a/modules/ai_openrouter.py +++ b/modules/ai_openrouter.py @@ -18,34 +18,88 @@ def verifier_cle_api(): return cle def obtenir_modele_dynamique(premium: bool, prompt: str = ""): - """Charge le catalogue dynamiquement et adapte le modèle si la règle Éducation est détectée.""" + """Charge le catalogue dynamiquement pour un seul modèle optimal.""" est_education = "RÈGLE ÉDUCATION NATIONALE" in prompt try: if os.path.exists(CATALOGUE_FILE): with open(CATALOGUE_FILE, "r", encoding="utf-8") as f: data = json.load(f) - if premium: - paid_models = data.get("paid_models", []) - claude = [m for m in paid_models if "claude-3.5-sonnet" in m.lower()] - return claude[0] if claude else (paid_models[0] if paid_models else "anthropic/claude-3.5-sonnet") - else: - # En mode gratuit + Education, Mistral est le plus pointu en syntaxe française - if est_education: - return "mistralai/mistral-small-24b-instruct-2501:free" - - free_models = data.get("free_models", []) - priorites = [m for m in free_models if "gemma" in m.lower() or "nemotron" in m.lower()] - return priorites[0] if priorites else (free_models[0] if free_models else "google/gemma-2-9b-it:free") + + if premium: + paid_models = data.get("paid_models", []) + if not paid_models: return "anthropic/claude-3.5-sonnet" + claude = [m for m in paid_models if "claude-3.5-sonnet" in m.lower()] + return claude[0] if claude else paid_models[0] + else: + free_models = [m for m in data.get("free_models", []) if not m.startswith("huggingface/")] + if not free_models: return "google/gemma-2-9b-it:free" + + if est_education: + mistral = [m for m in free_models if "mistral" in m.lower()] + return mistral[0] if mistral else free_models[0] + + priorites = [m for m in free_models if "gemma" in m.lower() or "nemotron" in m.lower()] + return priorites[0] if priorites else free_models[0] except Exception: pass - if premium: return "anthropic/claude-3.5-sonnet" - return "mistralai/mistral-small-24b-instruct-2501:free" if est_education else "google/gemma-2-9b-it:free" + return "anthropic/claude-3.5-sonnet" if premium else "google/gemma-2-9b-it:free" + +def obtenir_modeles_multi_dynamiques(premium: bool, prompt: str) -> list: + """Détermine dynamiquement les modèles et leur nombre selon le sujet de la requête.""" + try: + with open(CATALOGUE_FILE, "r", encoding="utf-8") as f: + data = json.load(f) + except Exception: + data = {"free_models": [], "paid_models": []} + + modeles_dispos = data.get("paid_models", []) if premium else data.get("free_models", []) + modeles_or = [m for m in modeles_dispos if not m.startswith("huggingface/")] + + if not modeles_or: + return ["anthropic/claude-3.5-sonnet"] if premium else ["google/gemma-2-9b-it:free"] + + # Analyse sémantique du sujet pour définir le nombre d'IA et la priorité + prompt_lower = prompt.lower() + nb_ia = 3 # Par défaut + + if any(mot in prompt_lower for mot in ["code", "script", "python", "javascript", "html", "css", "erreur", "bug", "kubejs", "serveur"]): + nb_ia = 5 + mots_cles = ["coder", "claude", "llama", "qwen", "deepseek"] + elif any(mot in prompt_lower for mot in ["loi", "juridique", "droit", "contrat", "urssaf", "légal"]): + nb_ia = 4 + mots_cles = ["mistral", "claude", "llama", "mixtral"] + elif any(mot in prompt_lower for mot in ["traduis", "rédaction", "écris", "lettre", "mail", "éducation"]): + nb_ia = 2 + mots_cles = ["mistral", "claude", "llama", "gemini", "gemma"] + else: + mots_cles = ["gemini", "llama", "mistral", "qwen", "gemma"] + + modeles_selectionnes = [] + + # Étape 1 : Sélectionner les modèles qui matchent les mots-clés + for mc in mots_cles: + for m in modeles_or: + if mc in m.lower() and m not in modeles_selectionnes: + modeles_selectionnes.append(m) + if len(modeles_selectionnes) == nb_ia: + break + if len(modeles_selectionnes) == nb_ia: + break + + # Étape 2 : Compléter avec les autres modèles du catalogue si le nombre n'est pas atteint + for m in modeles_or: + if len(modeles_selectionnes) >= nb_ia: + break + if m not in modeles_selectionnes: + modeles_selectionnes.append(m) + + return modeles_selectionnes def interroger_openrouter(prompt: str, type_modele: str = "openrouter_chat", premium: bool = False, max_retries: int = 2, chat_id: str = None): verifier_cle_api() - nom_modele_api = obtenir_modele_dynamique(premium, prompt) # On passe le prompt pour détecter le marqueur + nom_modele_api = obtenir_modele_dynamique(premium, prompt) nom_affichage = f"OpenRouter ({nom_modele_api.split('/')[-1]})" couleur = "#d97757" if premium else "#4a90e2" @@ -83,23 +137,9 @@ def interroger_openrouter_multi(prompt: str, type_modele: str = "openrouter_code verifier_cle_api() extra_body_data = {"session_id": chat_id} if chat_id else {} - # --- LA LISTE BLANCHE DES 5 MEILLEURS MODÈLES --- - if premium: - modeles = [ - "anthropic/claude-3.5-sonnet", # Le meilleur en code/logique - "openai/gpt-4o", # Ultra polyvalent - "google/gemini-1.5-pro", # Excellent contexte - "mistralai/mistral-large-2407", # Top pour le français - "meta-llama/llama-3.1-405b-instruct" # Le titan OpenSource - ] - else: - modeles = [ - "google/gemma-2-9b-it:free", - "meta-llama/llama-3.1-8b-instruct:free", - "microsoft/phi-3-mini-128k-instruct:free", - "mistralai/mistral-nemo:free", - "qwen/qwen-2-7b-instruct:free" - ] + # La liste s'adapte dynamiquement au nombre d'IA requis par le sujet et aux disponibilités + modeles = obtenir_modeles_multi_dynamiques(premium, prompt) + log_event("INFO", "OPENROUTER_MULTI", f"Lancement Multi-IA avec {len(modeles)} modèles adaptatifs.") reponses = [] for modele in modeles: @@ -115,7 +155,7 @@ def interroger_openrouter_multi(prompt: str, type_modele: str = "openrouter_code if contenu: reponses.append({"modele": modele, "texte": contenu}) except Exception as e: - log_event("WARNING", "OPENROUTER_MULTI", f"Échec rapide pour {modele} : {e}") + log_event("WARNING", "OPENROUTER_MULTI", f"Échec pour {modele} : {e}") continue return reponses \ No newline at end of file