def extraire_code(reponse):
m = re.search(r"```(?:python)?\s*(.*?)```", reponse or "", re.DOTALL)
return m.group(1).strip() if m else (reponse or "").strip()
import subprocess, sys, json
# --- Bac a sable : le code genere par le modele s'execute en SOUS-PROCESSUS confine ---
_NOMS_BUILTINS_SURS = (
"abs", "all", "any", "bool", "chr", "dict", "divmod", "enumerate", "filter",
"float", "format", "frozenset", "hasattr", "hash", "int", "isinstance", "len",
"list", "map", "max", "min", "next", "oct", "ord", "pow", "print", "range",
"repr", "reversed", "round", "set", "slice", "sorted", "str", "sum", "tuple", "zip",
"Exception", "ValueError", "TypeError", "IndexError", "KeyError",
"ZeroDivisionError", "ArithmeticError", "AttributeError", "RuntimeError",
"StopIteration", "NotImplementedError", "OverflowError", "RecursionError",
"AssertionError", "OSError",
)
_MODULES_SURS = ("math", "itertools", "functools", "collections", "string",
"heapq", "re", "statistics", "random", "datetime")
def _runner_source(code, tests):
"""Source du processus ENFANT. Le runner lui-meme tourne en confiance pleine ;
seule la portion exec(code, ...) voit le dictionnaire de builtins restreint.
Le resultat transite par une ligne __RESULT__ sur stdout (les print du code
genere ne polluent pas le canal)."""
return (
"import json, builtins as _b, importlib\n"
"_MODULES_SURS = " + repr(_MODULES_SURS) + "\n"
"def _import_sur(nom, *a, **k):\n"
" if nom not in _MODULES_SURS:\n"
" raise ImportError('module non autorise dans le bac a sable : ' + str(nom))\n"
" return importlib.import_module(nom)\n"
"_surs = {n: getattr(_b, n) for n in " + repr(_NOMS_BUILTINS_SURS) + "}\n"
"_surs['__import__'] = _import_sur\n"
"ns = {'__builtins__': _surs}\n"
"res = {'passes': 0, 'fails': [], 'erreur': None}\n"
"try:\n"
" exec(" + repr(code) + ", ns)\n"
"except BaseException as e:\n"
" res['erreur'] = type(e).__name__ + ': ' + str(e)\n"
"if res['erreur'] is None:\n"
" for _t in " + repr(list(tests)) + ":\n"
" try:\n"
" if eval(_t, ns):\n"
" res['passes'] += 1\n"
" else:\n"
" res['fails'].append(_t)\n"
" except BaseException:\n"
" res['fails'].append(_t)\n"
"print('__RESULT__' + json.dumps(res))\n"
)
def executer_tests_detaille(code, probleme, timeout=5):
"""Execute `code` genere par le modele dans un sous-processus confine.
Ce qui est reellement garanti (et demontre sur sortie committee plus bas) :
- builtins restreints : ni open, ni eval, ni exec, ni __import__ libre ;
- imports limites a une liste blanche de modules de calcul ;
- borne de temps REELLE : subprocess.run(timeout=...) tue le processus enfant,
y compris sous Windows (ou signal.alarm n'existe pas).
Ce qui n'est PAS garanti : ce n'est pas une frontiere de securite dure — c'est une
ceinture de securite pedagogique pour du code de benchmark ; du code reellement
non fiable demande un conteneur jetable ou un interpreteur WASM.
Renvoie (passes, total, fails, erreur)."""
tests = probleme["tests"]
total = len(tests)
if not code.strip():
return 0, total, [], "code vide"
try:
proc = subprocess.run(
[sys.executable, "-c", _runner_source(code, tests)],
capture_output=True, text=True, timeout=timeout,
stdin=subprocess.DEVNULL)
except subprocess.TimeoutExpired:
return 0, total, list(tests), f"timeout {timeout}s : execution interrompue de force"
for ligne in proc.stdout.splitlines():
if ligne.startswith("__RESULT__"):
res = json.loads(ligne[len("__RESULT__"):])
if res["erreur"] is not None:
return 0, total, list(tests), "echec de chargement intercepte : " + res["erreur"]
return res["passes"], total, res["fails"], None
return 0, total, list(tests), "pas de resultat (sortie anormale du bac a sable)"
def executer_tests(code, probleme, timeout=5):
"""(API inchangee) Renvoie (nb_tests_passes, nb_tests_total, tests_echoues).
Confinement effectif : voir executer_tests_detaille (NB-13 pour la demonstration).
Le parametre timeout est APPLIQUE (subprocess.run(timeout=...))."""
passes, total, fails, err = executer_tests_detaille(code, probleme, timeout=timeout)
if err:
return passes, total, [err]
return passes, total, fails
def _gen_code(spec, model=FAST_MODEL, temperature=0.0, max_tokens=2000):
resp = chat(spec + "\n\nReponds UNIQUEMENT avec le code Python dans un bloc ```python```.",
model=model, temperature=temperature, max_tokens=max_tokens)
return extraire_code(resp)
# Banque : deux problemes partageant un pitfall (logique modulo concatenative).
PROBLEMES = {
"fizzbuzz": {
"spec": "Ecris `fizzbuzz(n)` renvoyant une liste de longueur n (1-indexee) ou l'element i "
"vaut 'Fizz' si i multiple de 3, 'Buzz' si multiple de 5, 'FizzBuzz' si multiple "
"des deux, sinon i.",
"tests": ["fizzbuzz(5)==[1,2,'Fizz',4,'Buzz']", "fizzbuzz(15)[-1]=='FizzBuzz'"]},
"fizzbuzz_etendu": {
"spec": "Ecris `fizzbuzz_etendu(n)` renvoyant une liste de longueur n (1-indexee) ou "
"l'element i vaut 'Fizz' si i multiple de 3, 'Buzz' si 5, 'Jazz' si 7, en "
"CONCATENANT les regles qui s'appliquent (ex: 105 -> 'FizzBuzzJazz'), sinon i.",
"tests": ["fizzbuzz_etendu(3)==[1,2,'Fizz']", "fizzbuzz_etendu(35)[-1]=='BuzzJazz'",
"fizzbuzz_etendu(21)[-1]=='FizzJazz'"]},
}
def reflexion_sans_memoire(probleme, model=FAST_MODEL, iterations=3):
"""Reflexion NB-12 : boucle generation -> tests -> critique -> regeneration.
Le feedback est IN-MEMORY (variable locale), PERDU a la sortie. Renvoie (passes, total, lecon)."""
total = len(probleme["tests"])
meilleur, feedback, code, lecon = 0, "", "", ""
for it in range(iterations):
if feedback:
prompt = (probleme["spec"] + "\n\nCode precedent ECHEC :\n```python\n" + code +
"\n```\nTests echouant :\n" + feedback +
"\nCorrige. Reponds UNIQUEMENT avec le code dans ```python```.")
else:
prompt = (probleme["spec"] +
"\n\nReponds UNIQUEMENT avec le code dans ```python```.")
code = _gen_code(prompt, model=model)
passes, _, fails = executer_tests(code, probleme)
meilleur = max(meilleur, passes)
if passes >= total:
lecon = f"Resolu en {it+1} iteration(s)."
break
feedback = "\n".join(fails)
# Synthese d'un diagnostic court (la "lecon") -- c'est ce qu'on va memoriser
lecon = feedback[:200]
return meilleur, total, lecon
print("Moteur Reflexion (memoire in-memory) recharge depuis NB-12/NB-13.")