feat(ml): mixer de capacidades comfyui (compose + generate_mixed_oneshot + inject controlnet/ipadapter)

Mezclador del grupo comfyui-skill que promueve a una sola llamada la secuencia
base -> compose -> submit -> wait -> fetch -> judge (issue 0087):

- comfyui_compose_capabilities_py_ml (PURA): aplica en orden las capacidades
  activadas (loras, controlnet, ipadapter, facedetailer, hires) sobre un
  workflow base, sin mutar la entrada.
- comfyui_generate_mixed_oneshot_py_pipelines: one-shot que resuelve el base
  (skill/txt2img/dict), compone, encola, espera, descarga el PNG y lo puntua
  con el panel comfyui-judge.
- comfyui_inject_controlnet_py_ml, comfyui_inject_ipadapter_py_ml: inyectores
  encadenables que consume el compose.
- Tests (24 passed) + pagina madre docs/capabilities/comfyui-skill.md.

Prueba real en GPU: txt2img dreamshaper_8 + 2 LoRAs (3d_render_redmond +
detail_tweaker) + FaceDetailer -> imagen 512x512 en ~24s, juez verdict 'good'
(score 4.69, votos aesthetic+clip good; voto llm degradado por rate-limit 429).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-24 19:02:10 +02:00
parent c36c80dda9
commit 69d9aed46a
12 changed files with 1494 additions and 0 deletions
@@ -0,0 +1,87 @@
---
name: comfyui_compose_capabilities
kind: function
lang: py
domain: ml
version: "1.0.0"
purity: pure
signature: "def comfyui_compose_capabilities(base_workflow: dict, *, loras: list[dict] | None = None, controlnet: dict | None = None, ipadapter: dict | None = None, hires: dict | None = None, facedetailer: dict | None = None) -> dict"
description: "Mezclador de capacidades ComfyUI: toma un workflow base en API format (skill o txt2img) y aplica EN ORDEN las capacidades activadas (cada arg None = desactivada), componiendo los inyectores/builders encadenables del registry: loras (inject_multi_lora) -> controlnet (inject_controlnet) -> ipadapter (inject_ipadapter) -> facedetailer (build_facedetailer_workflow) -> hires (inject_hires_fix), reconectando MODEL/CLIP/positive/IMAGE. Cada capacidad es opcional e independiente; sin ninguna devuelve el base intacto. Pura: no muta el dict de entrada."
tags: [comfyui, comfyui-skill, ml, mixer, lora, controlnet, ipadapter, facedetailer, hires, workflow]
uses_functions: [comfyui_inject_multi_lora_py_ml, comfyui_inject_controlnet_py_ml, comfyui_inject_ipadapter_py_ml, comfyui_build_facedetailer_workflow_py_ml, comfyui_inject_hires_fix_py_ml]
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
params:
- name: base_workflow
desc: "dict en API format (salida de comfyui_build_skill_workflow o comfyui_build_txt2img_workflow). No se muta; se devuelve una copia."
- name: loras
desc: "Lista de dicts {name, strength_model?, strength_clip?} para inject_multi_lora. None o vacia = sin LoRAs. keyword-only."
- name: controlnet
desc: "Dict para inject_controlnet: {control_image (obligatoria), cn_name (obligatoria), strength?, positive_node?}. None = sin ControlNet. keyword-only."
- name: ipadapter
desc: "Dict para inject_ipadapter: {ref_image (obligatoria), mode ('style'|'faceid'), weight?, ...}. None = sin IPAdapter. keyword-only."
- name: hires
desc: "Dict de kwargs para inject_hires_fix (upscale_by, denoise, steps, cfg, seed, upscale_model, ...). {} = hires con defaults. None = sin hires. keyword-only."
- name: facedetailer
desc: "Dict de overrides para build_facedetailer_workflow. ckpt_name/positive/negative se detectan del workflow si faltan; resto = params del builder (denoise, steps, bbox_model, ...). {} = detect + defaults. None = sin facedetailer. keyword-only."
output: "copia del base con las capacidades activadas encadenadas en orden (loras -> controlnet -> ipadapter -> facedetailer -> hires). Sin ninguna activada, copia del base intacta. Tras facedetailer deja un unico SaveImage (el del detailer)."
tested: true
tests: ["sin capacidades devuelve el base intacto (mismos nodos)", "solo loras encadena los LoraLoader", "loras + facedetailer: cadena de loras + FaceDetailer + un solo SaveImage", "ipadapter + lora: IPAdapter toma el MODEL del ultimo LoraLoader", "hires anade UltimateSDUpscale", "controlnet sin control_image propaga ValueError", "ipadapter sin ref_image propaga ValueError", "no muta el dict de entrada (pureza)", "api format valido en todas las combinaciones", "activar una capacidad cambia el conjunto de class_types"]
test_file_path: "python/functions/ml/tests/test_comfyui_compose_capabilities.py"
file_path: "python/functions/ml/comfyui_compose_capabilities.py"
---
## Ejemplo
```python
import sys, os
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow
from ml.comfyui_compose_capabilities import comfyui_compose_capabilities
base = comfyui_build_txt2img_workflow("dreamshaper_8.safetensors", "a hero, 3d render style")
# 3 capacidades a la vez: 2 LoRAs + FaceDetailer (activar/desactivar = cambiar args)
mixed = comfyui_compose_capabilities(
base,
loras=[
{"name": "3d_render_redmond_sd15.safetensors", "strength_model": 0.9},
{"name": "detail_tweaker_sd15.safetensors", "strength_model": 0.5},
],
facedetailer={"denoise": 0.45},
# controlnet=..., ipadapter=..., hires=... -> None = desactivadas
)
```
## Cuando usarla
Cuando quieras **mezclar varias capacidades de generacion** (LoRAs + ControlNet +
IPAdapter + FaceDetailer + hires) sobre un mismo workflow base y poder
activar/desactivar cada una para iterar y mejorar. Es el "mixer" del grupo
`comfyui-skill`: una sola funcion en vez de encadenar los inyectores a mano. La
salida va directa a `comfyui_submit_workflow` (o usa el one-shot
`comfyui_generate_mixed_oneshot` para submit + juicio).
## Gotchas
- Pura: no muta el `base_workflow` y NO valida que checkpoints/loras/modelos
existan en el servidor. Las imagenes de control/referencia (ControlNet,
IPAdapter) deben estar en el `input/` del servidor antes de submit.
- **Orden fijo**: loras -> controlnet -> ipadapter -> facedetailer -> hires. El
IPAdapter se aplica sobre el MODEL ya modificado por los LoRAs (orden correcto).
- **hires + facedetailer NO encadenan** con las piezas actuales: ambos toman su
imagen del VAEDecode del render base, asi 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. Es la limitacion documentada del
mixer; el resto de combinaciones (loras+controlnet+ipadapter+uno de los dos
post-procesos) encadenan limpio.
- Cada capacidad apila coste de 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
resolucion o desactiva capacidades.
- Errores de incompatibilidad (controlnet sin `control_image`, ipadapter sin
`ref_image`, mode invalido) se propagan como `ValueError` del inyector, no
petan en silencio.