File size: 4,677 Bytes
a1506e1
8e3a425
a1506e1
8e3a425
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a1506e1
 
8e3a425
 
 
 
 
 
 
 
a1506e1
8e3a425
 
 
 
 
 
 
 
 
a1506e1
8e3a425
a1506e1
8e3a425
 
 
 
 
 
 
 
 
a1506e1
8e3a425
 
 
 
 
 
a1506e1
8e3a425
 
 
 
a1506e1
8e3a425
 
 
 
 
 
 
a1506e1
8e3a425
a1506e1
8e3a425
a1506e1
8e3a425
 
 
 
 
 
 
 
 
 
 
 
 
a1506e1
8e3a425
 
 
 
 
 
a1506e1
8e3a425
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a1506e1
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
# 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