Files
sliding-automation/patch_facilitator_c1.py
T

183 lines
7.3 KiB
Python

#!/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()