File size: 4,609 Bytes
9f28034
6eab95b
69c3767
008716e
6eab95b
69c3767
6eab95b
69c3767
 
6eab95b
69c3767
 
6eab95b
20e22df
 
 
6eab95b
008716e
 
 
 
 
6eab95b
 
d20fbec
69c3767
008716e
69c3767
 
 
 
9f28034
69c3767
 
008716e
 
 
69c3767
 
008716e
 
 
 
 
 
 
 
69c3767
3b74b7c
20e22df
69c3767
 
9f28034
 
69c3767
 
 
 
 
 
6eab95b
 
69c3767
 
 
 
 
d20fbec
 
69c3767
 
 
 
 
 
 
 
 
 
 
 
 
20e22df
69c3767
 
 
 
 
 
20e22df
 
 
 
 
 
 
 
69c3767
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# 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