atypique-api / core /branch_executor.py
RDS777's picture
Backend v3.0 complet
8e3a425
Raw
History Blame Contribute Delete
4.55 kB
# core/branch_executor.py
"""
Exécuteur de branches – isole chaque hypothèse dans un sandbox.
Si le code est vide, évalue la description sémantiquement.
"""
import logging
import time
import asyncio
from typing import Dict, Any, Optional
from concurrent.futures import ThreadPoolExecutor
from sandbox.secure_executor import execute_safe
from core.hypothesis_tree import HypothesisTree, Branch, BranchStatus
logger = logging.getLogger("vortex.branch_executor")
class BranchExecutor:
"""
Exécute une branche (hypothèse) dans un sandbox isolé.
Retourne le résultat, le score et les éventuelles erreurs.
"""
def __init__(self, timeout: int = 30):
self.timeout = timeout
self.executor = ThreadPoolExecutor(max_workers=4)
async def execute_branch(self, branch: Branch, tree: HypothesisTree) -> Dict:
"""
Exécute une branche et met à jour l'arbre.
Retourne un dict avec le résultat.
"""
branch_id = branch.id
tree.update_branch(branch_id, BranchStatus.RUNNING)
# Si le code est vide, évaluer uniquement la description
if not branch.code.strip():
score = self._evaluate_hypothesis_text(branch.hypothesis, tree.task)
tree.update_branch(
branch_id,
BranchStatus.SUCCESS,
score=score,
result={"evaluation": "descriptive", "score": score},
execution_time=0.0
)
return {"branch_id": branch_id, "success": True, "score": score, "result": "Évalué par description"}
# Sinon, exécuter le code dans le sandbox
t0 = time.time()
try:
result = await asyncio.get_event_loop().run_in_executor(
self.executor,
execute_safe,
branch.code,
self.timeout
)
execution_time = time.time() - t0
# Évaluation du résultat
score = self._evaluate_result(result, branch.hypothesis)
tree.update_branch(
branch_id,
BranchStatus.SUCCESS,
score=score,
result=result,
execution_time=execution_time
)
return {"branch_id": branch_id, "success": True, "score": score, "result": result}
except Exception as e:
execution_time = time.time() - t0
error_msg = str(e)
tree.update_branch(
branch_id,
BranchStatus.FAILED,
score=0.0,
error=error_msg,
execution_time=execution_time
)
return {"branch_id": branch_id, "success": False, "error": error_msg}
def _evaluate_hypothesis_text(self, text: str, task: str) -> float:
"""
Évalue une hypothèse basée sur le texte seul (pas de code exécuté).
Critères : pertinence, couverture des mots-clés, longueur.
"""
score = 0.3 # base
text_lower = text.lower()
task_lower = task.lower()
# Mots-clés importants pour la tâche actuelle (peut être adapté)
keywords = [
"compétences", "skill", "json", "visualisation", "arbre", "cerveau",
"three", "vis", "chromadb", "injection", "prompt", "structure",
"validation", "pydantic", "schema", "plugin", "module"
]
matched = sum(1 for kw in keywords if kw in text_lower)
score += min(0.4, matched * 0.08)
# Longueur (bonus si > 200 caractères)
if len(text) > 200:
score += 0.15
if len(text) > 500:
score += 0.15
# Pertinence par rapport à la tâche (mots communs)
common = set(task_lower.split()) & set(text_lower.split())
if common:
score += min(0.2, len(common) * 0.05)
return min(1.0, round(score, 3))
def _evaluate_result(self, result: Any, hypothesis: str) -> float:
"""
Heuristique d'évaluation d'un résultat (0‑1) lorsque le code a été exécuté.
"""
if result is None:
return 0.0
if isinstance(result, dict):
if "result" in result or "output" in result or "success" in result:
if result.get("success", False):
return 0.8
return 0.3
if isinstance(result, str):
if len(result) > 100:
return 0.7
return 0.4
return 0.5