Spaces:
Configuration error
Configuration error
File size: 10,183 Bytes
629eccf 579d2a7 629eccf acf4949 629eccf acf4949 629eccf acf4949 629eccf 579d2a7 acf4949 579d2a7 629eccf 579d2a7 629eccf acf4949 629eccf acf4949 629eccf acf4949 629eccf 579d2a7 629eccf acf4949 629eccf acf4949 629eccf acf4949 629eccf acf4949 629eccf acf4949 579d2a7 629eccf 579d2a7 629eccf acf4949 629eccf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | # core/auto_evolution.py
"""
Auto-évolution améliorée – propositions proactives même sans lacunes.
"""
import time
import logging
import threading
import asyncio
from typing import Dict, List, Optional
from dataclasses import dataclass
logger = logging.getLogger("lucie.auto_evolution")
@dataclass
class EvolutionProposal:
id: str
type: str
description: str
code: str
score: float
status: str = "pending"
class AutoEvolution:
def __init__(self, hermes_loop, fable_engine, error_tree, vector_memory,
skill_suite_prompting, validation_queue, chat_queue=None):
self.hermes = hermes_loop
self.fable = fable_engine
self.error_tree = error_tree
self.vector_memory = vector_memory
self.skill_suite = skill_suite_prompting
self.validation_queue = validation_queue
self.chat_queue = chat_queue or []
self.proposals: Dict[str, EvolutionProposal] = {}
self.is_running = False
self.last_activity = time.time()
self.inactivity_threshold = 180 # 3 min
self.cooldown = 600 # 10 min entre deux analyses
self.last_scan_result = None
self.last_train_time = 0
self.train_interval = 3600 * 24 # 24h entre deux auto‑train
def analyze_gaps(self) -> List[Dict]:
"""Analyse les lacunes actuelles (scores, erreurs)."""
gaps = []
try:
recent = self.vector_memory.search("*", n=20, min_score=0.0)
scores = [ep.get("metadata", {}).get("score", 0.5) for ep in recent if ep.get("metadata", {}).get("score")]
if scores:
avg_score = sum(scores) / len(scores)
if avg_score < 0.7:
gaps.append({
"type": "performance",
"description": f"Score moyen bas ({avg_score:.2f}).",
"severity": "high",
"suggestion": "Optimiser les cycles Hermès/Fable"
})
if hasattr(self.error_tree, "get_recent_errors"):
errors = self.error_tree.get_recent_errors(limit=10)
error_types = {}
for e in errors:
typ = e.get("type", "unknown")
error_types[typ] = error_types.get(typ, 0) + 1
for typ, count in error_types.items():
if count >= 2:
gaps.append({
"type": "error_pattern",
"description": f"Erreur '{typ}' répétée {count} fois.",
"severity": "medium",
"suggestion": f"Créer une compétence pour gérer '{typ}'"
})
if gaps:
for gap in gaps:
keywords = gap["description"].split()
for kw in keywords[:3]:
if len(kw) > 4:
skills = self.vector_memory.search_skills(kw, n=1, min_score=0.3)
if not skills:
gaps.append({
"type": "missing_skill",
"description": f"Aucune compétence pour '{kw}'.",
"severity": "medium",
"suggestion": f"Générer une compétence '{kw}'"
})
break
except Exception as e:
logger.error(f"Erreur auto-diagnostic : {e}")
return gaps
def _find_opportunities(self) -> List[Dict]:
"""Trouve des opportunités d'amélioration même sans lacunes."""
opportunities = []
# 1. Temps depuis le dernier train
if time.time() - self.last_train_time > self.train_interval:
opportunities.append({
"type": "opportunity",
"description": "Plus de 24h sans fine‑tuning.",
"severity": "low",
"suggestion": "Lancer un nouveau cycle @train pour s'améliorer."
})
# 2. Peu d'épisodes en mémoire
stats = self.vector_memory.stats() if self.vector_memory else {}
count = stats.get("count", 0)
if count < 50:
opportunities.append({
"type": "opportunity",
"description": f"Seulement {count} épisodes en mémoire.",
"severity": "low",
"suggestion": "Explorer de nouveaux sujets pour enrichir la base."
})
# 3. Proposer une compétence générique
opportunities.append({
"type": "opportunity",
"description": "Générer une compétence d'optimisation de code.",
"severity": "low",
"suggestion": "Créer une compétence pour améliorer les performances."
})
return opportunities
async def generate_skill(self, task: str) -> Optional[Dict]:
try:
result = await self.fable.run(
task,
max_iter=3,
feedback="Génère une compétence Python autonome, avec documentation et tests."
)
if result.get("best_score", 0) >= 0.8:
code = self._extract_code(result.get("best_result", ""))
return {
"description": task,
"code": code,
"score": result["best_score"],
"type": "new_skill"
}
except Exception as e:
logger.error(f"Erreur génération skill : {e}")
return None
def _extract_code(self, text: str) -> str:
import re
match = re.search(r'```python\s*(.*?)\s*```', text, re.DOTALL)
if match:
return match.group(1)
return text
async def propose_skill(self, task: str) -> Optional[EvolutionProposal]:
skill_data = await self.generate_skill(task)
if not skill_data:
return None
proposal = EvolutionProposal(
id=f"prop_{int(time.time())}",
type="new_skill",
description=skill_data["description"],
code=skill_data["code"],
score=skill_data["score"]
)
self.proposals[proposal.id] = proposal
return proposal
def start_consciousness(self):
if self.is_running:
return
self.is_running = True
threading.Thread(target=self._consciousness_loop, daemon=True).start()
logger.info("🧠 Mode conscience de soi activé.")
def _consciousness_loop(self):
while self.is_running:
time.sleep(60) # Vérification toutes les minutes
if not self._is_inactive():
continue
if self.last_scan_result and time.time() - self.last_scan_result < self.cooldown:
continue
logger.info("🔍 Silence détecté – analyse en cours...")
gaps = self.analyze_gaps()
self.last_scan_result = time.time()
if gaps:
# Priorité aux lacunes
gap = max(gaps, key=lambda x: {"high": 3, "medium": 2, "low": 1}.get(x["severity"], 0))
proposal = asyncio.run(self.propose_skill(gap["suggestion"]))
else:
# Pas de lacune → chercher des opportunités
opportunities = self._find_opportunities()
if opportunities:
# Prendre la première opportunité
opp = opportunities[0]
proposal = asyncio.run(self.propose_skill(opp["suggestion"]))
else:
proposal = None
if proposal:
self._notify_user(proposal)
# Mettre à jour le timestamp du dernier train si c'était un @train
if "train" in proposal.description.lower():
self.last_train_time = time.time()
def _is_inactive(self) -> bool:
return (time.time() - self.last_activity) > self.inactivity_threshold
def _notify_user(self, proposal: EvolutionProposal):
msg = (
f"🧠 **LUCIE propose une amélioration**\n\n"
f"**Type** : {proposal.type}\n"
f"**Description** : {proposal.description}\n"
f"**Score** : {proposal.score:.2f}\n\n"
f"Code généré :\n```python\n{proposal.code[:500]}\n```\n\n"
f"✅ Valider : `@approve {proposal.id}`\n"
f"❌ Refuser : `@reject {proposal.id}`"
)
if self.chat_queue is not None:
self.chat_queue.append({"role": "assistant", "content": msg})
def approve_proposal(self, proposal_id: str) -> bool:
prop = self.proposals.get(proposal_id)
if not prop or prop.status != "pending":
return False
try:
from core.skill_schema import Skill, SkillMetadata
skill = Skill(
metadata=SkillMetadata(
name=f"auto_{int(time.time())}",
description=prop.description[:100],
category="auto_generated"
),
code=prop.code,
score=prop.score
)
self.vector_memory.add_skill(skill)
import os
skill_dir = f"/data/skills/{skill.metadata.name}"
os.makedirs(skill_dir, exist_ok=True)
with open(f"{skill_dir}/__init__.py", "w") as f:
f.write(prop.code)
prop.status = "integrated"
logger.info(f"✅ Compétence {skill.metadata.name} intégrée.")
return True
except Exception as e:
logger.error(f"Erreur intégration skill : {e}")
return False
def reject_proposal(self, proposal_id: str) -> bool:
prop = self.proposals.get(proposal_id)
if not prop or prop.status != "pending":
return False
prop.status = "rejected"
logger.info(f"❌ Proposition {proposal_id} rejetée.")
return True |