# core/fable_engine.py """ FableEngine – Optimisation itérative et création automatique de skills. Utilise BlackBoxAdapter pour interagir avec le LLM. """ import asyncio import logging import time from typing import Optional, Dict, Any from core.skill_schema import Skill, SkillMetadata from core.vector_memory import VectorMemory logger = logging.getLogger("vortex.fable_engine") class FableEngine: def __init__(self, llm_engine, memory, error_tree, vector_memory: VectorMemory, blackbox=None): """ llm_engine : conservé pour compatibilité, mais non utilisé directement pour les appels LLM. blackbox : instance de BlackBoxAdapter qui expose une méthode async `query(prompt)`. """ self.llm = llm_engine self.memory = memory self.error_tree = error_tree self.vm = vector_memory self.blackbox = blackbox async def run(self, task: str, max_iter: int = 3, min_score: float = 0.85) -> Dict[str, Any]: logger.info(f"[Fable] Début optimisation – tâche : {task[:80]} (max {max_iter} itérations)") best_code = None best_score = 0.0 best_skill = None if not self.blackbox: raise RuntimeError("FableEngine a besoin d'une instance BlackBoxAdapter (blackbox).") for i in range(max_iter): prompt = self._build_prompt(task, best_code, best_score, i) # Appel via BlackBoxAdapter (même interface que le chat) response = await self.blackbox.query(prompt) # Extraire le texte : réponse peut être un objet avec .content ou une chaîne if hasattr(response, 'content'): text = response.content else: text = str(response) code = self._extract_code(text) if not code: continue score = await self._evaluate(code, task) logger.info(f"[Fable] Itération {i+1}/{max_iter} : score {score:.3f}") if score > best_score: best_score = score best_code = code if score >= min_score: skill = self._create_skill(task, best_code, best_score) best_skill = skill logger.info(f"[Fable] Skill sauvegardé : {skill.metadata.name} (score {best_score:.3f})") if score >= 0.99: break return { "skill": best_skill.to_dict() if best_skill else {}, "score": best_score, "iterations": i + 1, "code": best_code } def _build_prompt(self, task: str, current_code: Optional[str], current_score: float, iteration: int) -> str: if current_code is None: return f"Écris du code Python pour résoudre cette tâche :\n{task}\nFournis uniquement le code." return ( f"Tâche : {task}\n" f"Code actuel (score {current_score:.3f}) :\n```python\n{current_code}\n```\n" f"Améliore ce code (itération {iteration+1}). Fournis uniquement le code amélioré." ) def _extract_code(self, response: str) -> Optional[str]: if "```" in response: parts = response.split("```") for i, part in enumerate(parts): if i % 2 == 1: # contenu entre les backticks if part.startswith("python"): part = part[6:] return part.strip() return response.strip() async def _evaluate(self, code: str, task: str) -> float: """Évaluation heuristique simple (à remplacer par un vrai sandbox si besoin).""" score = 0.5 if len(code) > 50: score += 0.1 if len(code) > 100: score += 0.1 if "def " in code: score += 0.1 if "return " in code: score += 0.1 if "import " in code: score += 0.05 # Bonus mots-clés de la tâche task_words = set(task.lower().split()) code_words = set(code.lower().split()) common = task_words.intersection(code_words) score += min(0.1, len(common) * 0.02) return min(score, 1.0) def _create_skill(self, task: str, code: str, score: float) -> Skill: name = f"skill_{int(time.time())}" metadata = SkillMetadata( name=name, description=task[:100], category="auto_fable", version="1.0", tags=["fable", "optimized"] ) skill = Skill(metadata=metadata, code=code, score=score) self.vm.add_skill(skill) return skill