diff --git a/README.md b/README.md new file mode 100644 index 0000000..7fff06f --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# 🛠️ Ops-GPT : Orchestrateur SRE (Modèle GPT-OSS:20B) + +**Ops-GPT** est un assistant SRE spécialisé dans l'écosystème **RHEL / CentOS / Rocky Linux**. Il utilise les données temps réel pour diagnostiquer les pannes de service et les conflits de ressources. + +## 📌 Architecture de Diagnostic + +L'intelligence du système repose sur la corrélation de cinq flux de données via le protocole **MCP (Model Context Protocol)** : + +1. **Inventaire (RPM)** : Identification des versions OS et paquets. +2. **Performance (VictoriaMetrics)** : Monitoring (CPU, RAM, I/O, Network). +3. **Réseau & Sockets (`ss -ntulop`)** : État réel des ports et processus à l'écoute. +4. **Configuration Live par Service** : Lecture ciblée des fichiers de configuration (`/etc/`). +5. **Mémoire Collective (ChromaDB)** : Base vectorielle des incidents passés. + +--- + +## 🚀 Spécifications des Outils MCP + +### 📂 `get_live_config(service_name)` +* **Fonction** : Récupère intelligemment l'ensemble des fichiers de configuration liés à un service spécifique. +* **Comportement** : Si `service_name="nginx"`, l'outil renvoie `/etc/nginx/nginx.conf` et les fichiers dans `/etc/nginx/conf.d/`. +* **Format de sortie** : + ```text + SERVICE: [service_name] + FILES_COLLECTED: /etc/[service]/... + --- FILE: [path] --- + [Content] + ``` + +### 🔌 `get_network_sockets` +* **Fonction** : Fournit la sortie brute de la commande `ss -ntulop`. +* **Utilité** : Permet au LLM de corréler les ports configurés avec les processus réellement en cours d'exécution (PID, Users, Inodes). +* **Format de sortie** : + ```text + NETWORK_STATE (ss -ntulop): + Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process + tcp LISTEN 0 128 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1234,fd=6)) + ``` + + +### 📊 `get_system_metrics` (VictoriaMetrics) +* **Fonction** : Extraction des statistiques descriptives (Load, CPU, I/O Wait). + +--- + +## 🛡️ Règles de Gouvernance SRE + +### 1. Sécurité et Anonymisation +Le modèle applique un filtre systématique : +* `IPs` ➔ `[IP_SOURCE]` / `[IP_DEST]` +* `Hostnames` ➔ `[HOSTNAME]` +* `Secrets` ➔ `[REDACTED]` (Clés, Passwords dans les confs). + +### 2. Stratégie Linguistique +* **Interface Client** : Français. +* **Backend & Archivage** : Anglais Technique. + +### 3. Philosophie d'Intervention +* **Périmètre** : OS & Middleware (RHEL-based). +* **Priorité** : Restauration immédiate (MTTR) via Workarounds. +* **Données** : Zéro perte de données tolérée. + +--- + +## 📋 Exemple de Corrélation Avancée + +1. **Input** : "Impossible de démarrer Apache sur [HOSTNAME]." +2. **Action 1 (`get_network_sockets`)** : Le modèle voit que le port 80 est déjà "LISTEN" par le PID 567 (nginx). +3. **Action 2 (`get_live_config("apache")`)** : Le modèle confirme que le `Listen 80` est configuré. +4. **Action 3 (`get_rpm_inventory`)** : Vérifie si une mise à jour récente a installé `nginx` par erreur (dépendance). +5. **Synthèse** : "Conflit détecté : Nginx occupe déjà le port 80. Souhaitez-vous arrêter Nginx ou changer le port d'Apache ?" +6. **Archivage** : Enregistre le conflit de port dans ChromaDB. diff --git a/chroma-mcp-sse/main.py b/chroma-mcp-sse/main.py index 75071aa..8507a6f 100644 --- a/chroma-mcp-sse/main.py +++ b/chroma-mcp-sse/main.py @@ -1,42 +1,102 @@ from mcp.server.fastmcp import FastMCP import chromadb import os +import uuid +from datetime import datetime -# On définit l'hôte sur 0.0.0.0 et le port sur 8080 pour le container Docker -mcp = FastMCP("Chroma-Server", host="0.0.0.0", port=8080) +# 1. Initialisation de FastMCP +# On force l'hôte sur 0.0.0.0 pour Docker et le port sur 8080 +mcp = FastMCP("Chroma-Admin-Assistant", host="0.0.0.0", port=8080) -# Initialisation du client ChromaDB -CHROMA_URL = os.getenv("CHROMA_URL", "http://chroma-db:8000") -client = chromadb.HttpClient(host="chroma-db", port=8000) +# 2. Connexion au client ChromaDB +# On utilise l'URL interne au réseau Docker définie dans votre docker-compose +CHROMA_HOST = os.getenv("CHROMA_HOST", "chroma-db") +client = chromadb.HttpClient(host=CHROMA_HOST, port=8000) + +# Nom de la collection dédiée aux incidents +COLLECTION_NAME = "incident_history" @mcp.tool() async def find_incident_solution(query_error: str, service: str = None): """ Recherche des incidents passés similaires et retourne les solutions appliquées. - L'utilisateur peut décrire l'erreur (ex: 'serveur lent') ou donner un code d'erreur. + L'utilisateur peut décrire l'erreur ou donner un extrait de log technique. """ - collection = client.get_collection(name="incident_history") - - # On cherche les 2 incidents les plus proches pour ne pas saturer le LLM - results = collection.query( - query_texts=[query_error], - n_results=2, - where={"service": service} if service else None - ) - - if not results["documents"][0]: - return "Aucun incident similaire répertorié dans la base de connaissances." + try: + collection = client.get_or_create_collection(name=COLLECTION_NAME) + + # Recherche vectorielle des 2 incidents les plus proches + results = collection.query( + query_texts=[query_error], + n_results=2, + where={"service": service} if service else None + ) + + if not results["documents"] or not results["documents"][0]: + return "Aucun incident similaire répertorié pour le moment." - response = "Voici les incidents similaires trouvés :\n" - for i, doc in enumerate(results["documents"][0]): - meta = results["metadatas"][0][i] - response += f"\n--- INCIDENT {meta.get('incident_id', 'Inconnu')} ---" - response += f"\nLOGS : {doc}" - response += f"\nSOLUTION : {meta.get('solution_applied', 'Non renseignée')}\n" - - return response + response = "🔍 Voici les précédents incidents similaires trouvés :\n" + for i in range(len(results["documents"][0])): + doc = results["documents"][0][i] + meta = results["metadatas"][0][i] + response += f"\n--- INCIDENT {meta.get('incident_id')} ---" + response += f"\nLOGS : {doc}" + response += f"\nSOLUTION : {meta.get('solution_applied')}\n" + + return response + except Exception as e: + return f"Erreur lors de la recherche : {str(e)}" + +@mcp.tool() +async def add_incident_report(service: str, incident_description: str, solution_applied: str): + """ + Ajoute un nouvel incident et sa résolution dans la base de connaissances. + À utiliser quand une solution technique a été validée par un administrateur. + """ + try: + collection = client.get_or_create_collection(name=COLLECTION_NAME) + + uid = f"INC-{uuid.uuid4().hex[:8].upper()}" + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + collection.add( + documents=[incident_description], + metadatas=[{ + "service": service, + "solution_applied": solution_applied, + "incident_id": uid, + "timestamp": timestamp + }], + ids=[uid] + ) + + return f"✅ Succès : L'incident {uid} a été archivé pour le service {service}." + except Exception as e: + return f"Erreur lors de l'ajout : {str(e)}" + +@mcp.tool() +async def list_all_incidents(limit: int = 15): + """ + Liste les incidents enregistrés dans la base. + Permet d'avoir une vue d'ensemble des connaissances actuelles. + """ + try: + collection = client.get_or_create_collection(name=COLLECTION_NAME) + results = collection.get(limit=limit) + + if not results["ids"]: + return "La base de connaissances est vide." + + summary = f"📋 Liste des {len(results['ids'])} dernières entrées :\n" + for i in range(len(results["ids"])): + meta = results["metadatas"][i] + summary += f"\n- [{meta.get('timestamp')}] {meta.get('incident_id')} | Service: {meta.get('service')}" + summary += f"\n Extrait: {results['documents'][i][:75]}..." + + return summary + except Exception as e: + return f"Erreur lors de la lecture : {str(e)}" if __name__ == "__main__": - # On lance le serveur en mode SSE (Server-Sent Events) - # C'est ce que LM Studio et la plupart des clients attendent + # Lancement du serveur en mode SSE mcp.run(transport="sse")