Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 741724f633 | |||
| 2be62f6ef6 | |||
| 8e9e1e6c8a | |||
| ec46aae04c | |||
| b173ac2703 | |||
| ec0a5e53ac | |||
| 5280499df5 | |||
| 604d3d4feb | |||
| 287abbd6ee | |||
| f8793f96ac |
@@ -14,7 +14,7 @@ Indice de grupos de capacidades del registry. Cada grupo agrupa >=3 funciones qu
|
||||
|
||||
| Grupo | N | Que cubre |
|
||||
|---|---|---|
|
||||
| [gamedev-2d](gamedev-2d.md) | 36 | Assets 2D para Godot via ComfyUI: 31 builders de workflow (pixelart/seamless/iso/sprite/topdown/card/enemy/prop/structure/foliage/trap/projectile/decal/particle/rune/weather/badge/skill-tree/dialogue/icon/portrait/VFX...) + 5 de apoyo: post-proceso (pixelize, luma->alpha) + puente de assets a Godot 4 (.import + reimport headless). Tag canonico `gamedev-2d` (antes `gamedev`, ya unificado) |
|
||||
| [gamedev-2d](gamedev-2d.md) | 47 | Assets 2D para Godot via ComfyUI: 36 builders de workflow (31 de generación desde texto: pixelart/seamless/iso/sprite/topdown/card/enemy/prop/structure/foliage/trap/projectile/decal/particle/rune/weather/badge/skill-tree/dialogue/icon/portrait/VFX... + 5 de transformación desde imagen: asset_variant/sprite_from_sketch/inpaint_asset/outpaint_asset/directional_sprite) + 11 de apoyo: post-proceso (pixelize, luma->alpha, flatten_alpha), puente de assets a Godot 4 (.import + reimport headless), style presets (get/apply_gamedev_style_preset) y pipelines one-shot (asset_pack/character_set/styled_asset). Tag canonico `gamedev-2d` (antes `gamedev`, ya unificado) |
|
||||
| [gamedev-engine](gamedev-engine.md) | 8 | Runtime de juego C++ multiplataforma (PC + WebAssembly): SDL3 + sokol_gfx + miniaudio. Game loop fixed-timestep, camara 2D, input unificado (teclado/gamepad/touch), sprite batch, setup de render/audio y build a wasm. Grupo hermano de `gamedev-2d` (este ejecuta el juego, aquel genera los assets) |
|
||||
| [registry](registry.md) | 17 | Auditoria y monitorizacion del propio registry: copied-code, uses-functions, unused, proposals, telemetria |
|
||||
| [systemd](systemd.md) | 14 | Generar, instalar, restart y status de unit files systemd via SSH (deploys a VPS) |
|
||||
@@ -72,8 +72,9 @@ Indice de grupos de capacidades del registry. Cada grupo agrupa >=3 funciones qu
|
||||
| [seo](seo.md) | 3 | SEO orientado a datos sobre Google Search Console: autenticar con service account (`gsc_auth`), extraer Search Analytics paginado (`pull_gsc_search_analytics`) y el pipeline de ingesta a DuckDB + espejo Postgres para Metabase (`ingest_gsc_search_analytics`). Cadena de ingesta del proyecto `seo_analytics`; alimenta dashboards de striking distance, CTR opportunities y content decay |
|
||||
| [local-hub](local-hub.md) | 4 | Exponer los procesos locales como subdominios `*.localhost` (via Caddy, sin DNS) y reunirlos en una pantalla principal Glance con estado en vivo, refrescada a diario por dag_engine. Descubre servicios (manifiesto + registry), renderiza Caddyfile + config Glance (puras), y el pipeline `refresh_local_hub` regenera+recarga. Fuente de verdad: `apps/local_hub/local_services.yaml` |
|
||||
| [comfyui-judge](comfyui-judge.md) | 4 | Panel multi-juez de calidad de imagen: estético LAION-V2 (`comfyui_score_aesthetic`, 0-10) + fidelidad CLIP prompt↔imagen (`comfyui_score_clip_alignment`, 0-1) + crítica LLM-vision (`comfyui_critique_image_llm`, good/bad). Agregados por voto mayoría en `comfyui_judge_image`. Gate objetivo para tests/DoD y el bucle de mejora de skills ComfyUI; degrada con gracia si un juez cae. Jueces estético/fidelidad por subproceso al venv ComfyUI (torch+open_clip), crítico via claude-direct |
|
||||
| [comfyui](comfyui.md) | 29 | Controlar ComfyUI (Stable Diffusion por grafos) de dos formas: por API HTTP (build_txt2img_workflow puro → submit → wait → object_info; download_model con validación Civitai/HF) y por la UI web vía CDP sobre la pestaña abierta (load_workflow_ui, set_node_widget_ui para tunear prompt/steps/seed en vivo, queue_prompt_ui = botón Queue Prompt, export_workflow_ui, refresh_nodes_ui). El API format es el puente entre ambos caminos. Las funciones de UI componen `cdp_eval`. Incluye imagen→3D nativo (Hunyuan3D-2, tag `img-to-3d`): build_image_to_3d_workflow + fetch_output_mesh + install_3d_model + pipeline image_to_3d_oneshot |
|
||||
| [comfyui-skill](comfyui-skill.md) | 11 | Tratar una configuración de generación ComfyUI como una skill: receta versionada en disco (checkpoint + LoRAs + params + scaffold de prompt + post-proceso) que se compila a un workflow cambiando solo el subject. Save/load/list de recetas, bucle de mejora genera→juzga→bump con gate objetivo (el score del juez decide qué se promueve), export de la skill a grafo cargable en el navegador, y cosecha de Civitai (extract_recipe_from_png + harvest oneshot) que destila el workflow embebido de una imagen pública en una skill candidata |
|
||||
| [comfyui](comfyui.md) | 126 | Controlar ComfyUI (Stable Diffusion por grafos) de dos formas: por API HTTP (build_txt2img_workflow puro → submit → wait → object_info; download_model con validación Civitai/HF) y por la UI web vía CDP sobre la pestaña abierta (load_workflow_ui, set_node_widget_ui para tunear prompt/steps/seed en vivo, queue_prompt_ui = botón Queue Prompt, export_workflow_ui, refresh_nodes_ui). El API format es el puente entre ambos caminos. Las funciones de UI componen `cdp_eval`. Cubre txt2img/img2img/inpaint/controlnet/sdxl-refiner/flux, upscale + hires-fix + facedetailer, vídeo (LTX/Wan/SVD), audio (ACE-Step), imagen→3D nativo (Hunyuan3D-2) + post-proceso de malla, templates oficiales, civitai harvest y control de cola. N = funciones con tag `comfyui` (incluye los sub-grupos `comfyui-skill`/`comfyui-styles` y 45 de `gamedev-2d`); las páginas madre de cada sub-grupo desglosan su parte |
|
||||
| [comfyui-skill](comfyui-skill.md) | 17 | Tratar una configuración de generación ComfyUI como una skill: receta versionada en disco (checkpoint + LoRAs + params + scaffold de prompt + post-proceso) que se compila a un workflow cambiando solo el subject. Save/load/list de recetas, bucle de mejora genera→juzga→bump con gate objetivo (el score del juez decide qué se promueve), export de la skill a grafo cargable en el navegador, y cosecha de Civitai (extract_recipe_from_png + harvest oneshot) que destila el workflow embebido de una imagen pública en una skill candidata |
|
||||
| [comfyui-styles](comfyui-styles.md) | 5 | Capa de estilo reutilizable sobre los builders ComfyUI. Catálogo WAS (tag `comfyui-styles`): `curated_styles_catalog` (~190 estilos), `generate_styles_llm` (genera estilos por LLM via ask_llm), `append_styles` (merge+dedup+backup sobre el styles.json del selector WAS). Style presets gamedev (tag `gamedev-2d`): `get_gamedev_style_preset` (gameboy/ghibli/pixel-art-retro como datos puros) + `apply_style_preset` (preset+subject → kwargs de un builder gamedev-2d). El estilo se trata como dato curado, no como prompt repetido |
|
||||
| [comfyui-overview](comfyui-overview.md) | — | Mapa cross-grupo de las capacidades de generación ComfyUI (txt2img, img2img/inpaint, controlnet, skills/multiestilo-LoRA, video, upscale/detail, 3D, juez, operación): cada capacidad → builders/pipelines del registry + grafos UI + skills que la cubren. Índice de entrada al stack ComfyUI; las firmas y gotchas viven en `comfyui.md`/`comfyui-skill.md`/`comfyui-judge.md`. Catálogo navegable de los grafos en disco (subcarpetas por capacidad) en `~/ComfyUI/CAPABILITIES.md` |
|
||||
|
||||
## Como anadir grupo
|
||||
|
||||
@@ -8,7 +8,9 @@ Las tres páginas madre detalladas siguen siendo la fuente de verdad por grupo:
|
||||
|
||||
- [comfyui.md](comfyui.md) — grupo `comfyui`: builders de workflow, ejecución HTTP, UI vía CDP, I/O.
|
||||
- [comfyui-skill.md](comfyui-skill.md) — grupo `comfyui-skill`: recetas de estilo versionadas.
|
||||
- [comfyui-styles.md](comfyui-styles.md) — grupo `comfyui-styles`: presets + catálogo de estilo (selector WAS).
|
||||
- [comfyui-judge.md](comfyui-judge.md) — grupo `comfyui-judge`: panel multi-juez de calidad.
|
||||
- [gamedev-2d.md](gamedev-2d.md) — grupo `gamedev-2d`: 47 builders de assets 2D para Godot (45 también `comfyui`).
|
||||
|
||||
El catálogo navegable con los grafos concretos en disco (subcarpetas por capacidad, cómo cargar
|
||||
cada uno) vive **fuera del repo**, junto a la instalación: `~/ComfyUI/CAPABILITIES.md`. Este doc es
|
||||
@@ -25,7 +27,9 @@ Filtros MCP: `mcp__registry__fn_search query="" tag="comfyui"` (y `tag="comfyui-
|
||||
| 02 | **img2img / inpaint** | imagen → imagen, regenerar zona enmascarada | `build_img2img`, `build_inpaint` | ✅ | — |
|
||||
| 03 | **controlnet** | generación guiada por mapa (depth/pose/canny) | `build_controlnet` | ✅ | — |
|
||||
| 04 | **skills (multiestilo/LoRA)** | recetas de estilo reproducibles con `{subject}` | `build_skill_workflow`, `inject_lora`, `generate_with_skill_oneshot`, `harvest_civitai_skill_oneshot` | ✅ ×2 | ✅ ×2 |
|
||||
| 04b | **styles (presets/catálogo)** | estilo reutilizable: catálogo WAS + presets gamedev | `curated_styles_catalog`, `generate_styles_llm`, `append_styles`, `get_gamedev_style_preset`, `apply_style_preset` | — | — |
|
||||
| 05 | **video** | imagen/texto → vídeo (SVD, LTX, Wan) | `build_img2vid`, `build_video` | ✅ | — |
|
||||
| 05b | **audio** | texto → música/SFX/voz (ACE-Step) | `build_audio_workflow`, `fetch_output_audio` | — | — |
|
||||
| 06 | **upscale / detail** | ampliar y recuperar detalle (ESRGAN, hires-fix, FaceDetailer) | `build_upscale`, `build_hires_fix`, `inject_hires_fix`, `build_facedetailer` | — | — |
|
||||
| 07 | **3D** | imagen/texto → malla 3D (Hunyuan3D) + limpieza | `build_image_to_3d`, `build_textured_3d_multiview`, `image_to_3d_oneshot`, `text_to_3d_oneshot`, `mesh_cleanup_oneshot` | — | — |
|
||||
| 08 | **juez / calidad** | puntuar lo generado (gate de DoD y bucle de mejora) | `judge_image`, `score_aesthetic`, `score_clip_alignment`, `critique_image_llm` | — | — |
|
||||
@@ -67,6 +71,16 @@ sus IDs reales cuando se ejecute `fn index`.
|
||||
- `comfyui_extract_recipe_from_png_py_ml` — destila un PNG de Civitai en receta candidata.
|
||||
- CRUD + telemetría: `comfyui_list_skills_py_ml`, `comfyui_load_skill_py_ml`, `comfyui_save_skill_py_ml`, `comfyui_update_skill_score_py_ml`, `comfyui_bump_skill_version_py_ml`.
|
||||
|
||||
### 04b · styles (presets / catálogo)
|
||||
|
||||
Página madre: [comfyui-styles.md](comfyui-styles.md). Estilo reutilizable como dato, no como prompt repetido.
|
||||
|
||||
- `comfyui_curated_styles_catalog_py_ml` (pura) — catálogo curado (~190 estilos) para el selector WAS.
|
||||
- `comfyui_generate_styles_llm_py_ml` (impura) — genera N estilos de una categoría vía `ask_llm`.
|
||||
- `comfyui_append_styles_py_ml` (impura) — fusiona estilos sobre el `styles.json` WAS (merge+dedup+backup).
|
||||
- `comfyui_get_gamedev_style_preset_py_ml` (pura) — receta de *style preset* gamedev (gameboy/ghibli/pixel-art-retro).
|
||||
- `comfyui_apply_style_preset_py_ml` (pura) — traduce un preset + subject a los kwargs de un builder gamedev-2d.
|
||||
|
||||
### 05 · video
|
||||
|
||||
- `comfyui_build_img2vid_workflow_py_ml` (pura) — SVD: condicionamiento por CLIP_VISION (sin prompt de texto).
|
||||
|
||||
@@ -12,6 +12,13 @@ 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"`.
|
||||
|
||||
> **Tamaño del grupo (al 28/06/2026):** 17 funciones con tag `comfyui-skill` — CRUD de recetas
|
||||
> (save/load/list), compilación a workflow (`build_skill_workflow`), inyectores encadenables
|
||||
> (`inject_hires_fix`/`inject_multi_lora`, `build_ipadapter_workflow`), bucle de mejora
|
||||
> genera→juzga→bump (`generate_with_skill_oneshot` + `update_skill_score` + `bump_skill_version`),
|
||||
> export a grafo (`export_skill_template`), mixer de capacidades (`compose_capabilities` +
|
||||
> `generate_mixed_oneshot`) y cosecha de Civitai (`extract_recipe_from_png` + `harvest_civitai_skill_oneshot`).
|
||||
|
||||
## Qué es una skill
|
||||
|
||||
Una receta vive en `~/ComfyUI/skills_library/<slug>/` y la manipulan las funciones de este grupo:
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# ComfyUI Styles — presets y catálogo de estilo
|
||||
|
||||
Tag: `comfyui-styles` (+ `gamedev-2d` para los dos presets gamedev). Sub-grupo de
|
||||
[`comfyui`](comfyui.md) que añade una **capa de estilo reutilizable** sobre los builders de
|
||||
workflow: en vez de repetir a mano los mismos modificadores de cámara/iluminación/render en cada
|
||||
prompt, el estilo se trata como un dato curado y reusable.
|
||||
|
||||
Dos vertientes complementarias:
|
||||
|
||||
- **Catálogo WAS** (`comfyui-styles`): ~190 estilos curados en el formato exacto del selector WAS de
|
||||
ComfyUI (*Prompt Styles Selector* / *Prompt Multiple Styles Selector*), generación de estilos
|
||||
nuevos por LLM, y fusión segura sobre el `styles.json` del usuario.
|
||||
- **Style presets gamedev** (`gamedev-2d`): recetas que empaquetan como datos puros el *look* de un
|
||||
juego entero (prefijo/sufijo de prompt, checkpoint, LoRA, negative, tamaño, post-proceso) y se
|
||||
traducen a los kwargs que consume un builder de sujeto del grupo [`gamedev-2d`](gamedev-2d.md).
|
||||
|
||||
Filtro MCP: `mcp__registry__fn_search query="" tag="comfyui-styles"` (catálogo WAS) y
|
||||
`mcp__registry__fn_search query="style preset" tag="gamedev-2d"` (presets gamedev).
|
||||
|
||||
## Funciones del grupo
|
||||
|
||||
### Catálogo WAS — dominio `ml` (tag `comfyui-styles`)
|
||||
|
||||
| ID | Firma corta | Qué hace |
|
||||
|---|---|---|
|
||||
| [comfyui_curated_styles_catalog_py_ml](../../python/functions/ml/comfyui_curated_styles_catalog.md) | `curated_styles_catalog(category=None) -> dict` | Catálogo curado (~190 estilos) en el formato exacto `{nombre: {prompt, negative_prompt}}` que consume el selector WAS. Cada `prompt` son modificadores de estilo potentes (cámara, lente, iluminación, render engine, medio artístico, paleta, mood), no descripciones de escena. Filtra por `category`. **Pura**. |
|
||||
| [comfyui_generate_styles_llm_py_ml](../../python/functions/ml/comfyui_generate_styles_llm.md) | `generate_styles_llm(category, n=8, prefix='', avoid=None, model='claude-haiku-4-5-20251001') -> dict` | Genera N estilos de una categoría temática usando `ask_llm` (grupo claude-direct, API directa, arranque 0), en el mismo formato `{nombre: {prompt, negative_prompt}}`. `avoid` evita duplicar nombres ya existentes. **Impura** (LLM). |
|
||||
| [comfyui_append_styles_py_ml](../../python/functions/ml/comfyui_append_styles.md) | `append_styles(new_styles, styles_path=DEFAULT_STYLES_PATH, overwrite=False, backup=True, dry_run=False) -> dict` | Fusiona (merge + dedup por nombre) un dict de estilos sobre el `styles.json` del selector WAS de forma SEGURA y NO destructiva: preserva todos los existentes (ganan salvo `overwrite=True`), hace backup con timestamp antes de escribir. `dry_run=True` previsualiza sin tocar disco. **Impura** (I/O disco). |
|
||||
|
||||
### Style presets gamedev — dominio `ml` (tag `gamedev-2d`)
|
||||
|
||||
| ID | Firma corta | Qué hace |
|
||||
|---|---|---|
|
||||
| [comfyui_get_gamedev_style_preset_py_ml](../../python/functions/ml/comfyui_get_gamedev_style_preset.md) | `get_gamedev_style_preset(name=None) -> dict` | Devuelve la receta de un *style preset* gamedev curado (`gameboy`, `ghibli`, `pixel-art-retro`) o el catálogo de nombres si `name=None`. Un preset empaqueta como DATOS puros el look de un juego entero: `subject_prefix`/`suffix`, `style`, `negative`, checkpoint recomendado, LoRA + strength, `size`, `transparent`, post-proceso. **Pura**. |
|
||||
| [comfyui_apply_style_preset_py_ml](../../python/functions/ml/comfyui_apply_style_preset.md) | `apply_style_preset(preset, subject, *, style=None, negative=None) -> dict` | Traduce un *style preset* gamedev (de `get_gamedev_style_preset`) + un `subject` del usuario a lo que necesita un builder de sujeto del grupo gamedev-2d: el subject combinado con el prefijo/sufijo del estilo y los kwargs comunes (`style`, `checkpoint`, `lora`, `lora_strength`, `negative`, resolución) listos para `**spread`. `style`/`negative` permiten override puntual. **Pura**. |
|
||||
|
||||
## Ejemplo canónico — generar un estilo, fusionarlo y aplicarlo
|
||||
|
||||
Dos flujos típicos: (1) ampliar el catálogo del selector WAS, y (2) usar un preset gamedev para
|
||||
generar un asset con look consistente.
|
||||
|
||||
### A) Ampliar el catálogo WAS con estilos nuevos por LLM
|
||||
|
||||
```python
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join("python", "functions"))
|
||||
from ml.comfyui_generate_styles_llm import comfyui_generate_styles_llm
|
||||
from ml.comfyui_append_styles import comfyui_append_styles
|
||||
|
||||
# 1. Pedir 6 estilos de una categoría. Devuelve el dict {nombre: {prompt, negative_prompt}}
|
||||
# directo (best-effort: {} si el LLM falla).
|
||||
nuevos = comfyui_generate_styles_llm("film noir cinematic", n=6, prefix="noir-")
|
||||
|
||||
# 2. Previsualizar la fusión (no escribe), luego aplicar con backup.
|
||||
if nuevos:
|
||||
print(comfyui_append_styles(nuevos, dry_run=True)["total_after"]) # nº tras fusionar, sin tocar disco
|
||||
res = comfyui_append_styles(nuevos) # backup + merge + dedup + escritura
|
||||
print(res["total_before"], "->", res["total_after"], "añadidos:", len(res["added"]))
|
||||
```
|
||||
|
||||
### B) Aplicar un style preset gamedev a un sujeto
|
||||
|
||||
```python
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join("python", "functions"))
|
||||
from ml.comfyui_get_gamedev_style_preset import comfyui_get_gamedev_style_preset
|
||||
from ml.comfyui_apply_style_preset import comfyui_apply_style_preset
|
||||
from ml.comfyui_build_enemy_creature_workflow import comfyui_build_enemy_creature_workflow
|
||||
|
||||
preset = comfyui_get_gamedev_style_preset("gameboy") # receta pura del look Game Boy
|
||||
ap = comfyui_apply_style_preset(preset, "a wizard casting a spell")
|
||||
# ap = {subject, builder_kwargs, size, transparent, post, ...} listo para un builder gamedev-2d:
|
||||
wf = comfyui_build_enemy_creature_workflow(
|
||||
ap["subject"], size=ap["size"], transparent=ap["transparent"], **ap["builder_kwargs"]
|
||||
)
|
||||
```
|
||||
|
||||
El catálogo curado completo se consulta sin red (devuelve el dict plano directo):
|
||||
|
||||
```python
|
||||
from ml.comfyui_curated_styles_catalog import comfyui_curated_styles_catalog
|
||||
print(comfyui_curated_styles_catalog("__categories__")) # {'categories': {...}, 'total': 190}
|
||||
todos = comfyui_curated_styles_catalog() # dict {nombre: {prompt, negative_prompt}}
|
||||
print(len(todos), list(todos)[:5])
|
||||
```
|
||||
|
||||
## Fronteras
|
||||
|
||||
- **No genera imágenes**: este sub-grupo produce y gestiona DATOS de estilo (dicts de prompt /
|
||||
negative, presets). Generar el asset es trabajo de los builders del grupo [`comfyui`](comfyui.md)
|
||||
y [`gamedev-2d`](gamedev-2d.md), o de los pipelines oneshot (p.ej.
|
||||
`comfyui_generate_styled_asset_oneshot_py_pipelines`, que compone un preset + un builder + submit).
|
||||
- **El catálogo WAS asume el custom node WAS instalado**: `append_styles` escribe sobre el
|
||||
`styles.json` que lee el selector WAS en la UI. Sin ese node, el catálogo sigue siendo usable como
|
||||
dict de modificadores, pero el selector no aparecerá en el grafo.
|
||||
- **Los dos presets gamedev (`get`/`apply`) llevan tag `gamedev-2d`**, no `comfyui-styles`: son la
|
||||
vía de estilo para los builders de assets de juego, no para el selector WAS genérico. Se listan
|
||||
aquí por afinidad de capacidad (estilo reutilizable).
|
||||
- **Formato exacto**: el dict de estilos es `{nombre: {prompt, negative_prompt}}`. Los prompts son
|
||||
modificadores (cámara/lente/luz/render/medio/paleta/mood), no descripciones de escena — la escena
|
||||
la pone el `subject` del usuario.
|
||||
@@ -13,6 +13,17 @@ Tag: `comfyui`. Grupo de funciones para controlar [ComfyUI](https://github.com/c
|
||||
|
||||
Filtro MCP: `mcp__registry__fn_search query="" tag="comfyui"`.
|
||||
|
||||
> **Tamaño del grupo (al 28/06/2026):** 126 funciones con tag `comfyui` (63 puras, 50 impuras,
|
||||
> 13 pipelines). El grupo se reparte en sub-grupos con página madre propia:
|
||||
> [`comfyui-skill`](comfyui-skill.md) (recetas de estilo versionadas),
|
||||
> [`comfyui-styles`](comfyui-styles.md) (presets + catálogo de estilo para el selector WAS),
|
||||
> [`comfyui-judge`](comfyui-judge.md) (panel de calidad) y
|
||||
> [`gamedev-2d`](gamedev-2d.md) (assets 2D para Godot: 47 funciones, 45 de ellas también `comfyui`).
|
||||
> Esta página documenta el **núcleo** (lifecycle del server, API HTTP, builders, I/O de workflows,
|
||||
> imagen→3D, UI por CDP, audio, templates); los builders específicos de gamedev-2d viven en su
|
||||
> propia página. El mapa cross-grupo de capacidades está en
|
||||
> [comfyui-overview.md](comfyui-overview.md).
|
||||
|
||||
## Dos caminos, mismo motor
|
||||
|
||||
```
|
||||
@@ -44,7 +55,7 @@ El **API format** (dict de nodos numerados que produce `build_txt2img_workflow`
|
||||
| ID | Firma corta | Qué hace |
|
||||
|---|---|---|
|
||||
| [comfyui_build_txt2img_workflow_py_ml](../../python/functions/ml/comfyui_build_txt2img_workflow.md) | `build_txt2img_workflow(ckpt_name, positive, negative='', *, steps, cfg, width, height, seed, ...) -> dict` | Construye el dict del workflow txt2img básico (Checkpoint → CLIPTextEncode×2 + EmptyLatent → KSampler → VAEDecode → SaveImage) en API format. **Pura**. |
|
||||
| [comfyui_build_flux_workflow_py_ml](../../python/functions/ml/comfyui_build_flux_workflow.md) | `build_flux_workflow(prompt, *, unet='flux1-schnell-fp8-e4m3fn.safetensors', clip_l, t5xxl, vae='ae.safetensors', width=1024, height=1024, steps=4, guidance=3.5, seed, weight_dtype='fp8_e4m3fn', ...) -> dict` | Builder txt2img para **Flux** (schnell/dev): UNETLoader + DualCLIPLoader (clip_l + t5xxl, type flux) + VAELoader → CLIPTextEncode → FluxGuidance + EmptySD3LatentImage → KSampler (cfg fijo 1.0) → VAEDecode → SaveImage. La guía va por FluxGuidance, no por el cfg. fp8 + ~4 pasos para 8 GB. **Pura**. |
|
||||
| [comfyui_build_flux_workflow_py_ml](../../python/functions/ml/comfyui_build_flux_workflow.md) | `build_flux_workflow(prompt, *, variant='schnell', width=1024, height=1024, steps=None, guidance=3.5, seed=0, unet_name=None, clip_l_name='clip_l.safetensors', t5xxl_name='t5xxl_fp8_e4m3fn_scaled.safetensors', vae_name='ae.safetensors', weight_dtype='default', sampler_name='euler', scheduler='simple', ...) -> dict` | Builder txt2img para **Flux** (`variant='schnell'` o `'dev'`): UNETLoader + DualCLIPLoader (clip_l + t5xxl, type flux) + VAELoader → CLIPTextEncode → FluxGuidance + EmptySD3LatentImage → camino custom-advanced (RandomNoise + KSamplerSelect + BasicScheduler → BasicGuider → SamplerCustomAdvanced) → VAEDecode → SaveImage. La guía va por FluxGuidance, no por el cfg. `steps=None` autoselecciona por variante (~4 schnell); `unet_name=None` deduce el checkpoint de la variante; `weight_dtype='default'`. **Pura**. |
|
||||
| [comfyui_object_info_py_ml](../../python/functions/ml/comfyui_object_info.md) | `object_info(server='127.0.0.1:8188', node_class=None, timeout) -> dict` | Catálogo de nodos del server: inputs, tipos y enums (lista de checkpoints/samplers visibles). Para validar antes de enviar. Impura. |
|
||||
| [comfyui_submit_workflow_py_ml](../../python/functions/ml/comfyui_submit_workflow.md) | `submit_workflow(workflow, server, client_id, timeout) -> dict` | Encola un workflow API format vía POST /prompt; devuelve `prompt_id` + posición en cola. HTTP 400 propaga la validación por nodo. Impura. |
|
||||
| [comfyui_wait_result_py_ml](../../python/functions/ml/comfyui_wait_result.md) | `wait_result(prompt_id, server, timeout, poll_interval) -> dict` | Sondea GET /history/{prompt_id} hasta que termina; devuelve los outputs (PNGs con filename/subfolder/type). Impura. |
|
||||
@@ -207,6 +218,22 @@ un error accionable, sin lanzar.
|
||||
| [comfyui_list_templates_py_ml](../../python/functions/ml/comfyui_list_templates.md) | `list_templates(comfyui_python=None, bundle=None, name_filter=None, with_nodes=True, workflows_only=True, limit=0) -> dict` | Lista los templates oficiales con su grafo: nombre, bundle/categoría, path en disco, `n_nodes` y `node_types` (class_types reales, aplanando subgrafos y descartando UUID de instancia). Filtra por bundle/nombre; excluye entradas no-workflow por defecto. Impura (lee disco vía el intérprete de ComfyUI). |
|
||||
| [comfyui_extract_template_py_ml](../../python/functions/ml/comfyui_extract_template.md) | `extract_template(name, comfyui_python=None, to_api=False, server='127.0.0.1:8188') -> dict` | Extrae el grafo completo (formato UI) + `class_types` de un template por su `template_id`. `to_api=True` lo convierte a API format vía `comfyui_import_workflow_json` (requiere servidor ComfyUI vivo). Nombre inexistente → `ok=False` con sugerencias cercanas, sin traceback. Impura. |
|
||||
|
||||
### Estilos — presets y catálogo (sub-grupo `comfyui-styles`)
|
||||
|
||||
Capa de **estilo reutilizable** sobre los builders: un catálogo curado de ~190 modificadores de
|
||||
estilo para el selector WAS (Prompt Styles Selector), generación de estilos por LLM, y *style
|
||||
presets* gamedev (gameboy, ghibli, pixel-art-retro) que empaquetan como datos puros el look de un
|
||||
juego entero (prefijo/sufijo de prompt, checkpoint, LoRA, negative, tamaño). Página madre dedicada:
|
||||
[comfyui-styles.md](comfyui-styles.md). Las 5 funciones:
|
||||
|
||||
| ID | Firma corta | Qué hace |
|
||||
|---|---|---|
|
||||
| [comfyui_curated_styles_catalog_py_ml](../../python/functions/ml/comfyui_curated_styles_catalog.md) | `curated_styles_catalog(category=None) -> dict` | Catálogo curado (~190 estilos) en formato `{nombre: {prompt, negative_prompt}}` para el selector WAS. Cada prompt son modificadores potentes (cámara, lente, iluminación, render, medio, paleta). **Pura**. |
|
||||
| [comfyui_generate_styles_llm_py_ml](../../python/functions/ml/comfyui_generate_styles_llm.md) | `generate_styles_llm(category, n=8, prefix='', avoid=None, model='claude-haiku-4-5-...') -> dict` | Genera N estilos nuevos de una categoría temática vía `ask_llm` (grupo claude-direct), en el mismo formato del selector WAS. **Impura**. |
|
||||
| [comfyui_append_styles_py_ml](../../python/functions/ml/comfyui_append_styles.md) | `append_styles(new_styles, styles_path=..., overwrite=False, backup=True, dry_run=False) -> dict` | Fusiona (merge + dedup) estilos nuevos sobre el `styles.json` del selector WAS de forma NO destructiva: preserva los existentes (salvo `overwrite`), backup con timestamp. **Impura**. |
|
||||
| [comfyui_get_gamedev_style_preset_py_ml](../../python/functions/ml/comfyui_get_gamedev_style_preset.md) | `get_gamedev_style_preset(name=None) -> dict` | Devuelve la receta de un *style preset* gamedev curado (gameboy, ghibli, pixel-art-retro) o el catálogo de nombres si `name=None`. Empaqueta el look como datos puros. **Pura**. |
|
||||
| [comfyui_apply_style_preset_py_ml](../../python/functions/ml/comfyui_apply_style_preset.md) | `apply_style_preset(preset, subject, *, style=None, negative=None) -> dict` | Traduce un *style preset* gamedev + un subject del usuario a los kwargs que consume un builder de sujeto del grupo gamedev-2d (subject combinado + `**kwargs` listos para spread). **Pura**. |
|
||||
|
||||
## Ejemplo canónico end-to-end (build → load → tune → queue → resultado)
|
||||
|
||||
Combina API + UI: construyes el workflow por API, lo cargas en la UI del usuario, ajustas el
|
||||
|
||||
@@ -11,8 +11,9 @@ Cluster de funciones para producir y mover assets 2D de juego entre **ComfyUI**
|
||||
3. **Puente de assets** (CPU): coloca el resultado en un proyecto Godot
|
||||
con sus import settings.
|
||||
|
||||
Tag único del grupo: `gamedev-2d` (los 31 builders de workflow + las 5 funciones de
|
||||
apoyo de post-proceso y puente). El tag plano `gamedev` quedó deprecado y unificado a
|
||||
Tag único del grupo: `gamedev-2d` — **47 funciones**: 36 builders de workflow (31 de
|
||||
generación desde texto + 5 de transformación desde una imagen de entrada) + 11 de apoyo
|
||||
(post-proceso, puente a Godot, style presets y pipelines one-shot). El tag plano `gamedev` quedó deprecado y unificado a
|
||||
`gamedev-2d`. El **runtime de juego C++** (el motor que ejecuta el juego: game loop,
|
||||
cámara, input, render por lotes, audio) vive en el grupo hermano `gamedev-engine`.
|
||||
Filtro: `mcp__registry__fn_search query="" tag="gamedev-2d"`.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -78,6 +78,21 @@ CheckpointLoaderSimple -> ... -> KSampler -> VAEDecode --IMAGE--+-> SaveImage (f
|
||||
`-> DepthAnythingV2Preprocessor -> SaveImage (depth)
|
||||
```
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join("python", "functions"))
|
||||
from ml.comfyui_build_parallax_background_workflow import comfyui_build_parallax_background_workflow
|
||||
|
||||
# Fondo apaisado + su mapa de profundidad, 4 bandas de parallax (función pura, sin red).
|
||||
wf = comfyui_build_parallax_background_workflow("forest at dusk, fantasy", layers=4, seed=7)
|
||||
|
||||
# El dict API format trae DOS SaveImage: el fondo y el depth map. Encólalo con submit_workflow.
|
||||
saves = [n for n in wf.values() if n.get("class_type") == "SaveImage"]
|
||||
print(len(saves), "SaveImage (fondo + depth)") # 2
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Cuando necesites el fondo de un nivel 2D con scroll parallax y quieras las capas
|
||||
|
||||
@@ -5,7 +5,7 @@ lang: py
|
||||
domain: ml
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def comfyui_fetch_civitai_image_meta(image_ref, *, token: str | None = None, timeout: float = 15.0) -> dict"
|
||||
signature: "def comfyui_fetch_civitai_image_meta(image_ref, token: str | None = None, timeout: float = 15.0) -> dict"
|
||||
description: "Recupera los detalles de generacion de una imagen de Civitai por su id o URL (civitai.com/images/<id>): prompt, prompt negativo, modelo, sampler, steps, cfg, seed, dimensiones, recursos (checkpoint + LoRAs) y nivel NSFW. Es el paso 'entrar al link y observar como lo hicieron'. Usa los endpoints tRPC image.getGenerationData + image.get que consume la propia web de civitai.com, porque la API v1 publica (GET /api/v1/images) hoy devuelve meta=null y un JPEG recomprimido sin workflow embebido. Si la meta trae un workflow ComfyUI embebido (campo comfy) lo devuelve en API format. NO descarga la imagen ni reconstruye workflow: solo lee. Impura: HTTP a civitai.com + subprocess (pass para el token)."
|
||||
tags: [comfyui, civitai, replicate, ml, metadata, http, stable-diffusion]
|
||||
uses_functions: []
|
||||
|
||||
@@ -128,15 +128,15 @@ def _extract_comfy_workflow(meta):
|
||||
return {}
|
||||
|
||||
|
||||
def comfyui_fetch_civitai_image_meta(image_ref, *, token=None, timeout=15.0):
|
||||
def comfyui_fetch_civitai_image_meta(image_ref, token=None, timeout=15.0):
|
||||
"""Recupera los detalles de generación de una imagen de Civitai por id/URL.
|
||||
|
||||
Args:
|
||||
image_ref: id numérico de la imagen (int o str), o su URL
|
||||
`https://civitai.com/images/<id>` (con o sin query string).
|
||||
token: API token de Civitai (header Authorization Bearer). Si None se
|
||||
resuelve de `pass civitai/api-token`. No hardcodear. keyword-only.
|
||||
timeout: timeout HTTP en segundos por petición. keyword-only.
|
||||
resuelve de `pass civitai/api-token`. No hardcodear.
|
||||
timeout: timeout HTTP en segundos por petición.
|
||||
|
||||
Returns:
|
||||
dict {ok, image_id, meta, resources, process, comfy_workflow, width,
|
||||
|
||||
@@ -5,7 +5,7 @@ lang: py
|
||||
domain: ml
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def comfyui_import_workflow_png(png_path_or_url: str, *, timeout: float = 15.0) -> dict"
|
||||
signature: "def comfyui_import_workflow_png(png_path_or_url: str, timeout: float = 15.0) -> dict"
|
||||
description: "Extrae el workflow embebido en los chunks de texto de un PNG de ComfyUI. Lee el chunk 'prompt' (API format) y/o 'workflow' (UI graph) de los chunks tEXt/zTXt/iTXt con stdlib (struct, zlib). Acepta path local o URL. Impura: red opcional + lectura de disco."
|
||||
tags: [comfyui, ml, import, png, workflow, stable-diffusion]
|
||||
uses_functions: []
|
||||
|
||||
@@ -14,12 +14,12 @@ import urllib.request
|
||||
import zlib
|
||||
|
||||
|
||||
def comfyui_import_workflow_png(png_path_or_url: str, *, timeout: float = 15.0) -> dict:
|
||||
def comfyui_import_workflow_png(png_path_or_url: str, timeout: float = 15.0) -> dict:
|
||||
"""Devuelve el/los workflow(s) embebido(s) en un PNG de ComfyUI.
|
||||
|
||||
Args:
|
||||
png_path_or_url: ruta local de un PNG, o URL http(s) de un PNG.
|
||||
timeout: timeout HTTP en segundos (solo si es URL). keyword-only.
|
||||
timeout: timeout HTTP en segundos (solo si es URL).
|
||||
|
||||
Returns:
|
||||
dict {ok, prompt, workflow, format_detected, error}:
|
||||
|
||||
@@ -5,7 +5,7 @@ lang: py
|
||||
domain: ml
|
||||
version: "1.1.0"
|
||||
purity: impure
|
||||
signature: "def comfyui_interrupt_queue(*, clear_pending: bool = False, server: str = \"127.0.0.1:8188\", timeout: float = 10.0) -> dict"
|
||||
signature: "def comfyui_interrupt_queue(clear_pending: bool = False, server: str = \"127.0.0.1:8188\", timeout: float = 10.0) -> dict"
|
||||
description: "Corta la generacion en curso de ComfyUI (POST /interrupt) y, si clear_pending=True, vacia ademas la cola de pendientes (POST /queue {\"clear\":true}). Consulta GET /queue al final para reportar queue_remaining. Devuelve {ok, interrupted, cleared, queue_remaining, error}. NO lanza excepcion en fallo de red: degrada a {ok: False, error}. /interrupt corta solo el prompt en ejecucion, no vacia los pendientes salvo clear_pending. Impura: HTTP POST + GET, solo stdlib (urllib, json)."
|
||||
tags: [comfyui, ml, queue, interrupt, control, http]
|
||||
uses_functions: []
|
||||
|
||||
@@ -5,7 +5,7 @@ lang: py
|
||||
domain: ml
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def comfyui_list_skills(*, library_dir: str = None, include_nsfw: bool = False) -> dict"
|
||||
signature: "def comfyui_list_skills(library_dir: str = None, include_nsfw: bool = False) -> dict"
|
||||
description: "Lista las skills ComfyUI guardadas en la libreria de disco con su metadata de resumen: slug, title, base_workflow, version, score_mean/score_n y nsfw (de provenance.nsfw), mas n_versions. Respeta include_nsfw=False (oculta las NSFW por defecto). Libreria inexistente o vacia -> lista vacia sin error. library_dir default ~/ComfyUI/skills_library."
|
||||
error_type: error_go_core
|
||||
tags: [comfyui, comfyui-skill, ml, skill, library]
|
||||
|
||||
@@ -28,13 +28,12 @@ def _n_versions(skill_dir):
|
||||
if f.startswith("v") and f.endswith(".json")])
|
||||
|
||||
|
||||
def comfyui_list_skills(*, library_dir: str = None, include_nsfw: bool = False) -> dict:
|
||||
def comfyui_list_skills(library_dir: str = None, include_nsfw: bool = False) -> dict:
|
||||
"""Lista las skills de la libreria con su metadata de resumen.
|
||||
|
||||
Args:
|
||||
library_dir: raiz de la libreria. Default `~/ComfyUI/skills_library`. keyword-only.
|
||||
library_dir: raiz de la libreria. Default `~/ComfyUI/skills_library`.
|
||||
include_nsfw: si False (default), oculta las skills con `provenance.nsfw == True`.
|
||||
keyword-only.
|
||||
|
||||
Returns:
|
||||
dict ``{ok, skills, count, error}`` donde `skills` es una lista de dicts
|
||||
|
||||
@@ -5,7 +5,7 @@ lang: py
|
||||
domain: ml
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def comfyui_load_skill(slug: str, *, version=None, library_dir: str = None) -> dict"
|
||||
signature: "def comfyui_load_skill(slug: str, version=None, library_dir: str = None) -> dict"
|
||||
description: "Lee una receta de skill ComfyUI de la libreria de disco: recipe.json (version actual) o un snapshot versions/vN.json. Hermana inversa de comfyui_save_skill; el round-trip save(recipe)->load(slug) devuelve un dict identico. library_dir default ~/ComfyUI/skills_library. Slug, version o archivo inexistente -> {ok:False} sin lanzar."
|
||||
error_type: error_go_core
|
||||
tags: [comfyui, comfyui-skill, ml, skill, library]
|
||||
|
||||
@@ -36,14 +36,14 @@ def _version_filename(version):
|
||||
return None
|
||||
|
||||
|
||||
def comfyui_load_skill(slug: str, *, version=None, library_dir: str = None) -> dict:
|
||||
def comfyui_load_skill(slug: str, version=None, library_dir: str = None) -> dict:
|
||||
"""Lee la receta de una skill (version actual o un snapshot concreto).
|
||||
|
||||
Args:
|
||||
slug: slug de la skill (nombre de su carpeta en la libreria).
|
||||
version: si None, lee `recipe.json` (version actual). Si se pasa (int, "1" o
|
||||
"v1"), lee el snapshot `versions/vN.json`. keyword-only.
|
||||
library_dir: raiz de la libreria. Default `~/ComfyUI/skills_library`. keyword-only.
|
||||
"v1"), lee el snapshot `versions/vN.json`.
|
||||
library_dir: raiz de la libreria. Default `~/ComfyUI/skills_library`.
|
||||
|
||||
Returns:
|
||||
dict ``{ok, recipe, slug, path, version, error}``. En exito ``ok=True`` y `recipe`
|
||||
|
||||
@@ -5,7 +5,7 @@ lang: py
|
||||
domain: ml
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def comfyui_save_skill(recipe: dict, *, library_dir: str = None) -> dict"
|
||||
signature: "def comfyui_save_skill(recipe: dict, library_dir: str = None) -> dict"
|
||||
description: "Persiste una receta de skill ComfyUI (schema comfyui-skill) en la libreria de disco: valida el schema minimo y escribe <library_dir>/<slug>/recipe.json + un snapshot inmutable versions/vN.json (N incremental) + bitacora growth_log.jsonl + regenera INDEX.md. No muta la receta (round-trip identico con comfyui_load_skill). library_dir default ~/ComfyUI/skills_library. Devuelve dict {ok, slug, path, version_file, n_versions, error}; nunca lanza."
|
||||
error_type: error_go_core
|
||||
tags: [comfyui, comfyui-skill, ml, skill, library, persistence]
|
||||
|
||||
@@ -91,13 +91,13 @@ def _rewrite_index(lib):
|
||||
fh.write("\n".join(lines))
|
||||
|
||||
|
||||
def comfyui_save_skill(recipe: dict, *, library_dir: str = None) -> dict:
|
||||
def comfyui_save_skill(recipe: dict, library_dir: str = None) -> dict:
|
||||
"""Valida y persiste una receta de skill en la libreria de disco.
|
||||
|
||||
Args:
|
||||
recipe: dict de la receta (schema `comfyui-skill`). Requiere al menos `slug`,
|
||||
`base_workflow` y `version` (strings no vacios). No se muta.
|
||||
library_dir: raiz de la libreria. Default `~/ComfyUI/skills_library`. keyword-only.
|
||||
library_dir: raiz de la libreria. Default `~/ComfyUI/skills_library`.
|
||||
|
||||
Returns:
|
||||
dict ``{ok, slug, path, recipe_path, version_file, n_versions, error}``. En error de
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
---
|
||||
name: comfyui_generate_until_quality
|
||||
kind: pipeline
|
||||
lang: py
|
||||
domain: pipelines
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "comfyui_generate_until_quality(builder, subject, *, threshold=6.0, clip_threshold=0.24, max_iters=4, strategy='reroll+escalate+refine_prompt', server='127.0.0.1:8188', dest_dir='~/ComfyUI/output', judge_prompt=None, seed=0, refine_model='claude-haiku-4-5-20251001', judge_model='claude-opus-4-8', wait_timeout=300.0, **builder_kwargs) -> dict"
|
||||
description: "Loop evaluator-optimizer (GAN sin entrenar): genera una imagen con un builder del registry, la juzga con el panel multi-juez, y si no alcanza la calidad pedida refina (nueva seed, mas calidad, prompt corregido con el feedback del juez) y regenera hasta pasar el umbral o agotar intentos. Siempre devuelve la mejor candidata por score (best-of-N)."
|
||||
tags: [comfyui, comfyui-skill, pipeline, launcher, generate, judge, quality-loop, evaluator-optimizer]
|
||||
uses_functions:
|
||||
- comfyui_submit_workflow_py_ml
|
||||
- comfyui_wait_result_py_ml
|
||||
- comfyui_fetch_output_image_py_ml
|
||||
- comfyui_judge_image_py_ml
|
||||
- ask_llm_py_core
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: error_py_core
|
||||
imports: [comfyui_submit_workflow_py_ml, comfyui_wait_result_py_ml, comfyui_fetch_output_image_py_ml, comfyui_judge_image_py_ml, ask_llm_py_core]
|
||||
params:
|
||||
- name: builder
|
||||
desc: "Callable o nombre (str) de un builder comfyui_build_*_workflow del registry. El subject se pasa como primer positional (builders de asset: ui_hud, item_icon, enemy_creature...)."
|
||||
- name: subject
|
||||
desc: "Descripcion del elemento a generar (p.ej. 'RPG health and mana bars'). Se inyecta en el builder y, si se refina, se reescribe con el feedback del juez."
|
||||
- name: threshold
|
||||
desc: "Umbral estetico 0-10 que el juez usa para votar good/bad."
|
||||
- name: clip_threshold
|
||||
desc: "Umbral de fidelidad CLIP 0-1 del juez (prompt<->imagen)."
|
||||
- name: max_iters
|
||||
desc: "Numero maximo de iteraciones de generacion."
|
||||
- name: strategy
|
||||
desc: "Tacticas de mejora separadas por '+': reroll (seed nueva), escalate (mas steps/cfg en iters tardias), refine_prompt (reescribe el subject con ask_llm usando las razones del juez)."
|
||||
- name: server
|
||||
desc: "host:port del servidor ComfyUI sin esquema."
|
||||
- name: dest_dir
|
||||
desc: "Directorio local donde guardar los PNG."
|
||||
- name: judge_prompt
|
||||
desc: "Texto que se pasa al juez para medir fidelidad. None = se extrae el positive del workflow construido."
|
||||
- name: seed
|
||||
desc: "Semilla base; los rerolls derivan de ella de forma determinista."
|
||||
- name: refine_model
|
||||
desc: "Modelo de ask_llm para el refine del prompt (barato, haiku por defecto)."
|
||||
- name: judge_model
|
||||
desc: "Modelo del juez critico LLM-vision."
|
||||
- name: wait_timeout
|
||||
desc: "Segundos maximos esperando cada generacion."
|
||||
- name: builder_kwargs
|
||||
desc: "Parametros extra del builder (ui_style, checkpoint, size, transparent...). Solo se pasan los que el builder acepta (filtrados por inspect.signature)."
|
||||
output: "dict {ok, converged, best_image_path, best_score, best_verdict, iterations, error}. iterations = lista de {iter, seed, params, score, verdict, reasons, image, error}. converged=True si alguna iteracion logro verdict 'good'. best_* apuntan a la mejor candidata por score aunque ninguna convergiera."
|
||||
file_path: "python/functions/pipelines/comfyui_generate_until_quality.py"
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
---
|
||||
|
||||
# comfyui_generate_until_quality
|
||||
|
||||
Loop **evaluator-optimizer** sobre ComfyUI: el patrón de una GAN (generador vs.
|
||||
discriminador) pero **sin entrenar nada**. Un builder genera una imagen, el panel
|
||||
multi-juez (`comfyui_judge_image`) la puntúa, y si no llega al umbral el pipeline
|
||||
**refina** (nueva seed, más calidad, prompt corregido con las quejas del juez) y
|
||||
regenera, hasta converger (`verdict == 'good'`) o agotar `max_iters`. Devuelve
|
||||
**siempre la mejor candidata por score** (best-of-N): nunca basura por agotar
|
||||
intentos.
|
||||
|
||||
Es la promoción a pipeline one-shot (issue 0087) del bucle de mejora del grupo
|
||||
`comfyui-skill`: build → submit → wait → fetch → judge → (refine) → repeat.
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
import sys, json
|
||||
sys.path.insert(0, "python/functions")
|
||||
from pipelines.comfyui_generate_until_quality import comfyui_generate_until_quality
|
||||
|
||||
res = comfyui_generate_until_quality(
|
||||
"comfyui_build_ui_hud_workflow", # builder por nombre
|
||||
"RPG health and mana bars, clean game UI", # subject
|
||||
ui_style="fantasy game UI, clean vector, high contrast, sharp edges",
|
||||
threshold=6.5, max_iters=3,
|
||||
dest_dir="/tmp/comfy_until_quality", transparent=False, seed=1000,
|
||||
)
|
||||
print(res["converged"], round(res["best_score"], 2), res["best_verdict"])
|
||||
print("scores:", [it["score"] for it in res["iterations"]]) # historial subiendo
|
||||
print("mejor imagen:", res["best_image_path"])
|
||||
```
|
||||
|
||||
```bash
|
||||
# Lanzar directo (caso HUD del ejemplo __main__)
|
||||
~/fn_registry/python/.venv/bin/python3 \
|
||||
python/functions/pipelines/comfyui_generate_until_quality.py
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
- Cuando pides un asset (HUD, icono, sprite) y la primera generación sale
|
||||
borrosa/floja y quieres que el sistema **itere solo** hasta una versión usable,
|
||||
en vez de re-tirar seeds a mano.
|
||||
- Cuando quieres un **gate de calidad objetivo** que devuelva lo mejor de N
|
||||
intentos rankeado por el panel multi-juez, no la primera que salga.
|
||||
- Como bloque del bucle reactivo del grupo `comfyui-skill`: un skill no está
|
||||
"hecho" hasta que su imagen pasa el panel; este pipeline es ese bucle.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **Impuro**: red (HTTP a ComfyUI), GPU (generación), disco (PNG), API
|
||||
(juez crítico LLM + refine de prompt). Necesita ComfyUI vivo en `server` y el
|
||||
venv de jueces (`~/ComfyUI/.venv`, ver `comfyui-judge`).
|
||||
- **El `subject` se pasa como PRIMER positional del builder**. Vale para los
|
||||
builders de asset (`comfyui_build_ui_hud_workflow`, `_item_icon_`,
|
||||
`_enemy_creature_`...), cuyo primer arg es el elemento. NO para
|
||||
`comfyui_build_txt2img_workflow` (primer arg = `ckpt`): para texto crudo, envuélvelo
|
||||
o pasa un builder de asset.
|
||||
- **Filtra kwargs con `inspect.signature`**: solo pasa al builder los que acepta,
|
||||
así `escalate` (sube `steps`/`cfg`) y `reroll` (set `seed`) no rompen entre
|
||||
builders con firmas distintas. Si un builder no expone `steps`/`seed`, esa
|
||||
táctica simplemente no aplica en él.
|
||||
- **`escalate` sube `steps`+`cfg`**, no inyecta hires-fix (no todos los builders
|
||||
lo soportan y ui_hud lleva Rembg). Para upscale dedicado, usar
|
||||
`comfyui_build_hires_fix_workflow` como builder.
|
||||
- **Degrada con gracia**: si el juez cae (HTTP 429) la imagen se conserva con
|
||||
score 0/verdict 'unknown' y el loop sigue; si una iteración falla en
|
||||
submit/wait/fetch se registra su `error` y se reintenta la siguiente. Solo
|
||||
devuelve `ok=False` si NINGUNA iteración produjo imagen.
|
||||
- **VRAM (8GB)**: entre familias de generación, liberar con
|
||||
`POST /free {"unload_models":true,"free_memory":true}` si el juez estético
|
||||
(CLIP+LAION en el venv ComfyUI) compite por VRAM con el checkpoint SD.
|
||||
- **Determinista en estructura**: nunca lanza excepción cruda; siempre dict de
|
||||
estado. El refine usa `ask_llm` (best-effort): si falla, mantiene el subject.
|
||||
@@ -0,0 +1,349 @@
|
||||
"""comfyui_generate_until_quality — loop evaluator-optimizer (GAN sin entrenar).
|
||||
|
||||
Genera una imagen con un builder del registry, la juzga con el panel multi-juez
|
||||
(`comfyui_judge_image`), y si no alcanza la calidad pedida REFINA (nueva seed,
|
||||
mas calidad, prompt corregido con el feedback del juez) y regenera, hasta que
|
||||
pasa el umbral (`verdict == 'good'`) o se agotan los intentos. Siempre devuelve
|
||||
la MEJOR candidata por score (best-of-N): nunca devuelve basura por agotar
|
||||
iteraciones.
|
||||
|
||||
Es la doctrina del issue 0087 (promover una secuencia repetida a un pipeline
|
||||
one-shot) aplicada al bucle de mejora del grupo `comfyui-skill`: build -> submit
|
||||
-> wait -> fetch -> judge -> (refine) -> repeat. Compone funciones del registry:
|
||||
|
||||
<builder>_py_ml (workflow de nodos en API format)
|
||||
comfyui_submit_workflow_py_ml (POST /prompt)
|
||||
comfyui_wait_result_py_ml (poll /history)
|
||||
comfyui_fetch_output_image_py_ml (GET /view -> disco)
|
||||
comfyui_judge_image_py_ml (panel estetico + CLIP + critica LLM)
|
||||
ask_llm_py_core (refine del prompt con el feedback)
|
||||
|
||||
Pipeline impuro: red (HTTP), GPU (generacion), disco (PNG), y API (juez critico
|
||||
+ refine de prompt). Determinista en estructura: nunca lanza excepcion cruda,
|
||||
siempre devuelve un dict de estado.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Importa las funciones del registry (mismo arbol python/functions).
|
||||
_FUNCTIONS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if _FUNCTIONS_ROOT not in sys.path:
|
||||
sys.path.insert(0, _FUNCTIONS_ROOT)
|
||||
|
||||
from ml.comfyui_fetch_output_image import comfyui_fetch_output_image
|
||||
from ml.comfyui_judge_image import comfyui_judge_image
|
||||
from ml.comfyui_submit_workflow import comfyui_submit_workflow
|
||||
from ml.comfyui_wait_result import comfyui_wait_result
|
||||
|
||||
|
||||
# Primo grande para derrochar el espacio de seeds entre rerolls de forma
|
||||
# determinista (mismo subject + mismo base_seed -> misma traza de seeds).
|
||||
_SEED_STRIDE = 101_117
|
||||
|
||||
|
||||
def _resolve_builder(builder):
|
||||
"""Devuelve el callable del builder.
|
||||
|
||||
Acepta un callable directo o el nombre de la funcion (string), que se
|
||||
resuelve desde el paquete `ml` (convencion del registry: el modulo se llama
|
||||
igual que la funcion, p.ej. `comfyui_build_ui_hud_workflow`).
|
||||
"""
|
||||
if callable(builder):
|
||||
return builder
|
||||
if isinstance(builder, str):
|
||||
mod = importlib.import_module(f"ml.{builder}")
|
||||
return getattr(mod, builder)
|
||||
raise TypeError(
|
||||
f"builder debe ser callable o str (nombre de funcion ml.*), no {type(builder)}"
|
||||
)
|
||||
|
||||
|
||||
def _extract_positive_prompt(workflow: dict) -> str:
|
||||
"""Extrae el prompt positivo textual del workflow para pasarselo al juez.
|
||||
|
||||
Sigue el input `positive` del KSampler hasta su CLIPTextEncode. Fallback: el
|
||||
CLIPTextEncode con el texto mas largo (heuristica: el positive suele serlo).
|
||||
"""
|
||||
if not isinstance(workflow, dict):
|
||||
return ""
|
||||
for node in workflow.values():
|
||||
if not isinstance(node, dict):
|
||||
continue
|
||||
if node.get("class_type") in ("KSampler", "KSamplerAdvanced"):
|
||||
pos = node.get("inputs", {}).get("positive")
|
||||
if isinstance(pos, list) and pos:
|
||||
tgt = workflow.get(str(pos[0]))
|
||||
if isinstance(tgt, dict) and tgt.get("class_type") == "CLIPTextEncode":
|
||||
txt = tgt.get("inputs", {}).get("text")
|
||||
if isinstance(txt, str) and txt.strip():
|
||||
return txt
|
||||
texts = [
|
||||
n["inputs"]["text"]
|
||||
for n in workflow.values()
|
||||
if isinstance(n, dict)
|
||||
and n.get("class_type") == "CLIPTextEncode"
|
||||
and isinstance(n.get("inputs", {}).get("text"), str)
|
||||
]
|
||||
return max(texts, key=len) if texts else ""
|
||||
|
||||
|
||||
def _builder_default(sig: inspect.Signature, name: str, fallback):
|
||||
"""Default declarado de un parametro del builder, o el fallback dado."""
|
||||
p = sig.parameters.get(name)
|
||||
if p is None or p.default is inspect.Parameter.empty:
|
||||
return fallback
|
||||
return p.default if isinstance(p.default, (int, float)) else fallback
|
||||
|
||||
|
||||
def _refine_subject(subject: str, judge_prompt: str, reasons, model: str) -> str:
|
||||
"""Reescribe el subject corrigiendo lo que el juez senalo, via ask_llm.
|
||||
|
||||
Devuelve el subject mejorado (string corto) o el original si el LLM falla.
|
||||
"""
|
||||
from core.ask_llm import ask_llm
|
||||
|
||||
complaints = "; ".join(str(r) for r in (reasons or []) if r) or "(sin razones)"
|
||||
system = (
|
||||
"Eres un prompt-engineer de generacion de imagenes. Recibes el SUBJECT de "
|
||||
"una imagen rechazada por un juez de calidad y la lista de quejas del juez. "
|
||||
"Devuelve un SUBJECT mejorado y conciso (una frase, en ingles) que conserve la "
|
||||
"intencion original pero corrija las quejas anadiendo descriptores visuales "
|
||||
"concretos (p.ej. 'clean vector UI, sharp edges, high contrast, crisp lines' "
|
||||
"si era borroso). NO escribas explicaciones, NO uses comillas: responde SOLO "
|
||||
"con el subject mejorado."
|
||||
)
|
||||
user = (
|
||||
f"SUBJECT original: {subject}\n"
|
||||
f"Prompt completo generado: {judge_prompt}\n"
|
||||
f"Quejas del juez: {complaints}\n"
|
||||
"SUBJECT mejorado:"
|
||||
)
|
||||
try:
|
||||
out = ask_llm(user, model=model, system=system, echo=False)
|
||||
out = (out or "").strip().strip('"').strip()
|
||||
return out or subject
|
||||
except Exception: # noqa: BLE001 — refine es best-effort; nunca rompe el loop.
|
||||
return subject
|
||||
|
||||
|
||||
def comfyui_generate_until_quality(
|
||||
builder,
|
||||
subject: str,
|
||||
*,
|
||||
threshold: float = 6.0,
|
||||
clip_threshold: float = 0.24,
|
||||
max_iters: int = 4,
|
||||
strategy: str = "reroll+escalate+refine_prompt",
|
||||
server: str = "127.0.0.1:8188",
|
||||
dest_dir: str = "~/ComfyUI/output",
|
||||
judge_prompt: str | None = None,
|
||||
seed: int = 0,
|
||||
refine_model: str = "claude-haiku-4-5-20251001",
|
||||
judge_model: str = "claude-opus-4-8",
|
||||
wait_timeout: float = 300.0,
|
||||
**builder_kwargs,
|
||||
) -> dict:
|
||||
"""Genera y refina hasta alcanzar la calidad pedida (o agotar intentos).
|
||||
|
||||
Args:
|
||||
builder: callable o nombre (str) de un builder `comfyui_build_*_workflow`
|
||||
del registry. El `subject` se pasa como PRIMER positional del builder
|
||||
(caso de los builders de asset: ui_hud, item_icon, enemy_creature...,
|
||||
cuyo primer arg es el elemento/sujeto).
|
||||
subject: descripcion del elemento a generar (p.ej. "RPG health and mana
|
||||
bars" para `comfyui_build_ui_hud_workflow`). Se inyecta en el builder
|
||||
y, si se refina, se reescribe con el feedback del juez.
|
||||
threshold: umbral estetico (0-10) que el juez usa para votar good/bad.
|
||||
keyword-only.
|
||||
clip_threshold: umbral de fidelidad CLIP (0-1) del juez. keyword-only.
|
||||
max_iters: numero maximo de iteraciones de generacion. keyword-only.
|
||||
strategy: combinacion de tacticas de mejora separadas por '+':
|
||||
'reroll' (seed nueva cada iter), 'escalate' (mas steps/cfg en iters
|
||||
tardias) y 'refine_prompt' (reescribe el subject con ask_llm usando
|
||||
las razones del juez). keyword-only.
|
||||
server: host:port del servidor ComfyUI (sin esquema). keyword-only.
|
||||
dest_dir: directorio local donde guardar los PNG. keyword-only.
|
||||
judge_prompt: texto que se pasa al juez para medir fidelidad. Si None,
|
||||
se extrae el prompt positivo del workflow construido. keyword-only.
|
||||
seed: semilla base; los rerolls derivan de ella de forma determinista.
|
||||
keyword-only.
|
||||
refine_model: modelo de ask_llm para el refine del prompt (barato).
|
||||
judge_model: modelo del juez critico LLM-vision. keyword-only.
|
||||
wait_timeout: segundos maximos esperando cada generacion. keyword-only.
|
||||
**builder_kwargs: parametros extra del builder (ui_style, checkpoint,
|
||||
size, transparent...). Solo se pasan los que el builder acepta.
|
||||
|
||||
Returns:
|
||||
dict {ok, converged, best_image_path, best_score, best_verdict,
|
||||
iterations, error}. `iterations` es una lista de
|
||||
{iter, seed, params, score, verdict, reasons, image, error}. `converged`
|
||||
True si alguna iteracion logro verdict 'good'. `best_*` apuntan a la
|
||||
candidata de mayor score (aunque ninguna convergiera). Si nada se pudo
|
||||
generar, ok=False y error explica.
|
||||
"""
|
||||
parts = {p.strip() for p in str(strategy).split("+") if p.strip()}
|
||||
do_reroll = "reroll" in parts
|
||||
do_escalate = "escalate" in parts
|
||||
do_refine = "refine_prompt" in parts
|
||||
|
||||
try:
|
||||
builder_fn = _resolve_builder(builder)
|
||||
except (ImportError, AttributeError, TypeError) as exc:
|
||||
return {
|
||||
"ok": False, "converged": False, "best_image_path": "",
|
||||
"best_score": None, "best_verdict": "", "iterations": [],
|
||||
"error": f"no se pudo resolver el builder: {exc}",
|
||||
}
|
||||
|
||||
sig = inspect.signature(builder_fn)
|
||||
accepts = set(sig.parameters)
|
||||
base_steps = builder_kwargs.get("steps", _builder_default(sig, "steps", 28))
|
||||
base_cfg = builder_kwargs.get("cfg", _builder_default(sig, "cfg", 7.0))
|
||||
prefix = builder_kwargs.get("filename_prefix", "until_quality")
|
||||
|
||||
dest = os.path.expanduser(dest_dir)
|
||||
subject_cur = subject
|
||||
iterations: list[dict] = []
|
||||
best: dict | None = None
|
||||
converged = False
|
||||
|
||||
for i in range(max(1, int(max_iters))):
|
||||
# --- parametros de esta iteracion segun la estrategia ---
|
||||
cur_seed = (seed + i * _SEED_STRIDE) if do_reroll else seed
|
||||
kw = dict(builder_kwargs)
|
||||
if "seed" in accepts:
|
||||
kw["seed"] = cur_seed
|
||||
if do_escalate and i > 0:
|
||||
if "steps" in accepts:
|
||||
kw["steps"] = int(base_steps) + i * 8 # mas pasos = mas nitidez
|
||||
if "cfg" in accepts:
|
||||
kw["cfg"] = round(min(float(base_cfg) + i * 0.5, 12.0), 2)
|
||||
if "filename_prefix" in accepts:
|
||||
kw["filename_prefix"] = f"{prefix}_i{i}"
|
||||
# Solo pasamos kwargs que el builder acepta (evita TypeError entre builders).
|
||||
kw = {k: v for k, v in kw.items() if k in accepts}
|
||||
params = {
|
||||
"seed": cur_seed,
|
||||
"steps": kw.get("steps", base_steps),
|
||||
"cfg": kw.get("cfg", base_cfg),
|
||||
"subject": subject_cur,
|
||||
}
|
||||
|
||||
rec = {"iter": i, "seed": cur_seed, "params": params, "score": None,
|
||||
"verdict": "", "reasons": [], "image": "", "error": ""}
|
||||
|
||||
# --- build ---
|
||||
try:
|
||||
workflow = builder_fn(subject_cur, **kw)
|
||||
except Exception as exc: # noqa: BLE001 — registra y reintenta siguiente iter.
|
||||
rec["error"] = f"build fallo: {exc}"
|
||||
iterations.append(rec)
|
||||
continue
|
||||
|
||||
jp = judge_prompt if judge_prompt else _extract_positive_prompt(workflow)
|
||||
|
||||
# --- submit ---
|
||||
try:
|
||||
sub = comfyui_submit_workflow(workflow, server=server)
|
||||
prompt_id = sub["prompt_id"]
|
||||
except (RuntimeError, KeyError) as exc:
|
||||
rec["error"] = f"submit fallo: {exc}"
|
||||
iterations.append(rec)
|
||||
continue
|
||||
|
||||
# --- wait ---
|
||||
try:
|
||||
outputs = comfyui_wait_result(prompt_id, server=server, timeout=wait_timeout)
|
||||
except (TimeoutError, RuntimeError) as exc:
|
||||
rec["error"] = f"wait fallo: {exc}"
|
||||
iterations.append(rec)
|
||||
continue
|
||||
|
||||
# --- localizar el PNG ---
|
||||
img = None
|
||||
for node_out in outputs.values():
|
||||
images = node_out.get("images") if isinstance(node_out, dict) else None
|
||||
if images:
|
||||
img = images[0]
|
||||
break
|
||||
if img is None:
|
||||
rec["error"] = f"el workflow no produjo imagenes (outputs={list(outputs)})"
|
||||
iterations.append(rec)
|
||||
continue
|
||||
|
||||
# --- fetch ---
|
||||
fetched = comfyui_fetch_output_image(
|
||||
img["filename"], subfolder=img.get("subfolder", ""),
|
||||
type_=img.get("type", "output"), server=server, dest_dir=dest,
|
||||
)
|
||||
if not fetched.get("ok"):
|
||||
rec["error"] = f"fetch fallo: {fetched.get('error')}"
|
||||
iterations.append(rec)
|
||||
continue
|
||||
rec["image"] = fetched["path"]
|
||||
|
||||
# --- judge (degrada con gracia si un juez cae) ---
|
||||
try:
|
||||
verdict = comfyui_judge_image(
|
||||
fetched["path"], jp, threshold=threshold,
|
||||
clip_threshold=clip_threshold, server=server, model=judge_model,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001 — un juez caido no debe tumbar el loop.
|
||||
verdict = {"ok": False, "verdict": "unknown", "score": 0.0,
|
||||
"reasons": [f"juez no disponible: {exc}"]}
|
||||
|
||||
rec["score"] = float(verdict.get("score") or 0.0)
|
||||
rec["verdict"] = verdict.get("verdict", "unknown")
|
||||
rec["reasons"] = list(verdict.get("reasons") or [])
|
||||
iterations.append(rec)
|
||||
|
||||
# --- best-of-N: guarda siempre la mejor por score ---
|
||||
if best is None or rec["score"] > best["score"]:
|
||||
best = rec
|
||||
|
||||
# --- convergencia ---
|
||||
if rec["verdict"] == "good":
|
||||
converged = True
|
||||
break
|
||||
|
||||
# --- refine para la siguiente iteracion ---
|
||||
if do_refine and i < max_iters - 1:
|
||||
subject_cur = _refine_subject(subject_cur, jp, rec["reasons"], refine_model)
|
||||
|
||||
if best is None:
|
||||
last_err = iterations[-1]["error"] if iterations else "sin iteraciones"
|
||||
return {
|
||||
"ok": False, "converged": False, "best_image_path": "",
|
||||
"best_score": None, "best_verdict": "", "iterations": iterations,
|
||||
"error": f"ninguna iteracion produjo imagen ({last_err})",
|
||||
}
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"converged": converged,
|
||||
"best_image_path": best["image"],
|
||||
"best_score": best["score"],
|
||||
"best_verdict": best["verdict"],
|
||||
"iterations": iterations,
|
||||
"error": "",
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import json
|
||||
|
||||
res = comfyui_generate_until_quality(
|
||||
"comfyui_build_ui_hud_workflow",
|
||||
"RPG health and mana bars, clean game UI",
|
||||
ui_style="fantasy game UI, clean vector, high contrast",
|
||||
threshold=6.5,
|
||||
max_iters=3,
|
||||
dest_dir="/tmp/comfy_until_quality",
|
||||
transparent=False,
|
||||
)
|
||||
print(json.dumps(res, indent=2))
|
||||
Reference in New Issue
Block a user