# core/research_loop.py import re import time import uuid from typing import Dict, Any from core.memory import MemoryTier class ResearchLoop: def __init__(self, llm_engine, memory, executor): self.llm = llm_engine self.memory = memory self.executor = executor # execute_safe self.error_tree = None # sera injecté plus tard (via app.py) self.publications = [] def set_error_tree(self, error_tree): self.error_tree = error_tree async def generate_hypothesis(self, domain: str) -> str: if not self.llm: return f"L'utilisation d'un cache LRU réduit le temps d'exécution de 20% pour des appels répétés dans {domain}." prompt = ( f"Propose une hypothèse précise et testable sur l'optimisation de {domain}. " "Elle doit être formulée en une phrase et contenir un indicateur quantifiable (ex: 'réduit de X%', 'accélère de Y ms'). " "Exemple : 'L'indexation des colonnes fréquemment jointes réduit le temps de requête SQL de 30%'. " "Réponse (une phrase) :" ) try: resp = await self.llm.call( agent="research", system="Tu es un chercheur en optimisation.", user=prompt, max_tokens=80, temperature=0.3, use_cache=False ) raw = resp.content if hasattr(resp, "content") else str(resp) raw = raw.strip().split('\n')[0] raw = re.sub(r'^(Answer|Hypothesis|Réponse|Hypothèse):\s*', '', raw, flags=re.IGNORECASE) if len(raw) < 10: return f"L'optimisation de {domain} améliore les performances de 15%." return raw except Exception as e: # 🔥 Enregistrer l'échec de génération dans Harbor if self.error_tree is not None: try: self.error_tree.add_error(str(e), f"ResearchLoop generate_hypothesis domain={domain}") except Exception: pass return f"L'optimisation de {domain} améliore les performances de 15%." async def run_experiment(self, hypothesis: str) -> float: try: # 1. Générer un code de test basé sur l'hypothèse code_prompt = ( f"Écris un court code Python qui teste l'hypothèse suivante : {hypothesis}\n" "Le code doit définir une fonction `run_test()` qui retourne un score entre 0 et 1 " "(0 = échec, 1 = succès parfait). Exemple :\n" "def run_test():\n" " # ... test ...\n" " return 0.85\n" "Ne retourne que le code, sans commentaires." ) resp = await self.llm.call( agent="research", system="Tu es un ingénieur de test.", user=code_prompt, max_tokens=200, temperature=0.2, use_cache=False ) code = resp.content if hasattr(resp, "content") else str(resp) code = re.sub(r'```(?:python)?\n?', '', code).replace('```', '').strip() # 2. Exécuter le code dans le sandbox result = self.executor(code, timeout=10) if not result.get("success"): if self.error_tree is not None: try: self.error_tree.add_error( f"Exécution échouée: {result.get('error', '')}", f"Hypothèse: {hypothesis[:100]}" ) except Exception: pass return 0.0 # 3. Récupérer la sortie output = result.get("result", "").strip() match = re.search(r'(\d+\.?\d*)', output) if match: score = float(match.group(1)) score = max(0.0, min(1.0, score)) return score else: return 0.0 except Exception as e: if self.error_tree is not None: try: self.error_tree.add_error(str(e), f"ResearchLoop run_experiment") except Exception: pass return 0.0 async def publish(self, hypothesis: str, score: float): pub = { "id": str(uuid.uuid4())[:8], "hypothesis": hypothesis, "score": round(score, 3), "timestamp": time.time() } self.publications.append(pub) self.memory.ingest( f"Research: {hypothesis[:150]} (score={score:.3f})", MemoryTier.SEMANTIC, "research_loop", importance=score, confidence=score ) async def run_one_cycle(self, domain: str) -> Dict[str, Any]: hypothesis = await self.generate_hypothesis(domain) score = await self.run_experiment(hypothesis) await self.publish(hypothesis, score) return { "hypothesis": hypothesis, "score": round(score, 3), "domain": domain }