10dbc510b7
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>
351 lines
25 KiB
Markdown
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`).
|