File size: 5,240 Bytes
8e3a425
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# 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
        }