--- 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.