Phase 6.3: stop the gate depending on penpot.root, and stop blaming the plugin for its own bugs
The setup returned penpot.root.id and the run aborted when it came back as an empty string on the second prompt of a batch. penpot.root is the root of the ACTIVE page, and after createPage plus openPage it need not have caught up yet - a race the page-emptying code introduced, since that leaves the emptied page active. The value was never useful anyway: every page shares the same root id, so it identified nothing. Setup now returns only pageId, which is unique and stable, and both the audit and the cleanup use it. The diagnostic message is the second half of the same mistake. It printed "PLUGIN DEGRADADO, ask the user to reload the browser" whenever any prompt went unmeasured, regardless of why - so it said that for a failure that was entirely the gate's own. A message that sends the user to reload their browser when the bug is mine costs both of us time. It now classifies on the error text: task timeouts and transport drops point at the plugin, anything else points at the gate and says so explicitly. Also closes two evaluation leaks the gate's own pre-flight caught, both in the seeds rather than the gate prompts, since the prompts have to stay as a real user would write them: - A seed shared the 6-gram "la home de una escuela de" with gate prompt 9. My first fix was overwritten by a subagent still writing the file, which is why it reappeared. - A seed used the same business as gate prompt 9 - a music school - without sharing any 6-gram. Shingles cannot see that: two texts describe the same business without sharing words. Training on the domain we then evaluate inflates the result invisibly. So the gate now also checks that no seed uses any of the gate's business nouns, listed explicitly. The training mix was rebuilt: it had been assembled before the 20 ambiguous-brief seeds existed, so training on it would not have used the corpus that was audited. The ambiguous-brief class gets its own mix portion rather than being folded into design, because diluted across 76 design seeds it would be at the mercy of a ratio, and that is the class the user named as the main painpoint. 125 seeds, 168 unique payloads, 446 distinct user prompts, 901 train and 99 eval.
This commit is contained in:
@@ -75,6 +75,21 @@ DISJOINT_AGAINST = [
|
||||
]
|
||||
SHINGLE_N = 6
|
||||
|
||||
# Rubros que usan los prompts de la puerta. Ningun seed puede compartirlos.
|
||||
#
|
||||
# Los shingles de 6-gramas NO atrapan esto: "necesito la home de una escuela de musica para
|
||||
# chicos" (puerta) y "estoy redisenando el sitio de la escuela de musica" (seed) no comparten
|
||||
# ningun 6-grama literal y sin embargo son el MISMO negocio. Entrenar sobre el dominio que
|
||||
# despues se evalua infla el resultado sin que se note en ninguna metrica -- es la misma fuga
|
||||
# que la de las palabras, un nivel mas arriba. Los sinonimos y las parafrasis del mismo rubro
|
||||
# solo se atrapan nombrando el rubro.
|
||||
RUBROS_DE_LA_PUERTA = [
|
||||
"pizzeria", "pizzería",
|
||||
"escuela de musica", "escuela de música",
|
||||
"notaria", "notaría",
|
||||
"trattoria",
|
||||
]
|
||||
|
||||
# Reintento de transporte contra el MCP de Penpot. El servidor corta la conexion a mitad de
|
||||
# llamada de forma intermitente; ya se habia visto en la puerta 4 de la Fase 4.
|
||||
MCP_MAX_RETRIES = int(os.environ.get("PENPOT_MCP_RETRIES", "4"))
|
||||
@@ -481,7 +496,11 @@ if (seed) {
|
||||
return {
|
||||
pageId: page.id,
|
||||
pageName: page.name,
|
||||
rootId: penpot.root.id,
|
||||
// NO se devuelve penpot.root.id. `penpot.root` es el root de la pagina ACTIVA, y despues de
|
||||
// `createPage` + `openPage` puede no haberse actualizado todavia: en la primera corrida con
|
||||
// borrado de paginas activado devolvio "" en el segundo prompt. Ademas todas las paginas
|
||||
// comparten el mismo id de root, asi que ese valor nunca sirvio para identificar nada. El
|
||||
// pageId es lo unico unico y estable, y es lo que usa la auditoria.
|
||||
seedBoardId: seedBoardId
|
||||
};
|
||||
"""
|
||||
@@ -846,6 +865,39 @@ def corpus_shingles(paths):
|
||||
return acumulado
|
||||
|
||||
|
||||
def check_rubros(prompts):
|
||||
"""Ningun seed puede compartir el RUBRO de un prompt de la puerta. Ver RUBROS_DE_LA_PUERTA."""
|
||||
import unicodedata as _ud
|
||||
|
||||
def plano(t):
|
||||
t = _ud.normalize("NFKD", (t or "").lower())
|
||||
return "".join(c for c in t if not _ud.combining(c))
|
||||
|
||||
rubros = sorted({plano(r) for r in RUBROS_DE_LA_PUERTA})
|
||||
problemas = []
|
||||
seeds_path = REPO_ROOT / "data" / "raw" / "seeds" / "penpot.jsonl"
|
||||
if not seeds_path.exists():
|
||||
print("[WARN] no hay corpus de seeds: se saltea el chequeo de rubros")
|
||||
return
|
||||
with open(seeds_path, encoding="utf-8") as f:
|
||||
for lineno, linea in enumerate(f, start=1):
|
||||
linea = linea.strip()
|
||||
if not linea:
|
||||
continue
|
||||
texto = plano(linea)
|
||||
for rubro in rubros:
|
||||
if rubro in texto:
|
||||
problemas.append(f"seed {lineno}: usa el rubro {rubro!r} de la puerta 5")
|
||||
break
|
||||
if problemas:
|
||||
raise Gate5Error(
|
||||
"hay seeds que comparten RUBRO con los prompts de la puerta 5 (fuga de dominio: los "
|
||||
"shingles no la ven porque no hace falta compartir palabras para compartir negocio):"
|
||||
"\n - " + "\n - ".join(problemas[:20])
|
||||
)
|
||||
print(f"[OK] ningun seed usa los rubros de la puerta ({', '.join(rubros)})")
|
||||
|
||||
|
||||
def check_disjunto(prompts):
|
||||
corpus = corpus_shingles(DISJOINT_AGAINST)
|
||||
if not corpus:
|
||||
@@ -1246,9 +1298,9 @@ def run_prompt(cfg, mcp, tools, system_prompt, fila, png_dir):
|
||||
print(f"\n--- {prompt_id} ({fila.get('dificultad', '?')}) -> pagina {page_name}")
|
||||
setup = mcp.execute_json(build_setup_js(page_name, fila.get("seed_board")),
|
||||
f"{prompt_id}: setup de la pagina")
|
||||
root_id = setup.get("rootId")
|
||||
if not root_id:
|
||||
raise Gate5Error(f"{prompt_id}: el setup no devolvio rootId: {setup}")
|
||||
page_id = setup.get("pageId")
|
||||
if not page_id:
|
||||
raise Gate5Error(f"{prompt_id}: el setup no devolvio pageId: {setup}")
|
||||
|
||||
contexto = [
|
||||
f"Trabajás en la página «{page_name}», que ya está creada y abierta "
|
||||
@@ -1355,15 +1407,22 @@ def run_prompt(cfg, mcp, tools, system_prompt, fila, png_dir):
|
||||
|
||||
# --- auditoria inyectada por la puerta ------------------------------------------------
|
||||
try:
|
||||
audit = mcp.execute_json(build_audit_js(setup["pageId"]), f"{prompt_id}: auditoria")
|
||||
audit = mcp.execute_json(build_audit_js(page_id), f"{prompt_id}: auditoria")
|
||||
except Gate5Error as e:
|
||||
print(f"[WARN] {prompt_id}: la auditoria fallo ({e})")
|
||||
audit = {"auditError": str(e)}
|
||||
|
||||
# --- PNG de la puerta (nunca reemplaza al export del modelo, se suma) -----------------
|
||||
png_path = None
|
||||
shape_a_exportar = audit.get("mainBoardId") or root_id
|
||||
# Sin mainBoardId no hay nada util que exportar: el root de la pagina no se puede exportar
|
||||
# por id (todas las paginas comparten el mismo), asi que se omite el PNG en vez de exportar
|
||||
# el arbol equivocado.
|
||||
shape_a_exportar = audit.get("mainBoardId")
|
||||
if not shape_a_exportar:
|
||||
print(f"[WARN] {prompt_id}: la auditoria no encontro un board principal; no hay PNG")
|
||||
try:
|
||||
if not shape_a_exportar:
|
||||
raise Gate5Error("sin board principal que exportar")
|
||||
_texto, binarios, _ = mcp.call_tool(
|
||||
"export_shape", {"shapeId": shape_a_exportar, "format": "png", "mode": "shape"})
|
||||
if binarios:
|
||||
@@ -1385,7 +1444,7 @@ def run_prompt(cfg, mcp, tools, system_prompt, fila, png_dir):
|
||||
# Ademas le deja el archivo limpio al usuario.
|
||||
if LIMPIAR_PAGINAS and png_path is not None:
|
||||
try:
|
||||
info = mcp.execute_json(build_delete_page_js(setup["pageId"]),
|
||||
info = mcp.execute_json(build_delete_page_js(page_id),
|
||||
f"{prompt_id}: limpieza de la pagina")
|
||||
if info.get("borrada"):
|
||||
print(f"[CLEAN] {info['nombre']}: {info['shapesBorrados']} shape(s) borrados "
|
||||
@@ -1422,7 +1481,7 @@ def run_prompt(cfg, mcp, tools, system_prompt, fila, png_dir):
|
||||
"prompt": fila["prompt"],
|
||||
"pageId": setup.get("pageId"),
|
||||
"pageName": page_name,
|
||||
"rootId": root_id,
|
||||
"pageId_auditado": page_id,
|
||||
"seedBoardId": setup.get("seedBoardId"),
|
||||
"turnos_usados": len(turnos),
|
||||
"finish_reason": finish_reason,
|
||||
@@ -1643,6 +1702,7 @@ def run_full(cfg, tools, system_prompt):
|
||||
prompts = load_prompts()
|
||||
todos_los_prompts = list(prompts)
|
||||
check_disjunto(prompts)
|
||||
check_rubros(prompts)
|
||||
|
||||
# Re-medir un subconjunto sin quemar los 10. Existe porque el MCP de Penpot se cae: cuando
|
||||
# una corrida pierde 5 prompts por un corte de transporte, repetir los 10 desperdicia media
|
||||
@@ -1772,11 +1832,27 @@ def run_full(cfg, tools, system_prompt):
|
||||
sin_medir = sorted(set(pendientes) |
|
||||
{r["id"] for r in fusionar(resultados) if r.get("score") is None})
|
||||
if sin_medir:
|
||||
print(f"\n[PLUGIN DEGRADADO] {len(sin_medir)} prompt(s) no se pudieron medir. El plugin "
|
||||
f"de Penpot responde pero se degrada tras 5 o 6 prompts pesados.")
|
||||
print(f" Pedile al usuario que RECARGUE el plugin en el navegador y retoma con:")
|
||||
# NO culpar al plugin por defecto. Un mensaje que manda al usuario a recargar el navegador
|
||||
# cuando la causa es un bug de la puerta cuesta tiempo de las dos partes, y ya paso una
|
||||
# vez: el setup fallo por depender de `penpot.root` y el script igual imprimio "plugin
|
||||
# degradado". Se clasifica por la evidencia del error, no por el hecho de que falte algo.
|
||||
errores = " ".join(str(r.get("error", "")) for r in fusionar(resultados)
|
||||
if r.get("score") is None)
|
||||
parece_plugin = bool(re.search(
|
||||
r"timed out after \d+ seconds|No Penpot plugin instances|ConnectionError|"
|
||||
r"error de transporte", errores, re.I))
|
||||
print(f"\n[INCOMPLETO] {len(sin_medir)} prompt(s) no se pudieron medir.")
|
||||
if parece_plugin:
|
||||
print(" Causa probable: el plugin de Penpot. Los errores son timeouts de tarea o "
|
||||
"cortes de transporte.")
|
||||
print(" Pedile al usuario que RECARGUE el plugin en el navegador y retoma con:")
|
||||
else:
|
||||
print(" Causa probable: un bug de la PUERTA, no del plugin -- los errores no son "
|
||||
"timeouts ni cortes de transporte. Revisar el mensaje de error antes de "
|
||||
"pedirle nada al usuario.")
|
||||
print(" Cuando este arreglado, retoma con:")
|
||||
print(f" GATE5_ONLY={','.join(sin_medir)}")
|
||||
return EXIT_PLUGIN_DEGRADADO
|
||||
return EXIT_PLUGIN_DEGRADADO if parece_plugin else 1
|
||||
print(f" resultados en {salida.relative_to(REPO_ROOT)}")
|
||||
print(f" PNGs en {png_dir.relative_to(REPO_ROOT)}/ (la pagina de cada prompt se vacia tras "
|
||||
f"exportar su PNG; solo se conserva la de un prompt cuyo PNG haya fallado)")
|
||||
|
||||
Reference in New Issue
Block a user