Spaces:
Configuration error
Configuration error
| # core/auto_knowledge.py | |
| """ | |
| Moteur d'auto‑connaissance — collecte et distillation autonome des données. | |
| Interroge VORTEX sur des sujets prédéfinis, indexe les réponses dans le RAG, | |
| synthétise des connaissances de haut niveau et suit la progression. | |
| """ | |
| import asyncio | |
| import json | |
| import logging | |
| import time | |
| from pathlib import Path | |
| from typing import List, Dict, Any, Optional | |
| from core.kernel import KERNEL | |
| from core.memory import MemoryTier | |
| log = logging.getLogger("vortex.auto_knowledge") | |
| # Sujets de base pour amorcer l'arbre de connaissance | |
| BASE_TOPICS = [ | |
| "optimisation de code Python", | |
| "algorithmes de tri (fusion, rapide, insertion)", | |
| "gestion de la mémoire en Python", | |
| "parallélisation et asynchronisme", | |
| "design patterns courants", | |
| "tests unitaires avec pytest", | |
| "API REST avec FastAPI", | |
| "bases de données SQL et NoSQL", | |
| "déploiement sur HF Spaces", | |
| "sécurité des applications web" | |
| ] | |
| class AutoKnowledgeEngine: | |
| """ | |
| Gère les cycles de collecte et distillation automatique. | |
| """ | |
| def __init__(self, llm_engine, memory, vector_memory, error_tree, | |
| progress_file: str = "data/knowledge_progress.json"): | |
| self.llm = llm_engine | |
| self.memory = memory | |
| self.vector_memory = vector_memory | |
| self.error_tree = error_tree | |
| self.progress_file = Path(progress_file) | |
| self.progress_file.parent.mkdir(parents=True, exist_ok=True) | |
| self.topics = [] | |
| self.current_topic_index = 0 | |
| self.responses = [] | |
| self.distilled = [] | |
| self._load_progress() | |
| def _load_progress(self): | |
| if self.progress_file.exists(): | |
| try: | |
| with open(self.progress_file, "r") as f: | |
| data = json.load(f) | |
| self.topics = data.get("topics", BASE_TOPICS[:]) | |
| self.current_topic_index = data.get("current_topic_index", 0) | |
| self.responses = data.get("responses", []) | |
| self.distilled = data.get("distilled", []) | |
| log.info(f"📂 Progression chargée : {len(self.topics)} sujets, {len(self.responses)} réponses, {len(self.distilled)} distillats.") | |
| except Exception as e: | |
| log.warning(f"Échec chargement progression : {e}") | |
| self.topics = BASE_TOPICS[:] | |
| self.current_topic_index = 0 | |
| self.responses = [] | |
| self.distilled = [] | |
| else: | |
| self.topics = BASE_TOPICS[:] | |
| self.current_topic_index = 0 | |
| self.responses = [] | |
| self.distilled = [] | |
| self._save_progress() | |
| def _save_progress(self): | |
| try: | |
| with open(self.progress_file, "w") as f: | |
| json.dump({ | |
| "topics": self.topics, | |
| "current_topic_index": self.current_topic_index, | |
| "responses": self.responses, | |
| "distilled": self.distilled | |
| }, f, indent=2) | |
| except Exception as e: | |
| log.error(f"Échec sauvegarde progression : {e}") | |
| def _get_topic(self) -> Optional[str]: | |
| if self.current_topic_index < len(self.topics): | |
| topic = self.topics[self.current_topic_index] | |
| self.current_topic_index += 1 | |
| return topic | |
| return None | |
| def _add_topic(self, topic: str): | |
| if topic not in self.topics: | |
| self.topics.append(topic) | |
| self._save_progress() | |
| async def _ask_question(self, topic: str) -> Dict[str, Any]: | |
| """Interroge VORTEX sur un sujet via Hermès (ou BlackBox).""" | |
| task = f"Explique en détail le sujet suivant, en donnant des exemples concrets et des bonnes pratiques : {topic}" | |
| try: | |
| # On utilise BlackBox pour plus de rapidité (pas besoin des 7 étapes) | |
| from core.blackbox_adapter import BlackBoxAdapter | |
| blackbox = BlackBoxAdapter(self.llm) | |
| resp = await blackbox.query(task) | |
| content = resp if isinstance(resp, str) else resp.content | |
| score = 0.8 # estimation | |
| return { | |
| "topic": topic, | |
| "content": content, | |
| "score": score, | |
| "timestamp": time.time(), | |
| "source": "auto_knowledge" | |
| } | |
| except Exception as e: | |
| log.error(f"Erreur lors de la question '{topic}': {e}") | |
| return { | |
| "topic": topic, | |
| "content": f"[Erreur] {e}", | |
| "score": 0.0, | |
| "timestamp": time.time(), | |
| "source": "auto_knowledge_error" | |
| } | |
| async def _distill_responses(self) -> str: | |
| """Synthétise les réponses collectées en connaissances de haut niveau.""" | |
| if len(self.responses) < 3: | |
| return "" | |
| # Prendre les 10 dernières réponses | |
| recent = self.responses[-10:] | |
| # Construire un prompt pour la distillation | |
| context = "\n\n".join([f"Sujet: {r['topic']}\n{r['content'][:500]}" for r in recent]) | |
| prompt = f""" | |
| Voici plusieurs réponses sur différents sujets liés à l'ingénierie logicielle et à l'IA. | |
| Synthétise ces informations en 5 points clés de haut niveau, en les reliant entre eux pour former une connaissance unifiée. | |
| RÉPONSES: | |
| {context} | |
| RÉSUMÉ DES CONNAISSANCES (5 points, format concis): | |
| """ | |
| try: | |
| resp = await self.llm.call( | |
| agent="auto_knowledge", | |
| system="Tu es un expert en synthèse de connaissances.", | |
| user=prompt, | |
| max_tokens=300, | |
| temperature=0.3 | |
| ) | |
| content = resp.content if hasattr(resp, 'content') else str(resp) | |
| return content.strip() | |
| except Exception as e: | |
| log.error(f"Erreur lors de la distillation : {e}") | |
| return "" | |
| async def run_cycle(self, max_questions: int = 3, force_distill: bool = False) -> Dict[str, Any]: | |
| """ | |
| Exécute un cycle : collecte de questions, indexation, distillation éventuelle. | |
| Retourne un rapport de progression. | |
| """ | |
| stats = { | |
| "questions_asked": 0, | |
| "responses_added": 0, | |
| "distilled_added": 0, | |
| "total_topics": len(self.topics), | |
| "remaining_topics": len(self.topics) - self.current_topic_index, | |
| "progress_percent": 0 | |
| } | |
| # 1. Collecte | |
| for _ in range(max_questions): | |
| topic = self._get_topic() | |
| if topic is None: | |
| # Si plus de sujets, ajouter des sujets dérivés (à partir des erreurs ou des skills) | |
| # Ici on peut faire une pause, mais on ne crée pas de nouveaux sujets pour l'instant. | |
| break | |
| log.info(f"📚 AutoKnowledge: question sur '{topic}'") | |
| response = await self._ask_question(topic) | |
| self.responses.append(response) | |
| stats["questions_asked"] += 1 | |
| # Indexer dans le RAG | |
| if self.vector_memory and response["score"] > 0.3: | |
| text = f"Connaissance auto-générée : {response['topic']}\n{response['content']}" | |
| metadata = { | |
| "source": "auto_knowledge", | |
| "topic": response["topic"], | |
| "score": response["score"], | |
| "timestamp": response["timestamp"] | |
| } | |
| self.vector_memory.add_episode(text, metadata=metadata) | |
| stats["responses_added"] += 1 | |
| # Également stocker dans la mémoire hiérarchique (SQLite) | |
| try: | |
| self.memory.ingest( | |
| f"AutoKnowledge: {response['topic']}", | |
| MemoryTier.SEMANTIC, | |
| "auto_knowledge", | |
| importance=response["score"], | |
| confidence=response["score"] | |
| ) | |
| except Exception as e: | |
| log.warning(f"Erreur ingestion mémoire : {e}") | |
| # 2. Distillation si on a assez de réponses ou si demandé | |
| if len(self.responses) >= 5 or force_distill: | |
| distilled_content = await self._distill_responses() | |
| if distilled_content and len(distilled_content) > 20: | |
| # Stocker le distillat comme épisode spécial | |
| if self.vector_memory: | |
| self.vector_memory.add_episode( | |
| f"Distillat de connaissances : {distilled_content}", | |
| metadata={ | |
| "source": "distilled_knowledge", | |
| "timestamp": time.time(), | |
| "type": "distilled" | |
| } | |
| ) | |
| self.distilled.append({ | |
| "content": distilled_content, | |
| "timestamp": time.time() | |
| }) | |
| stats["distilled_added"] = 1 | |
| self._save_progress() | |
| log.info(f"🧠 Distillation ajoutée : {len(distilled_content)} caractères") | |
| # Calcul du pourcentage de progression (basé sur le nombre de sujets traités) | |
| if self.topics: | |
| done = self.current_topic_index | |
| total = len(self.topics) | |
| stats["progress_percent"] = min(100, round(done / total * 100)) | |
| else: | |
| stats["progress_percent"] = 100 | |
| # Sauvegarde | |
| self._save_progress() | |
| # Mise à jour du fichier de progression avec les notifications | |
| notification = { | |
| "timestamp": time.time(), | |
| "action": "cycle_completed", | |
| "stats": stats, | |
| "last_question": self.responses[-1]["topic"] if self.responses else None, | |
| "last_distill": self.distilled[-1]["content"][:100] + "..." if self.distilled else None | |
| } | |
| try: | |
| with open(DATA_DIR / "knowledge_notifications.json", "w") as f: | |
| json.dump(notification, f, indent=2) | |
| except Exception: | |
| pass | |
| return stats | |
| # Pour intégration dans le scheduler | |
| async def run_auto_knowledge_job(llm_engine, memory, vector_memory, error_tree): | |
| engine = AutoKnowledgeEngine(llm_engine, memory, vector_memory, error_tree) | |
| stats = await engine.run_cycle(max_questions=3) | |
| log.info(f"📊 AutoKnowledge cycle terminé : {stats}") | |
| return stats |