fix: Résolution import RAG et correction HTTPResponse pour les pings
Build and Push Docker Image / build-and-push (push) Successful in 38s

This commit is contained in:
xavier committed 2026-08-20 19:33:17 +02:00
1 parent ffe820b00f
commit 2d7d93bc27
6 files changed
+3164 -59

No files matched your search

File diff suppressed because it is too large. Load diff
+6
View File
@@ -75,5 +75,11 @@ Pour fonctionner de manière optimale, l'Orchestrateur Local nécessite au minim
- [x] Protection anti-abus Admin : Si un Admin enfreint la loi, il est exclu de l'alerte ; seuls les autres Admins statuent. - [x] Protection anti-abus Admin : Si un Admin enfreint la loi, il est exclu de l'alerte ; seuls les autres Admins statuent.
- [x] Archivage légal sécurisé : Stockage pérenne des logs, preuves d'infractions et **décisions d'audit manuscrites des Admins** dans une arborescence dédiée. - [x] Archivage légal sécurisé : Stockage pérenne des logs, preuves d'infractions et **décisions d'audit manuscrites des Admins** dans une arborescence dédiée.
### Phase 10 : Base de Connaissances Intelligente (RAG) 📚
- [x] Initialisation de l'arborescence dynamique (`modules/rag_manager.py`) sur le RAID (`/DATA/AppData/MULTI-IA-AETHAS38/RAG/`).
- [x] Évolution de l'Orchestrateur Local (Qwen) pour la sélection automatique du dossier sémantique pertinent ("minecraft", "droit", etc.).
- [x] Injection asynchrone des documents Markdown et Texte directement dans la fenêtre de contexte des LLMs.
- [ ] Interface UI : Ajout d'une vue Administrateur pour téléverser de nouveaux documents RAG directement depuis le navigateur.
## 📷 Vidéosurveillance & Détection Intelligente ## 📷 Vidéosurveillance & Détection Intelligente
*(Voir configuration Frigate locale et Google Coral TPU existante)* *(Voir configuration Frigate locale et Google Coral TPU existante)*
+324
View File
@@ -3586,5 +3586,329 @@
"action": "Acc\u00e8s \u00e0 /ping", "action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 06:57:39", "date": "2026-08-20 06:57:39",
"timestamp": 1787201859.937185 "timestamp": 1787201859.937185
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /",
"date": "2026-08-20 15:27:42",
"timestamp": 1787232462.760368
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:27:47",
"timestamp": 1787232467.9892862
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:27:52",
"timestamp": 1787232472.976546
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:27:58",
"timestamp": 1787232478.288624
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:28:02",
"timestamp": 1787232482.9765418
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:28:08",
"timestamp": 1787232488.2938766
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:28:12",
"timestamp": 1787232492.9783697
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:28:18",
"timestamp": 1787232498.2986085
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:28:22",
"timestamp": 1787232502.9857152
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:28:28",
"timestamp": 1787232508.2840195
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:28:32",
"timestamp": 1787232512.9795508
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:28:39",
"timestamp": 1787232519.2652702
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:28:43",
"timestamp": 1787232523.938412
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:28:49",
"timestamp": 1787232529.2499743
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:28:53",
"timestamp": 1787232533.9441636
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:28:59",
"timestamp": 1787232539.2690368
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:29:03",
"timestamp": 1787232543.951135
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:29:09",
"timestamp": 1787232549.2610183
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:29:13",
"timestamp": 1787232553.951553
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:29:19",
"timestamp": 1787232559.2616453
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:29:23",
"timestamp": 1787232563.950994
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:29:29",
"timestamp": 1787232569.304894
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 15:29:33",
"timestamp": 1787232573.9450626
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:25:24",
"timestamp": 1787246724.425951
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /",
"date": "2026-08-20 19:25:24",
"timestamp": 1787246724.4533405
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /",
"date": "2026-08-20 19:25:27",
"timestamp": 1787246727.3842022
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:25:32",
"timestamp": 1787246732.9418385
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:25:37",
"timestamp": 1787246737.9472933
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:25:42",
"timestamp": 1787246742.9488406
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:25:47",
"timestamp": 1787246747.9486556
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:25:52",
"timestamp": 1787246752.9420183
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:25:57",
"timestamp": 1787246757.937178
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:26:02",
"timestamp": 1787246762.9488032
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:26:07",
"timestamp": 1787246767.9528205
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:26:12",
"timestamp": 1787246772.9503908
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:26:17",
"timestamp": 1787246777.9426181
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:26:22",
"timestamp": 1787246782.9457717
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:26:27",
"timestamp": 1787246787.9487703
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:27:29",
"timestamp": 1787246849.2826297
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:28:28",
"timestamp": 1787246908.9441626
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:30:29",
"timestamp": 1787247029.5549562
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /",
"date": "2026-08-20 19:30:29",
"timestamp": 1787247029.5876029
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:30:34",
"timestamp": 1787247034.942688
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:30:39",
"timestamp": 1787247039.9398396
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:30:44",
"timestamp": 1787247044.9434073
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:30:49",
"timestamp": 1787247049.9399495
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:30:54",
"timestamp": 1787247054.9533317
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:30:59",
"timestamp": 1787247059.9391792
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:31:04",
"timestamp": 1787247064.9469361
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:31:09",
"timestamp": 1787247069.9403768
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:31:14",
"timestamp": 1787247074.9371598
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:31:19",
"timestamp": 1787247079.945977
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:31:24",
"timestamp": 1787247084.9489312
},
{
"ip": "127.0.0.1",
"action": "Acc\u00e8s \u00e0 /ping",
"date": "2026-08-20 19:32:26",
"timestamp": 1787247146.2469366
} }
] ]
+4 -4
View File
@@ -1,7 +1,7 @@
import uuid import uuid
from fasthtml.common import * from fasthtml.common import *
from starlette.staticfiles import StaticFiles from starlette.staticfiles import StaticFiles
from starlette.responses import FileResponse from starlette.responses import FileResponse, Response
from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.base import BaseHTTPMiddleware
# Imports ultra-propres depuis les modules dédiés # Imports ultra-propres depuis les modules dédiés
@@ -54,10 +54,10 @@ async def post(request, session: dict):
return await handle_chat_request(request, session) return await handle_chat_request(request, session)
@rt('/favicon.ico') @rt('/favicon.ico')
def favicon_get(): return HTTPResponse(status_code=204) def favicon_get(): return Response(status_code=204)
@rt('/favicon.ico') @rt('/favicon.ico')
def favicon_head(): return HTTPResponse(status_code=204) def favicon_head(): return Response(status_code=204)
@rt('/new_chat') @rt('/new_chat')
def get(session): session['current_chat_id'] = str(uuid.uuid4()); return RedirectResponse('/', status_code=303) def get(session): session['current_chat_id'] = str(uuid.uuid4()); return RedirectResponse('/', status_code=303)
@@ -90,7 +90,7 @@ def get(): return FileResponse(HISTORY_FILE, media_type='application/json', file
@rt('/ping') @rt('/ping')
def get(): def get():
return HTTPResponse(status_code=204) return Response(status_code=204)
if __name__ == '__main__': if __name__ == '__main__':
serve(port=5001) serve(port=5001)
+11 -5
View File
@@ -54,30 +54,35 @@ async def handle_chat_request(request, session):
log_mode = "PAYANT 👑" if mode_payant else "GRATUIT 🆓" log_mode = "PAYANT 👑" if mode_payant else "GRATUIT 🆓"
log_event("INFO", "SYSTEM", f"Requête reçue [{log_mode}]. Fichiers joints : {liste_fichiers_str}") log_event("INFO", "SYSTEM", f"Requête reçue [{log_mode}]. Fichiers joints : {liste_fichiers_str}")
# NOUVEAU PROMPT : On demande à Qwen de deviner le thème RAG
sec_prompt = f"""Tu es l'Orchestrateur système. Analyse la requête et choisis le meilleur fournisseur IA : sec_prompt = f"""Tu es l'Orchestrateur système. Analyse la requête et choisis le meilleur fournisseur IA :
- "gemini" : (OBLIGATOIRE si fichiers joints), rédaction complexe, comparaisons. - "gemini" : (OBLIGATOIRE si fichiers joints), rédaction complexe, comparaisons.
- "openrouter_chat" : requêtes générales, texte rapide, traduction. - "openrouter_chat" : requêtes générales, texte rapide, traduction.
- "openrouter_code" : programmation, Minecraft/KubeJS, scripts, mathématiques. - "openrouter_code" : programmation, Minecraft/KubeJS, scripts, mathématiques.
- "openrouter_droit" : droit français, CGV, CGU, RGPD. - "openrouter_droit" : droit français, CGV, CGU, RGPD.
- "local" : salutations basiques, questions simples. - "local" : salutations basiques, questions simples.
Renvoie STRICTEMENT un JSON : {{"intention": "Résumé...", "fournisseur": "gemini|openrouter_chat|openrouter_code|openrouter_droit|local"}} Tu dois aussi déterminer si la requête nécessite de lire la documentation interne (RAG). Thèmes : "minecraft", "droit", "serveur", "regles" ou "aucun".
Renvoie STRICTEMENT un JSON : {{"intention": "Résumé...", "fournisseur": "gemini|openrouter_chat|openrouter_code|openrouter_droit|local", "theme_rag": "minecraft|droit|serveur|regles|aucun"}}
Requête : {prompt}""" Requête : {prompt}"""
try: try:
texte_routeur, _, _, _ = interroger_local_ollama(sec_prompt, "qwen2.5-coder:3b") texte_routeur, _, _, _ = interroger_local_ollama(sec_prompt, "qwen2.5-coder:3b")
decision = nettoyer_decision_router(texte_routeur) decision = nettoyer_decision_router(texte_routeur)
except Exception: except Exception:
decision = {"intention": "Analyse de secours", "fournisseur": "openrouter_code" if "minecraft" in prompt.lower() else "openrouter_chat"} decision = {"intention": "Analyse de secours", "fournisseur": "openrouter_code" if "minecraft" in prompt.lower() else "openrouter_chat", "theme_rag": "aucun"}
intention = decision.get("intention", "Analyse") intention = decision.get("intention", "Analyse")
fournisseur = decision.get("fournisseur", "openrouter_chat").lower() fournisseur = decision.get("fournisseur", "openrouter_chat").lower()
thematique_rag = decision.get("theme_rag", "aucun").lower()
if file_names: if file_names:
fournisseur = "gemini" fournisseur = "gemini"
intention += " (Forcé Gemini via Sécurité Fichiers)" intention += " (Forcé Gemini via Sécurité Fichiers)"
thematique_rag = "minecraft" if "mine" in prompt.lower() or "kubejs" in prompt.lower() else ("droit" if "droit" in fournisseur else None) # Récupération dynamique du contexte RAG
contexte_rag = rechercher_contexte_rag(prompt, thematique=thematique_rag) contexte_rag = ""
if thematique_rag != "aucun":
contexte_rag = rechercher_contexte_rag(prompt, thematique=thematique_rag)
urls = re.findall(r"https?://[^\s]+", prompt) urls = re.findall(r"https?://[^\s]+", prompt)
mots_cles = ["cherche", "recherche", "sur internet", "sur le web", "sur le net", "trouve", "site"] mots_cles = ["cherche", "recherche", "sur internet", "sur le web", "sur le net", "trouve", "site"]
@@ -86,9 +91,10 @@ async def handle_chat_request(request, session):
web_text = extraire_contenu_urls(list(set(urls))) if urls else "" web_text = extraire_contenu_urls(list(set(urls))) if urls else ""
# Concaténation de tous les contextes
full_prompt = prompt + ("\n" + "\n".join(file_summary) if file_summary else "") full_prompt = prompt + ("\n" + "\n".join(file_summary) if file_summary else "")
if contexte_rag: if contexte_rag:
full_prompt += f"\n\n[Contexte Documentaire RAG :\n{contexte_rag}]" full_prompt += f"\n\n[Contexte Documentaire RAG (Base de connaissances interne) :\n{contexte_rag}]"
if web_text: if web_text:
full_prompt += f"\n\n[Contexte web :\n{web_text}]" full_prompt += f"\n\n[Contexte web :\n{web_text}]"
+55 -50
View File
@@ -1,63 +1,68 @@
import os import os
import re
from modules.logger import log_event from modules.logger import log_event
RAG_BASE_DIR = "rag_data" # Définition du chemin racine sécurisé sur ton RAID
THEMATIQUES = ["minecraft", "droit", "serveur", "general"] RAG_BASE_DIR = "/DATA/AppData/MULTI-IA-AETHAS38/RAG"
def initialiser_structure_rag(): # Les thématiques générales pour organiser l'apprentissage
"""Crée l'arborescence des dossiers RAG s'ils n'existent pas.""" THEMATIQUES_RAG = [
os.makedirs(RAG_BASE_DIR, exist_ok=True) "Minecraft_KubeJS",
for theme in THEMATIQUES: "Juridique_Loi_Francaise",
os.makedirs(os.path.join(RAG_BASE_DIR, theme), exist_ok=True) "Serveur_Debian",
"Bureautique_Excel",
"Regles_Aethas38"
]
def indexer_document(thematique: str, nom_fichier: str, contenu: str): def initialiser_arborescence_rag():
"""Enregistre un document dans la thématique correspondante.""" """Crée les dossiers RAG physiques sur le disque s'ils n'existent pas."""
initialiser_structure_rag()
if thematique not in THEMATIQUES:
thematique = "general"
chemin = os.path.join(RAG_BASE_DIR, thematique, nom_fichier)
try: try:
with open(chemin, "w", encoding="utf-8") as f: if not os.path.exists(RAG_BASE_DIR):
f.write(contenu) os.makedirs(RAG_BASE_DIR)
log_event("INFO", "RAG", f"Document {nom_fichier} indexé dans {thematique}.") log_event("INFO", "RAG", "Création du dossier racine RAG.")
return True
for theme in THEMATIQUES_RAG:
chemin_theme = os.path.join(RAG_BASE_DIR, theme)
if not os.path.exists(chemin_theme):
os.makedirs(chemin_theme)
log_event("INFO", "RAG", f"Création du sous-dossier thématique : {theme}")
except Exception as e: except Exception as e:
log_event("ERROR", "RAG", f"Échec d'indexation pour {nom_fichier} : {e}") log_event("ERROR", "RAG", f"Erreur lors de l'initialisation de l'arborescence : {e}")
return False
def rechercher_contexte_rag(requete: str, thematique: str = None, top_k: int = 3) -> str: def extraire_contexte_rag(thematique):
""" """
Recherche les passages les plus pertinents dans la base documentaire. Lit tous les fichiers .txt ou .md d'une thématique donnée
Filtre par thématique si spécifiée, sinon cherche globalement. et les concatène pour les injecter dans le prompt de l'IA.
""" """
initialiser_structure_rag() contexte_global = ""
dossiers_cibles = [os.path.join(RAG_BASE_DIR, thematique)] if thematique in THEMATIQUES else [os.path.join(RAG_BASE_DIR, t) for t in THEMATIQUES] chemin_theme = os.path.join(RAG_BASE_DIR, thematique)
mots_cles = set(re.findall(r'\w+', requete.lower())) if not os.path.exists(chemin_theme):
resultats = [] return contexte_global
for dossier in dossiers_cibles: try:
if not os.path.exists(dossier): for fichier in os.listdir(chemin_theme):
continue if fichier.endswith(".txt") or fichier.endswith(".md"):
for fichier in os.listdir(dossier): chemin_fichier = os.path.join(chemin_theme, fichier)
chemin = os.path.join(dossier, fichier) with open(chemin_fichier, 'r', encoding='utf-8') as f:
if os.path.isfile(chemin): contexte_global += f"\n--- Extrait de {fichier} ---\n"
try: contexte_global += f.read() + "\n"
with open(chemin, "r", encoding="utf-8", errors="ignore") as f: except Exception as e:
contenu = f.read() log_event("ERROR", "RAG", f"Erreur de lecture du contexte {thematique} : {e}")
paragraphes = contenu.split("\n\n")
for p in paragraphes:
texte_p = p.strip()
if not texte_p:
continue
mots_p = set(re.findall(r'\w+', texte_p.lower()))
score = len(mots_cles.intersection(mots_p))
if score > 0:
resultats.append((score, fichier, texte_p))
except Exception as e:
log_event("WARNING", "RAG", f"Erreur lecture {chemin} : {e}")
resultats.sort(key=lambda x: x[0], reverse=True) return contexte_global
extraits = [f"[{res[1]}] {res[2]}" for res in resultats[:top_k]]
return "\n---\n".join(extraits) def rechercher_contexte_rag(prompt, categorie="general"):
"""
Fonction appelée par le chat_handler pour récupérer le contexte.
Pour l'instant, c'est un bouchon qui empêche le crash du serveur.
"""
try:
# À terme, nous ferons le lien intelligent ici avec extraire_contexte_rag()
# et une analyse sémantique. Pour l'instant on renvoie vide pour tester.
return ""
except Exception as e:
log_event("ERROR", "RAG_MANAGER", f"Erreur lors de la recherche RAG : {e}")
return ""
# On s'assure que les dossiers existent dès l'import du module
initialiser_arborescence_rag()