#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ patch_facilitator_c1.py — Chantier C1 (Encoder structuré) ========================================================= Patch strict de facilitator_v9.py : P1. Config : ajoute ENCODER_MODE (.env, défaut 'agent' — rien ne change tant que ENCODER_MODE=schema n'est pas posé). P2. Insère run_encoder_schema() avant la section FREE DESIGNER (même contrat de retour que run_encoder, fallback agent intégré). P3. Dispatch dans run_full_pipeline_pass (pipeline + révisions). P4. Dispatch dans le flux standard (fallback agent en mode express : le plan Narrator n'y est pas annoté SLIDE N — layout). Usage (dossier du pipeline, single-line) : python3 patch_facilitator_c1.py Vérifie chaque ancre (présence + unicité), écrit facilitator_v9.py.bak, compile le résultat. Idempotent : refuse de patcher deux fois. """ import py_compile import shutil import sys from pathlib import Path TARGET = Path("facilitator_v9.py") FUNC = ''' def run_encoder_schema(plan: str, layouts: dict, proj: "Project"): """Encoder structuré (chantier C1) : chat/completions + json_schema strict Mistral, slide par slide. Même contrat de retour que run_encoder : (yaml_str, data, path, attempts) — attempts = nb de slides en échec. Fallback automatique sur l'Encoder agent si le module manque ou si aucune slide n'est encodée.""" section("ÉTAPE 3 — THE ENCODER (structured outputs)") try: import encoder_schema as enc except ImportError as e: warn(f"encoder_schema.py indisponible ({e}) — bascule mode agent.") return run_encoder(AgentSession(ENCODER_ID), plan, layouts, proj) try: import schemas as sch for issue in (sch.verify_against_layouts(layouts) if layouts else []): warn(f"Schéma vs layouts_v2 : {issue}") except ImportError: warn("schemas.py absent — vérification de cohérence sautée.") info(f"Encodage slide par slide ({enc.DEFAULT_MODEL}, temp 0)...") data, usage, errors = enc.encode_plan(plan, API_KEY, progress=info) nb = len(data.get("slides", [])) ok(f"{nb} slides encodées — tokens : {usage.get('total_tokens', 0)} " f"(prompt {usage.get('prompt_tokens', 0)} / " f"completion {usage.get('completion_tokens', 0)})") for e in errors: warn(e) if not nb: warn("Aucune slide encodée — bascule mode agent.") return run_encoder(AgentSession(ENCODER_ID), plan, layouts, proj) yaml_str = enc.to_yaml(data) is_valid, message, _ = validate_yaml(yaml_str, layouts) if is_valid: ok(f"YAML valide : {message}") else: warn(f"Validation : {message}") if errors: print("\\n [1] Continuer sans les slides en échec") print(" [2] Basculer sur l'Encoder agent (deck complet)") print(" [0] Abandonner") choix = ask("Votre choix :") if choix == "2": return run_encoder(AgentSession(ENCODER_ID), plan, layouts, proj) if choix == "0": return None, None, None, len(errors) path = save_text(yaml_str, proj.out("input", "yaml")) return yaml_str, data, path, len(errors) ''' PATCHES = [ # P1 — config ( 'ENCODER_ID = os.getenv("ENCODER_AGENT_ID")\n', 'ENCODER_ID = os.getenv("ENCODER_AGENT_ID")\n' 'ENCODER_MODE = os.getenv("ENCODER_MODE", "agent")' ' # agent | schema (C1)\n', ), # P3 — dispatch pipeline pass (révisions comprises) ( ' encoder = AgentSession(ENCODER_ID)\n' ' yaml_str, yaml_data, yaml_path, tries = run_encoder(\n' ' encoder, plan, layouts, proj, scope_encoder)\n', ' if ENCODER_MODE == "schema":\n' ' yaml_str, yaml_data, yaml_path, tries = ' 'run_encoder_schema(\n' ' plan, layouts, proj)\n' ' else:\n' ' encoder = AgentSession(ENCODER_ID)\n' ' yaml_str, yaml_data, yaml_path, tries = run_encoder(\n' ' encoder, plan, layouts, proj, scope_encoder)\n', ), # P4 — dispatch flux standard (express → agent) ( ' encoder = AgentSession(ENCODER_ID)\n' ' yaml_str, yaml_data, yaml_path, tries = run_encoder(\n' ' encoder, plan, layouts, proj)\n' ' manifest.step("encoder", tentatives_correction=tries)\n' ' if yaml_str is None:\n' ' if express:\n', ' if ENCODER_MODE == "schema" and not express:\n' ' yaml_str, yaml_data, yaml_path, tries = ' 'run_encoder_schema(\n' ' plan, layouts, proj)\n' ' else:\n' ' if ENCODER_MODE == "schema" and express:\n' ' info("Mode express → Encoder agent ' '(plan non annoté).")\n' ' encoder = AgentSession(ENCODER_ID)\n' ' yaml_str, yaml_data, yaml_path, tries = run_encoder(\n' ' encoder, plan, layouts, proj)\n' ' manifest.step("encoder", tentatives_correction=tries)\n' ' if yaml_str is None:\n' ' if express:\n', ), ] MARKER = "run_encoder_schema" ANCHOR_SECTION = "# FLUX LIBRE — THE FREE DESIGNER" def fail(msg): print(" ! %s" % msg) sys.exit(1) def main(): if not TARGET.exists(): fail("%s introuvable — lancer depuis le dossier du pipeline." % TARGET) for dep in ("schemas.py", "encoder_schema.py"): if not Path(dep).exists(): fail("%s manquant à côté du facilitator." % dep) content = TARGET.read_text(encoding="utf-8") if MARKER in content: fail("Déjà patché (run_encoder_schema présent) — rien à faire.") # Vérification de toutes les ancres AVANT toute écriture for i, (old, _) in enumerate(PATCHES, 1): n = content.count(old) if n == 0: fail("Ancre du patch %d introuvable — facilitator inattendu." % i) if n > 1: fail("Ancre du patch %d non unique (%d occurrences)." % (i, n)) if content.count(ANCHOR_SECTION) != 1: fail("Ancre de section FREE DESIGNER introuvable ou non unique.") shutil.copy2(TARGET, TARGET.with_suffix(".py.bak")) print(" + Sauvegarde : %s.bak" % TARGET) for old, new in PATCHES: content = content.replace(old, new) # P2 — insertion de la fonction avant le séparateur de la section lines = content.split("\n") idx = next(i for i, l in enumerate(lines) if ANCHOR_SECTION in l) ins = idx - 1 if lines[idx - 1].startswith("# ───") else idx lines[ins:ins] = FUNC.split("\n") content = "\n".join(lines) TARGET.write_text(content, encoding="utf-8") print(" + 4 patchs appliqués.") try: py_compile.compile(str(TARGET), doraise=True) print(" + Compilation OK.") except py_compile.PyCompileError as e: shutil.copy2(TARGET.with_suffix(".py.bak"), TARGET) fail("Erreur de compilation — fichier restauré :\n%s" % e) print("\n Activer : ajouter ENCODER_MODE=schema dans .env") print(" Retour arrière : ENCODER_MODE=agent (ou supprimer la ligne).") if __name__ == "__main__": main()