Cierra el gap receta->grafo del grupo comfyui-skill. La función impura comfyui_export_skill_template compila una skill a template API format (exports/<slug>.template.json) y, con ui_graph=True, genera el UI graph posicionado vía CDP (load_workflow_ui + export_workflow_ui) en la carpeta nativa de la UI (~/ComfyUI/user/default/workflows/<slug>.json), de modo que la skill aparece en el menú Workflows del navegador y se abre como grafo visual. Sin navegador, deja el template API y reporta el fallback (no falla). - 4 tests offline (golden + edge + 2 error paths). - Página madre comfyui-skill.md: fila en la tabla del grupo + sección "Skills como grafos en el navegador". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
16 KiB
ComfyUI Skill — Recetas versionadas de generación reutilizables
Tag: comfyui-skill. Grupo para tratar una configuración de generación de ComfyUI como una
skill: una receta versionada en disco (checkpoint + LoRAs + params + scaffold de prompt +
bloques de post-proceso) que se guarda una vez y se reproduce a un workflow concreto cambiando
solo el subject. Es la doctrina del issue 0087 aplicada a la generación de imágenes: el registry
crece promoviendo configuraciones que funcionan a recetas reutilizables, no reescribiendo el
grafo de nodos cada vez.
Construye sobre el grupo comfyui (los builders puros de workflow y el ciclo
submit/wait). Una skill no es un workflow: es la receta que compila a uno.
Filtro MCP: mcp__registry__fn_search query="" tag="comfyui-skill".
Qué es una skill
Una receta vive en ~/ComfyUI/skills_library/<slug>/ y la manipulan las funciones de este grupo:
~/ComfyUI/skills_library/
INDEX.md # índice regenerado de todas las skills
<slug>/
recipe.json # la receta actual
versions/vN.json # snapshot inmutable de cada save (N incremental)
growth_log.jsonl # bitácora append-only de cada save
exports/ # plantillas de workflow exportadas
samples/ # imágenes de muestra
Schema de recipe.json (canónico)
{
"schema_version": 1,
"slug": "portrait_cinematic_sdxl",
"version": "1.0.0",
"title": "Retrato cinematográfico SDXL",
"base_workflow": "txt2img",
"checkpoint": "juggernaut_xl_v11.safetensors",
"loras": [{"name": "add_detail.safetensors", "strength_model": 0.6, "strength_clip": 0.6}],
"params": {"steps": 30, "cfg": 5.5, "sampler_name": "dpmpp_2m",
"scheduler": "karras", "width": 832, "height": 1216, "denoise": 1.0},
"prompt_scaffold": {"positive": "cinematic portrait of {subject}, sharp focus",
"negative": "blurry, lowres", "trigger_words": []},
"blocks": [{"type": "facedetailer", "params": {"denoise": 0.45}},
{"type": "hires_fix", "params": {"upscale_by": 1.5, "denoise": 0.4}}],
"score_mean": 0.0, "score_n": 0,
"provenance": {"source": "manual", "nsfw": false},
"export_template_path": "exports/portrait_cinematic_sdxl.template.json"
}
base_workflow ∈ {txt2img, flux, sdxl_refiner} (las bases que se generan desde un subject
de texto). blocks[].type ∈ {facedetailer, hires_fix}.
Funciones del grupo
| ID | Firma corta | Qué hace | Purity |
|---|---|---|---|
| comfyui_build_skill_workflow_py_ml | build_skill_workflow(recipe, subject, *, seed=0) -> dict |
Compila una receta a un workflow en API format: despacha al builder base, sustituye {subject} + trigger_words, encadena LoRAs y aplica los blocks en orden. SkillWorkflowError si la base es desconocida o requiere imagen. |
pura |
| comfyui_export_skill_template_py_ml | export_skill_template(slug, *, ui_graph=False, port=9222, ...) -> dict |
Exporta una skill a artefactos cargables como GRAFO: template API en exports/<slug>.template.json y, con ui_graph=True, el UI graph posicionado (vía load_workflow_ui+export_workflow_ui por CDP) en la carpeta nativa ~/ComfyUI/user/default/workflows/<slug>.json (menú Workflows del navegador). Sin navegador, deja el template API y reporta el fallback. |
impura |
| comfyui_inject_hires_fix_py_ml | comfyui_inject_hires_fix(workflow, *, upscale_by=1.5, denoise=0.4, steps=20, ...) -> dict |
Inyecta una 2ª pasada hires-fix (UpscaleModelLoader + UltimateSDUpscale) sobre un workflow ya construido, repuntando el SaveImage. Versión encadenable-sobre-dict del builder hermano. | pura |
| comfyui_save_skill_py_ml | comfyui_save_skill(recipe, *, library_dir=None) -> dict |
Valida el schema mínimo y escribe recipe.json + snapshot versions/vN.json + growth_log + INDEX.md. No muta la receta (round-trip con load). |
impura |
| comfyui_load_skill_py_ml | comfyui_load_skill(slug, *, version=None, library_dir=None) -> dict |
Lee recipe.json (actual) o un snapshot versions/vN.json. Slug/versión inexistente → {ok:False} sin lanzar. |
impura |
| comfyui_list_skills_py_ml | comfyui_list_skills(*, library_dir=None, include_nsfw=False) -> dict |
Lista las skills con slug/title/base_workflow/version/score/nsfw/n_versions. Oculta NSFW por defecto. | impura |
| ask_llm_vision_py_core | ask_llm_vision(prompt, image_path='', *, image_b64='', media_type='', model='claude-opus-4-8', ...) -> dict |
Pregunta multimodal (imagen + texto) al modelo via API directa de Anthropic (grupo claude-direct). Útil para puntuar el PNG de una skill y alimentar score_mean. |
impura |
| comfyui_generate_with_skill_oneshot_py_pipelines | generate_with_skill_oneshot(slug, subject, *, server='127.0.0.1:8188', dest=None, seed=0, judge=True, recipe_patch=None, ...) -> dict |
One-shot del bucle: carga la skill, la compila para el subject, encola, espera, descarga el PNG y (si judge) lo puntúa con el panel comfyui-judge, acumulando el score en la media. recipe_patch prueba una variante en memoria sin guardar. |
pipeline (impura) |
| comfyui_update_skill_score_py_ml | comfyui_update_skill_score(slug, new_score, *, library_dir=None) -> dict |
Acumula el score de un juicio en score_mean/score_n por media incremental, reescribiendo recipe.json en sitio (sin snapshot ni growth_log). |
impura |
| comfyui_bump_skill_version_py_ml | comfyui_bump_skill_version(slug, change, *, score_before, score_after, judge_run_id=None, recipe_patch=None, force=False, ...) -> dict |
Promueve una versión nueva solo si el score sube (gate objetivo): snapshot versions/vN.json + aplica recipe_patch + sube el semver + línea en growth_log. Gate bloquea si no mejora. |
impura |
build_skill_workflow compone los builders del grupo comfyui:
comfyui_build_txt2img_workflow, comfyui_build_flux_workflow,
comfyui_build_sdxl_refiner_workflow, comfyui_inject_lora,
comfyui_build_facedetailer_workflow y comfyui_inject_hires_fix.
Ejemplo canónico end-to-end (receta → workflow → PNG → score)
Guardar una skill, cargarla, compilarla a un workflow para un sujeto, encolarla y puntuar el
resultado con visión. Requiere el server ComfyUI en 127.0.0.1:8188 y los modelos de la receta
instalados.
import sys, os
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
from ml.comfyui_save_skill import comfyui_save_skill
from ml.comfyui_load_skill import comfyui_load_skill
from ml.comfyui_build_skill_workflow import build_skill_workflow
from ml.comfyui_submit_workflow import comfyui_submit_workflow
from ml.comfyui_wait_result import comfyui_wait_result
from ml.comfyui_fetch_output_image import comfyui_fetch_output_image
from core.ask_llm_vision import ask_llm_vision
# 1. Definir y guardar la skill (una vez).
recipe = {
"schema_version": 1, "slug": "portrait_cinematic_sdxl", "version": "1.0.0",
"title": "Retrato cinematográfico SDXL", "base_workflow": "txt2img",
"checkpoint": "dreamshaper_8.safetensors",
"loras": [{"name": "add_detail.safetensors", "strength_model": 0.6, "strength_clip": 0.6}],
"params": {"steps": 28, "cfg": 6.0, "sampler_name": "dpmpp_2m", "scheduler": "karras"},
"prompt_scaffold": {"positive": "cinematic portrait of {subject}, sharp focus",
"negative": "blurry, lowres", "trigger_words": []},
"blocks": [{"type": "facedetailer", "params": {"denoise": 0.45}}],
"score_mean": 0.0, "score_n": 0, "provenance": {"source": "manual", "nsfw": False},
}
comfyui_save_skill(recipe) # ~/ComfyUI/skills_library/portrait_cinematic_sdxl/
# 2. Cargar + compilar a un workflow para un sujeto concreto.
recipe = comfyui_load_skill("portrait_cinematic_sdxl")["recipe"]
wf = build_skill_workflow(recipe, "a woman with red hair", seed=42)
# 3. Encolar y esperar el PNG (camino headless del grupo comfyui).
pid = comfyui_submit_workflow(wf)["prompt_id"]
outputs = comfyui_wait_result(pid)["outputs"]
img = comfyui_fetch_output_image(outputs[0]["filename"], dest_dir="/tmp")["path"]
# 4. Puntuar el resultado con visión (alimenta el bucle de scoring de la skill).
verdict = ask_llm_vision(
"Puntúa de 0 a 10 el realismo de este retrato. Responde solo el número.",
image_path=img, model="claude-opus-4-8",
)
print(verdict["text"])
El paso "guardar la receta" se hace una sola vez; a partir de ahí cada generación es
load → build → submit, cambiando solo el subject y la seed.
Bucle de mejora (skill → genera → juzga → bump)
La doctrina del issue 0087 cerrada en un lazo: una skill no crece inflando la receta a ciegas, crece registrando mejoras medibles. El juez (no el humano) decide qué se promueve.
┌─────────────────────────────────────────────────────────────────┐
│ generate_with_skill_oneshot(slug, subject, judge=True) │
│ load → build → submit → wait → fetch → judge → score_mean │ ← canónica
└─────────────────────────────────────────────────────────────────┘
│ score_before = score de la receta vigente
▼
┌─────────────────────────────────────────────────────────────────┐
│ generate_with_skill_oneshot(..., recipe_patch={params:{...}}) │ ← variante (no guarda score)
│ misma seed, un cambio plausible → judge.score = score_after │
└─────────────────────────────────────────────────────────────────┘
│
▼ GATE objetivo
comfyui_bump_skill_version(slug, change, score_before, score_after, judge_run_id=...)
│
score_after > score_before ?
├── sí → promueve: versions/vN.json (snapshot) + recipe_patch + semver↑ + growth_log
└── no → {ok:False} — NO se promueve (la variante se descarta)
Pasos concretos:
- Genera la canónica con
judge=True. El panelcomfyui-judgeemite unscorey el pipeline lo acumula enscore_mean/score_nde la skill (víacomfyui_update_skill_score). Ese score es elscore_before. - Genera una variante con
recipe_patch(p.ej.{"params": {"steps": 32}}) y la misma seed. El patch se aplica en memoria, NO se guarda, y su score NO contamina la media. Sujudge.scorees elscore_after, y sujudge_run_ides la evidencia. - Promueve con el gate:
comfyui_bump_skill_versionaplica el patch arecipe.json, sube el semver y deja una línea engrowth_log.jsonlsolo siscore_after > score_before. Si no mejora, devuelve{ok:False}y la receta se queda como estaba. El gate es objetivo: lo decide el número del juez, no quien lanza la generación.
Así versions/ y growth_log reflejan versiones de receta con mejora demostrada, mientras
score_mean es la telemetría de calidad media de la versión vigente.
Skills como grafos en el navegador
Una skill no vive solo como receta JSON: se exporta a un grafo de ComfyUI cargable como tal en el
navegador. comfyui_export_skill_template cierra ese hueco (receta → grafo):
from ml.comfyui_export_skill_template import export_skill_template
# Headless (sin navegador): congela el template API junto a la skill.
export_skill_template("portrait_cinematic_sd15")
# -> exports/portrait_cinematic_sd15.template.json (API format, node-template reproducible)
# Con navegador (pestaña ComfyUI abierta en CDP 9222): además el grafo visual posicionado.
out = export_skill_template("portrait_cinematic_sd15", ui_graph=True, port=9222)
# -> ~/ComfyUI/user/default/workflows/portrait_cinematic_sd15.json (aparece en el menú Workflows)
Dos formatos, dos usos:
- API format (
exports/<slug>.template.json) — el dict{node_id:{class_type,inputs}}. Se carga concomfyui_load_workflow_ui(app.loadApiJson, litegraph lo auto-posiciona) o va directo acomfyui_submit_workflow. Es el node-template versionable de la skill. - UI graph (
~/ComfyUI/user/default/workflows/<slug>.json+ copia enexports/<slug>.ui.json) —nodes/links/pos(app.graph.serialize()). La carpeta nativa de la UI solo acepta este formato; por eso solo se escribe conui_graph=True(se genera vía CDP cargando el API en la UI y serializando el grafo posicionado). Es el que se abre como grafo visual desde el menú Workflows.
Fotos ↔ grafo. Cada PNG de ComfyUI lleva su workflow embebido (chunk prompt, API format).
comfyui_import_workflow_png lo recupera, de modo que toda muestra de una skill queda asociada a su
grafo reproducible 1:1 (ver INDEX.md de la librería: samples/<base>.png + samples/<base>.graph.json).
No destructivo en el navegador: ui_graph=True reemplaza el grafo in-memory de la pestaña. Si
hay trabajo sin guardar (título con *), respalda antes con
comfyui_export_workflow_ui(api_format=True, save_path=...) y restáuralo después con
comfyui_load_workflow_ui.
Fronteras
- No genera ni descarga modelos: una skill referencia checkpoints/LoRAs por nombre; deben
estar ya instalados en ComfyUI (
comfyui_download_model, otro flujo).build_skill_workflowes puro y no valida contra el servidor — usacomfyui_validate_workflowantes de encolar si dudas. base_workflowsolo de texto:txt2img,flux,sdxl_refiner. Las bases que parten de una imagen (img2img,inpaint,controlnet) lanzanSkillWorkflowError; para esas, monta el workflow con los builders del grupocomfyuidirectamente.blockssoportados:facedetaileryhires_fix. Otros post-procesos (IPAdapter, multi-ControlNet) se añaden creando su función-inyector hermana y registrándola en el dispatcher debuild_skill_workflow.- El juicio (
comfyui-judge) vive en su grupo: este grupo lo consume (víagenerate_with_skill_oneshotconjudge=True), pero el panel multi-juez —estético + CLIP + LLM-vision— se documenta encomfyui-judge. Aquí solo se acumula suscoreenscore_mean(comfyui_update_skill_score) y se usa como gate del bump. - El bump solo sube versiones, no genera ni juzga:
comfyui_bump_skill_versionaplica el patch y registra la mejora; generar la imagen y puntuarla es trabajo del pipeline + el panel-juez. Una variante que no supera a la vigente se descarta sola (el gate la rechaza). - La librería es metadata local: vive bajo
~/ComfyUI/skills_library(no toca el venv ni los modelos en disco). No tiene repo propio ni se indexa — es estado vivo, como unoperations.db. - Las funciones impuras del grupo (save/load/list, ask_llm_vision) no llevan unit tests por
diseño (I/O de disco / red);
build_skill_workfloweinject_hires_fixson puras y sí tienen tests de estructura offline (python/functions/ml/tests/test_comfyui_build_skill_workflow.py,test_comfyui_inject_hires_fix.py).