70d541fca9
Grupo nuevo comfyui-skill: recetas versionadas de generación ComfyUI que
compilan a un workflow cambiando solo el subject.
- comfyui_build_skill_workflow (pura): receta -> workflow API format,
despacha base (txt2img/flux/sdxl_refiner), sustituye {subject}+triggers,
encadena loras e inject blocks (facedetailer, hires_fix). SkillWorkflowError tipada.
- comfyui_inject_hires_fix (pura): inyecta 2ª pasada UltimateSDUpscale sobre dict.
- comfyui_save/load/list_skill (impuras): CRUD de la librería en disco con
versionado por snapshots, round-trip idéntico, filtro NSFW.
- ask_llm_vision (core, claude-direct): pregunta multimodal imagen+texto via
API directa Anthropic, para puntuar generaciones.
- Página madre docs/capabilities/comfyui-skill.md con schema canónico de recipe.json.
Tests offline: 11 verdes (6 builder + 5 inject_hires_fix). Sin GPU.
143 lines
8.8 KiB
Markdown
143 lines
8.8 KiB
Markdown
# 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`](comfyui.md) (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)
|
|
|
|
```json
|
|
{
|
|
"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](../../python/functions/ml/comfyui_build_skill_workflow.md) | `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_inject_hires_fix_py_ml](../../python/functions/ml/comfyui_inject_hires_fix.md) | `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](../../python/functions/ml/comfyui_save_skill.md) | `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](../../python/functions/ml/comfyui_load_skill.md) | `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](../../python/functions/ml/comfyui_list_skills.md) | `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](../../python/functions/core/ask_llm_vision.md) | `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 |
|
|
|
|
`build_skill_workflow` compone los builders del grupo [`comfyui`](comfyui.md):
|
|
`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.
|
|
|
|
```python
|
|
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`.
|
|
|
|
## 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_workflow` es
|
|
puro y no valida contra el servidor — usa `comfyui_validate_workflow` antes de encolar si dudas.
|
|
- **`base_workflow` solo de texto**: `txt2img`, `flux`, `sdxl_refiner`. Las bases que parten de una
|
|
imagen (`img2img`, `inpaint`, `controlnet`) lanzan `SkillWorkflowError`; para esas, monta el
|
|
workflow con los builders del grupo `comfyui` directamente.
|
|
- **`blocks` soportados**: `facedetailer` y `hires_fix`. Otros post-procesos (IPAdapter,
|
|
multi-ControlNet) se añaden creando su función-inyector hermana y registrándola en el dispatcher
|
|
de `build_skill_workflow`.
|
|
- **El scoring (`score_mean`/`score_n`) no se calcula aquí**: `ask_llm_vision` da el juicio del
|
|
modelo sobre una imagen, pero actualizar la receta con el score acumulado es trabajo de otra
|
|
pieza (el bucle de scoring) que reescribe la receta y la re-guarda con `comfyui_save_skill`.
|
|
- **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 un `operations.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_workflow` e `inject_hires_fix` son puras y sí tienen
|
|
tests de estructura offline (`python/functions/ml/tests/test_comfyui_build_skill_workflow.py`,
|
|
`test_comfyui_inject_hires_fix.py`).
|