Files
xavier 3b2cf51a24
Build and Push Docker Image / build-and-push (push) Successful in 35s
Amélioration fonction RAG auto incrémentation
2026-08-28 20:13:17 +02:00

217 lines
11 KiB
Python

import os
import json
from datetime import date
import uuid
import time
import re
import psutil
import threading
import requests
from fasthtml.common import *
from modules.database import load_history, save_history
from modules.scraper import rechercher_sur_google, extraire_contenu_urls
from modules.router import nettoyer_decision_router
from modules.ai_local import interroger_local_ollama
from modules.ai_engine import executer_requete_ia
from modules.ui_components import render_user_msg, render_ai_msg, render_sidebar, render_popup_paiement
from modules.logger import log_event
from modules.billing_manager import verifier_et_valider_budget, obtenir_solde
from modules.rag_manager import rechercher_contexte_rag, auto_apprendre_rag
from modules.moderator import analyser_intention_illicite, appliquer_sanction_et_archiver
from modules.ai_media import router_media
def evaluer_pertinence_pour_rag(prompt_utilisateur: str, reponse_ia: str) -> bool:
"""Juge si la réponse est suffisamment technique/factuelle pour être indexée."""
prompt_superviseur = f"""Tu es un bibliothécaire strict. Évalue si cet échange mérite d'être conservé.
Règle 1 : Si la réponse contient des infos factuelles, du code, des règles ou des configurations techniques, réponds UNIQUEMENT "OUI".
Règle 2 : Si c'est conversationnel, vague, ou une erreur, réponds UNIQUEMENT "NON".
Question : {prompt_utilisateur}
Réponse : {reponse_ia[:800]}
"""
try:
reponse = requests.post("http://172.19.0.1:11434/api/generate", json={
"model": "llama3.1", # Modèle rapide local via Ollama
"prompt": prompt_superviseur,
"stream": False
}, timeout=10).json()
return "OUI" in reponse.get("response", "").strip().upper()
except Exception:
return False
def check_and_update_resource_quota(utilisateur_nom, role_utilisateur):
quota_file = "/DATA/AppData/MULTI-IA-AETHAS38/Utilisateurs/quotas_ressources.json"
today = str(date.today())
limites = {"super admin": 10, "admin": 10, "vip": 5, "famille": 5, "standard": 3}
limite_max = limites.get(role_utilisateur.lower(), 3)
try:
if os.path.exists(quota_file):
with open(quota_file, "r") as f: data = json.load(f)
else: data = {}
except: data = {}
if data.get("date") != today: data = {"date": today, "users": {}}
user_uses = data["users"].get(utilisateur_nom, 0)
if user_uses >= limite_max: return False
data["users"][utilisateur_nom] = user_uses + 1
os.makedirs(os.path.dirname(quota_file), exist_ok=True)
with open(quota_file, "w") as f: json.dump(data, f)
return True
async def handle_chat_request(request, session):
start_time = time.time()
try:
form = await request.form()
prompt = form.get("prompt", "")
chat_id = form.get("chat_id", str(uuid.uuid4()))
files = form.getlist("files")
mode_payant = form.get("mode_payant") == "on"
mode_multi_ia = form.get("mode_multi_ia") == "on"
mode_education = form.get("mode_education") == "on"
media_mode = form.get("media_mode", "texte")
validation_acquise = form.get("validation_acquise") == "true"
choix_utilisateur = form.get("choix_utilisateur", "aucun")
if choix_utilisateur != "aucun":
log_event("INFO", "BILLING", f"Décision utilisateur face au pop-up : {choix_utilisateur}")
if choix_utilisateur == "gratuit_force":
mode_payant = False
session["current_chat_id"] = chat_id
history = load_history()
utilisateur_nom = session.get("user", "xavier")
role_utilisateur = "Super Admin" if utilisateur_nom.lower() == "xavier" else ("VIP" if utilisateur_nom.lower() in ["céline", "anaëlle", "renan", "audric"] else "standard")
ip_client = request.client.host if request.client else "Inconnue"
cpu_usage = psutil.cpu_percent(interval=0.1)
ram_usage = psutil.virtual_memory().percent
if cpu_usage > 60.0 or ram_usage > 60.0:
if not check_and_update_resource_quota(utilisateur_nom, role_utilisateur):
return Div(H3("⚠️ Limite de requêtes atteintes", style="color:#ff9800;"), P(f"Serveur très sollicité (>60%). Quota épuisé."), style="background:#333; padding:15px; border-radius:10px; border: 1px solid #ff9800;")
est_illicite, raison = analyser_intention_illicite(prompt)
if est_illicite:
sanction = appliquer_sanction_et_archiver(utilisateur_nom, prompt, raison, ip_client)
return Div(H3("⚠️ Avertissement de Sécurité"), P(f"Sanction : {sanction}"), style="color:red; background:#300; padding:15px; border-radius:10px;")
file_summary = []
file_names = []
for f in files[:10]:
if f and f.filename:
file_names.append(f.filename)
content = await f.read()
file_summary.append(f"\n--- Fichier : {f.filename} ---\n{content.decode('utf-8', errors='ignore')}")
liste_fichiers_str = ", ".join(file_names) if file_names else "Aucun fichier"
log_event("INFO", "SYSTEM", f"Requête reçue (Mode: {media_mode.upper()} / Payant: {mode_payant})")
# --- AIGUILLAGE MULTIMÉDIA ---
if media_mode in ["image", "video"]:
reponse, couleur, fournisseur_nom, modele, trace_route = router_media(prompt, type_media=media_mode, premium=mode_payant, fichiers=file_names)
intention = f"Génération {media_mode.capitalize()}"
thematique_rag = "aucun"
# --- AIGUILLAGE TEXTE (CLASSIQUE) ---
else:
sec_prompt = f"""Tu es l'Orchestrateur système. Analyse la requête et choisis le meilleur fournisseur IA..."""
try:
texte_routeur, _, _, _ = interroger_local_ollama(sec_prompt)
decision = nettoyer_decision_router(texte_routeur)
except:
decision = {"intention": "Analyse", "fournisseur": "openrouter_chat", "theme_rag": "aucun"}
intention = decision.get("intention", "Analyse")
fournisseur = decision.get("fournisseur", "openrouter_chat").lower()
thematique_rag = decision.get("theme_rag", "aucun").lower()
if mode_payant or mode_multi_ia:
fournisseur = "openrouter_code"
intention += " (API Premium/Multi)"
if file_names:
fournisseur = "gemini"
if mode_education:
fournisseur = "openrouter_chat"
thematique_rag = "Education_Nationale"
intention = "Éducation Nationale " + ("(Payant)" if mode_payant else "(IA Gratuite)")
instruction_edu = "[SYSTÈME] RÈGLE ÉDUCATION NATIONALE : Agis comme un expert de l'Éducation Nationale française. Pédagogie stricte, respect du B.O.\n\n"
prompt = instruction_edu + prompt
contexte_rag = ""
if thematique_rag != "aucun":
contexte_rag = rechercher_contexte_rag(prompt, categorie=thematique_rag)
urls = re.findall(r"https?://[^\s]+", prompt)
if any(m in prompt.lower() for m in ["cherche", "recherche", "sur le web"]):
urls.extend(rechercher_sur_google(prompt))
web_text = extraire_contenu_urls(list(set(urls))) if urls else ""
full_prompt = prompt + ("\n" + "\n".join(file_summary) if file_summary else "")
if contexte_rag: full_prompt += f"\n\n[Contexte RAG :\n{contexte_rag}]"
if web_text: full_prompt += f"\n\n[Contexte web :\n{web_text}]"
if "openrouter" in fournisseur:
modele_estime = "anthropic/claude-3.5-sonnet" if mode_payant else "cohere/north-mini-code:free"
if mode_payant and not validation_acquise:
from modules.billing_manager import estimer_cout_facture
_, prix_estime, _ = estimer_cout_facture(full_prompt, modele_estime, rang_utilisateur=role_utilisateur, multi_ia=mode_multi_ia)
if prix_estime > 0:
return render_popup_paiement(prix_estime, prompt, chat_id, mode_multi_ia, mode_education)
if mode_payant and role_utilisateur.lower() not in ["super admin", "admin", "vip", "famille"]:
from modules.billing_manager import estimer_cout_facture
_, prix_estime, _ = estimer_cout_facture(full_prompt, modele_estime, rang_utilisateur=role_utilisateur, multi_ia=mode_multi_ia)
if prix_estime > obtenir_solde(utilisateur_nom):
return Div(H3("❌ Solde Insuffisant", style="color:red;"), P("Rechargez votre compte."), style="background:#300; padding:15px;")
reponse, couleur, fournisseur_nom, modele, trace_route = executer_requete_ia(
fournisseur, full_prompt, mode_payant=mode_payant, multi_ia=mode_multi_ia, chat_id=chat_id
)
if "openrouter" in fournisseur_nom.lower() and mode_payant:
verifier_et_valider_budget(full_prompt, modele, utilisateur_nom=utilisateur_nom, rang_utilisateur=role_utilisateur, multi_ia=mode_multi_ia)
# --- SUPERVISEUR IA EN ARRIÈRE-PLAN ---
if thematique_rag != "aucun" and reponse:
def superviseur_et_rag(p, r, t):
if evaluer_pertinence_pour_rag(p, r):
log_event("INFO", "SUPERVISEUR", f"Réponse validée. Indexation dans {t}")
auto_apprendre_rag(p, r, t)
else:
log_event("INFO", "SUPERVISEUR", "Échange ignoré pour le RAG (non pertinent).")
threading.Thread(target=superviseur_et_rag, args=(prompt, reponse, thematique_rag)).start()
if chat_id not in history:
history[chat_id] = {"title": (prompt[:30] if prompt else "Discussion") + "...", "pinned": False, "timestamp": time.time(), "messages": []}
execution_time = round(time.time() - start_time, 1)
display_msg = prompt.replace("[SYSTÈME] RÈGLE ÉDUCATION NATIONALE : Agis comme un expert de l'Éducation Nationale française. Pédagogie stricte, respect du B.O.\n\n", "")
if file_names: display_msg += f"\n\n📎 **Fichiers joints :** {', '.join(file_names)}"
id_message_unique = str(uuid.uuid4())
history[chat_id]["messages"].append({"role": "user", "content": display_msg})
history[chat_id]["messages"].append({
"role": "ai",
"content": reponse,
"intention": intention,
"fournisseur": fournisseur_nom,
"modele": modele,
"couleur": couleur,
"execution_time": execution_time,
"trace": trace_route,
"prompt_associe": prompt,
"thematique": thematique_rag,
"msg_id": id_message_unique
})
save_history(history)
return render_user_msg(display_msg), render_ai_msg(reponse, intention, fournisseur_nom, modele, couleur, execution_time, trace_route, prompt, thematique_rag, id_message_unique), Script("renderMarkdown();"), render_sidebar()
except Exception as e:
log_event("CRITICAL", "SYSTEM", f"Erreur : {e}")
return Div(f"Erreur système : {e}", style="color:red; background:#300; padding:15px; border-radius:10px;")