Files
fn_registry/docs/capabilities/comfyui-skill.md
T
egutierrez 10dbc510b7 feat(ml): LoRAs con prefijo de arquitectura (SD15_/SDXL_/FLUX_) + refs actualizadas
Mueve el indicador de arquitectura del SUFIJO al PREFIJO del nombre de cada
LoRA para que el dropdown del LoraLoader muestre de inmediato que LoRA casa con
que checkpoint (evita el shape mismatch SD1.5 vs SDXL que crashea ComfyUI).

- 20 LoRAs renombradas en disco (15 SD15/SDXL en /mnt/2tb, 5 FLUX en ~/ComfyUI),
  mapa de reversion en ~/ComfyUI/models/loras/_rename_map.json.
- Refs actualizadas en builders gamedev-2d, style presets, pipelines, tests y
  docs/capabilities. Defaults hardcodeados (pixel-art, lcm-lora, etc.) apuntan a
  los nombres con prefijo.
- Ejemplos genericos en docstrings normalizados a la convencion de prefijo.
- comfyui_replicate_civitai_oneshot::_norm ignora el token de arquitectura al
  comparar, robusto al reordenado (sufijo civitai vs prefijo instalado).

Refs a repos HuggingFace (nerijs/pixel-art-xl) y checkpoints (juggernaut_xl_v11)
preservados. Verificado: dropdown LoraLoader con prefijos + generacion real
pixel-art OK + tests comfyui verdes (481 ml + 26 pipelines).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 16:33:03 +02:00

351 lines
25 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_export_skill_template_py_ml](../../python/functions/ml/comfyui_export_skill_template.md) | `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](../../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_inject_multi_lora_py_ml](../../python/functions/ml/comfyui_inject_multi_lora.md) | `comfyui_inject_multi_lora(workflow, loras) -> dict` | Encadena N `LoraLoader` sobre un workflow ya construido reusando `comfyui_inject_lora` por LoRA. Cada lora = `{name, strength_model, strength_clip}`; respeta el orden (primero cerca del checkpoint, último cerca del KSampler). Apila estilo + detalle en una sola llamada. | **pura** |
| [comfyui_build_ipadapter_workflow_py_ml](../../python/functions/ml/comfyui_build_ipadapter_workflow.md) | `comfyui_build_ipadapter_workflow(prompt, ref_image, *, base_checkpoint, mode='style'\|'faceid', weight=0.8, ...) -> dict` | txt2img + IPAdapter (custom node cubiq). `mode='style'` transfiere estilo/composición de una imagen de referencia (IPAdapterUnifiedLoader+IPAdapter); `mode='faceid'` impone un rostro consistente vía insightface + .bin FaceID + su LoRA (IPAdapterUnifiedLoaderFaceID+IPAdapterFaceID). Repunta el KSampler a la rama IPAdapter. | **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 |
| [comfyui_generate_with_skill_oneshot_py_pipelines](../../python/functions/pipelines/comfyui_generate_with_skill_oneshot.md) | `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](../../python/functions/ml/comfyui_update_skill_score.md) | `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](../../python/functions/ml/comfyui_bump_skill_version.md) | `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 |
| [comfyui_extract_recipe_from_png_py_ml](../../python/functions/ml/comfyui_extract_recipe_from_png.md) | `comfyui_extract_recipe_from_png(png_path, *, slug=None, civitai_meta=None, image_url='', nsfw=False) -> dict` | Destila un PNG cosechado de Civitai en una receta de skill **candidata** (`score_n=0`, `provenance.source='civitai'`). Compone `comfyui_import_workflow_png` (workflow API embebido) + `comfyui_read_png_metadata` (params del KSampler); fallback a la `meta` de Civitai. Degradación honesta: `ok=False` sin inventar si no hay ni workflow embebido ni meta utilizable. | impura |
| [comfyui_harvest_civitai_skill_oneshot_py_pipelines](../../python/functions/pipelines/comfyui_harvest_civitai_skill_oneshot.md) | `comfyui_harvest_civitai_skill_oneshot(*, query=None, model_version_id=None, nsfw='None', dest_dir, library_dir='~/ComfyUI/skills_library', ...) -> dict` | One-shot Civitai → skill candidata: `search``fetch` (segrega NSFW) → `extract_recipe``save_skill`. Itera los items hasta hallar uno con receta destilable (preferentemente workflow embebido), descartando los PNG sin receta; 2º pase al feed global si filtró por modelo. **No baja modelos a ciegas**: los ausentes van a `missing_models`. | pipeline (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`.
## 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:
1. **Genera la canónica** con `judge=True`. El panel `comfyui-judge` emite un `score` y el pipeline
lo acumula en `score_mean`/`score_n` de la skill (vía `comfyui_update_skill_score`). Ese score es
el `score_before`.
2. **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. Su `judge.score` es
el `score_after`, y su `judge_run_id` es la evidencia.
3. **Promueve con el gate**: `comfyui_bump_skill_version` aplica el patch a `recipe.json`, sube el
semver y deja una línea en `growth_log.jsonl` **solo si `score_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):
```python
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 con `comfyui_load_workflow_ui` (`app.loadApiJson`, litegraph lo auto-posiciona) o va directo
a `comfyui_submit_workflow`. Es el node-template versionable de la skill.
- **UI graph** (`~/ComfyUI/user/default/workflows/<slug>.json` + copia en `exports/<slug>.ui.json`)
`nodes`/`links`/`pos` (`app.graph.serialize()`). La carpeta nativa de la UI **solo** acepta este
formato; por eso solo se escribe con `ui_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`.
## Cosecha Civitai → skill candidata
El registry crece también captando recetas que **ya existen en internet**, no solo escribiéndolas a
mano: doctrina del issue 0087 aplicada a la captación de assets. Cada imagen publicada en Civitai
suele llevar su workflow de ComfyUI embebido en el PNG (chunk `prompt`, API format); cosecharla
destila la receta entera (checkpoint + LoRAs + params + prompt) en una **skill candidata** lista para
juzgar.
```python
import sys, os
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions", "pipelines"))
from comfyui_harvest_civitai_skill_oneshot import comfyui_harvest_civitai_skill_oneshot
res = comfyui_harvest_civitai_skill_oneshot(
query="cinematic portrait", nsfw="None",
dest_dir=os.path.expanduser("~/ComfyUI/civitai_harvest"),
)
print(res["ok"], res["slug"], "workflow_embebido=", res["has_workflow"])
print("guardada en:", res["skill_path"]) # ~/ComfyUI/skills_library/<slug>/recipe.json
print("modelos ausentes:", res["missing_models"]) # checkpoints/LoRAs a bajar (NO bajados)
```
Dos piezas, una composición:
- **`comfyui_extract_recipe_from_png`** (paso puro de destilación, impura solo por leer disco) —
toma un PNG ya descargado y produce la receta candidata. Compone
`comfyui_import_workflow_png` (workflow embebido) + `comfyui_read_png_metadata` (params del
KSampler), con fallback a la `meta` de generación de Civitai y, para samplers no-KSampler
(flux/`SamplerCustomAdvanced`), heurística sobre los nodos `CLIPTextEncode`. **Degradación honesta**:
si no hay ni workflow embebido ni meta utilizable devuelve `ok=False` sin inventar la receta.
- **`comfyui_harvest_civitai_skill_oneshot`** (pipeline) — encadena
`search_civitai_images → fetch_civitai_image → extract_recipe_from_png → save_skill` en una sola
llamada. Itera los resultados del search hasta encontrar el primero con receta destilable
(descartando los PNG sin workflow), y si filtró por un modelo concreto y ninguno trae grafo, hace
un 2º pase al feed global "Most Reactions" (donde abundan los workflows ComfyUI de usuarios flux).
Notas de uso:
- **La skill nace CANDIDATA** (`score_n=0`, `provenance.source='civitai'`): no está validada. El
prompt cosechado es **concreto**, no un scaffold con `{subject}` — sustitúyelo a mano si quieres
reutilizar la skill para otros sujetos. La validación la da el bucle
`generate_with_skill_oneshot` (juzga) + `comfyui_bump_skill_version` (promueve si mejora).
- **No baja modelos a ciegas**: si la receta referencia un checkpoint o LoRA que no está en
`<comfyui_dir>/models/`, lo lista en `missing_models` y no descarga nada. Bajarlos
(`comfyui_search_civitai_models` + `comfyui_download_model`) es una decisión aparte del caller.
- **NSFW segregado**: el PNG se descarga a `<dest_dir>/nsfw/` si el item es NSFW (permitido pero
siempre separado). El `dest_dir` vive fuera del repo (`~/ComfyUI/`) y se trata como datos: no se
commitea ni se indexa.
- El token Civitai es secreto: viene de `pass civitai/api-token`, nunca hardcodeado.
## Mezclar capacidades (mixer)
Una skill fija *una* receta. El **mixer** resuelve el otro eje: combinar **a la carta** todas las
capacidades de generación sobre un mismo workflow base y activar/desactivar cada una para iterar.
Misma doctrina del issue 0087 (componer piezas probadas, no reescribir el grafo), pero aplicada a
mezclar capacidades en vez de a guardar una receta.
Dos funciones:
| ID | firma corta | qué hace |
|---|---|---|
| `comfyui_compose_capabilities_py_ml` | `compose_capabilities(base, *, loras, controlnet, ipadapter, hires, facedetailer) -> dict` | **PURA.** Aplica EN ORDEN las capacidades activadas (cada arg `None` = desactivada) sobre un dict base, componiendo los inyectores/builders encadenables. Reconecta MODEL/CLIP/positive/IMAGE. Sin ninguna = base intacto. |
| `comfyui_generate_mixed_oneshot_py_pipelines` | `generate_mixed_oneshot(base, subject, *, capabilities, server, judge, ...) -> dict` | **Pipeline.** base (skill slug / `'txt2img'` / dict) → compose → submit → wait → fetch → (si `judge`) juzga. Devuelve `{ok, prompt_id, image_path, capabilities_active, judge, error}`. |
El mixer se apoya en los **inyectores encadenables-sobre-dict** (cada uno la versión componible de
su builder-desde-cero hermano):
| Capacidad | Inyector | Reconecta |
|---|---|---|
| LoRAs (N) | `comfyui_inject_multi_lora_py_ml` | cadena MODEL/CLIP tras el checkpoint |
| ControlNet | `comfyui_inject_controlnet_py_ml` | `KSampler.positive``ControlNetApply` |
| IPAdapter (style/faceid) | `comfyui_inject_ipadapter_py_ml` | `KSampler.model` ← IPAdapter (tras las LoRAs) |
| hires/upscale | `comfyui_inject_hires_fix_py_ml` | `UltimateSDUpscale` tras el `VAEDecode` |
| FaceDetailer | `comfyui_build_facedetailer_workflow_py_ml` | regenera caras del `VAEDecode` |
Orden fijo: `loras → controlnet → ipadapter → facedetailer → hires`. El IPAdapter se aplica sobre
el MODEL ya modificado por los LoRAs (orden correcto). Tras FaceDetailer el mixer deja un único
`SaveImage` (el del detailer).
### Ejemplo canónico (≥3 capacidades, juzgado)
```python
import sys, os
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
from pipelines.comfyui_generate_mixed_oneshot import comfyui_generate_mixed_oneshot
# txt2img dreamshaper + 2 LoRAs + FaceDetailer (3 capacidades). Activar/desactivar = cambiar args.
res = comfyui_generate_mixed_oneshot(
"txt2img",
"a heroic knight portrait, 3d render style, dramatic lighting, detailed face",
checkpoint="dreamshaper_8.safetensors",
capabilities={
"loras": [
{"name": "SD15_3d_render_redmond.safetensors", "strength_model": 0.9},
{"name": "SD15_detail_tweaker.safetensors", "strength_model": 0.5, "strength_clip": 0.5},
],
"facedetailer": {"denoise": 0.45},
# "ipadapter": {"ref_image": "face.png", "mode": "faceid"}, # se activa con solo añadirla
# "hires": {"upscale_by": 1.5},
},
dest="/tmp/comfy_mixed", seed=42, judge=True,
)
print(res["ok"], res["prompt_id"], res["capabilities_active"], res["judge"])
```
### Límite conocido (8GB / piezas actuales)
- **hires + facedetailer no encadenan**: ambos toman su imagen del `VAEDecode` del render base, así
que combinarlos deja a uno sin efecto sobre la salida final (con los dos activos, hires "gana" y
facedetailer queda sin consumidor). Usa uno U otro por workflow. El resto de combinaciones
(LoRAs + ControlNet + IPAdapter + uno de los dos post-procesos) encadenan limpio.
- **VRAM**: en 8GB lowvram con SD1.5 entran ~2-3 capacidades modestas (p.ej. 2 LoRAs + FaceDetailer
a 512px). Apilar IPAdapter FaceID + ControlNet + hires + facedetailer a la vez puede dar OOM —
baja resolución o reduce capacidades. `mixer` no valida VRAM; el OOM aflora en `wait`.
- **Incompatibilidad explícita, no silenciosa**: ControlNet sin `control_image` o IPAdapter sin
`ref_image` lanzan `ValueError` del inyector (no petan a medias). Las imágenes de control/referencia
deben estar en el `input/` del servidor antes de encolar.
## 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 juicio (`comfyui-judge`) vive en su grupo**: este grupo lo *consume* (vía
`generate_with_skill_oneshot` con `judge=True`), pero el panel multi-juez —estético + CLIP +
LLM-vision— se documenta en [`comfyui-judge`](comfyui-judge.md). Aquí solo se acumula su `score`
en `score_mean` (`comfyui_update_skill_score`) y se usa como gate del bump.
- **El bump solo sube versiones, no genera ni juzga**: `comfyui_bump_skill_version` aplica 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 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`).