MORSATIPIK / core /fable_engine.py
RDS777's picture
Update core/fable_engine.py
008716e verified
Raw
History Blame Contribute Delete
4.61 kB
# 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 None,
"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