feat(ml): comfyui_build_flux_workflow builder txt2img Flux (API format)
Builder puro hermano de comfyui_build_txt2img_workflow para modelos Flux (schnell/dev): UNETLoader + DualCLIPLoader (clip_l + t5xxl, type flux) + VAELoader -> CLIPTextEncode -> FluxGuidance + EmptySD3LatentImage -> KSampler (cfg fijo 1.0) -> VAEDecode -> SaveImage. La guia va por FluxGuidance, no por el cfg del sampler. fp8 + ~4 pasos para GPU de 8GB. class_type/inputs verificados contra /object_info del server vivo. Validado end-to-end: genera imagen real (prompt_id 909b8876, flux_builder_test_00001_.png, status success). 6 tests unitarios verde. Pagina madre docs/capabilities/comfyui.md actualizada con la fila del builder. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,7 @@ El **API format** (dict de nodos numerados que produce `build_txt2img_workflow`
|
|||||||
| ID | Firma corta | Qué hace |
|
| 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_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_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_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_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. |
|
| [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. |
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
---
|
||||||
|
name: comfyui_build_flux_workflow
|
||||||
|
kind: function
|
||||||
|
lang: py
|
||||||
|
domain: ml
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: pure
|
||||||
|
signature: "def comfyui_build_flux_workflow(prompt: str, *, unet: str = \"flux1-schnell-fp8-e4m3fn.safetensors\", clip_l: str = \"clip_l.safetensors\", t5xxl: str = \"t5xxl_fp8_e4m3fn_scaled.safetensors\", vae: str = \"ae.safetensors\", width: int = 1024, height: int = 1024, steps: int = 4, guidance: float = 3.5, seed: int = 0, weight_dtype: str = \"fp8_e4m3fn\", sampler_name: str = \"euler\", scheduler: str = \"simple\", filename_prefix: str = \"comfy_flux\") -> dict"
|
||||||
|
description: "Construye el dict de un workflow ComfyUI txt2img con Flux en API format (nodos numerados con class_type + inputs, conexiones como [node_id, output_index]). A diferencia de SD1.5/SDXL, Flux carga por separado UNETLoader + DualCLIPLoader (clip_l + t5xxl, type flux) + VAELoader; la guia va por FluxGuidance (no por el cfg del KSampler, que se fija a 1.0). Cadena: UNETLoader+DualCLIPLoader+VAELoader -> CLIPTextEncode -> FluxGuidance + EmptySD3LatentImage -> KSampler -> VAEDecode -> SaveImage. Pura, sin red ni I/O. Hermana de comfyui_build_txt2img_workflow."
|
||||||
|
tags: [comfyui, flux, ml, txt2img, workflow]
|
||||||
|
uses_functions: []
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: ""
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: prompt
|
||||||
|
desc: "Prompt positivo: lo que se quiere ver en la imagen."
|
||||||
|
- name: unet
|
||||||
|
desc: "Nombre del modelo de difusion en models/diffusion_models/ tal como lo lista comfyui_object_info para UNETLoader (unet_name). Por defecto el Flux schnell fp8. keyword-only."
|
||||||
|
- name: clip_l
|
||||||
|
desc: "Nombre del encoder CLIP-L en models/text_encoders/ (clip_name2 del DualCLIPLoader). Por defecto 'clip_l.safetensors'. keyword-only."
|
||||||
|
- name: t5xxl
|
||||||
|
desc: "Nombre del encoder T5-XXL en models/text_encoders/ (clip_name1 del DualCLIPLoader). Por defecto 't5xxl_fp8_e4m3fn_scaled.safetensors'. keyword-only."
|
||||||
|
- name: vae
|
||||||
|
desc: "Nombre del VAE en models/vae/ (vae_name del VAELoader). Por defecto 'ae.safetensors', el autoencoder de Flux. keyword-only."
|
||||||
|
- name: width
|
||||||
|
desc: "Ancho del latente/imagen en px, multiplo de 16 para SD3/Flux. keyword-only."
|
||||||
|
- name: height
|
||||||
|
desc: "Alto del latente/imagen en px, multiplo de 16 para SD3/Flux. keyword-only."
|
||||||
|
- name: steps
|
||||||
|
desc: "Pasos de sampling del KSampler. Flux schnell rinde con ~4; Flux dev necesita ~20. keyword-only."
|
||||||
|
- name: guidance
|
||||||
|
desc: "Valor del nodo FluxGuidance (no es el cfg clasico). Schnell es poco sensible; dev responde a 3.0-4.0. keyword-only."
|
||||||
|
- name: seed
|
||||||
|
desc: "Semilla del KSampler. 0 es determinista; cambiar para variar la imagen. keyword-only."
|
||||||
|
- name: weight_dtype
|
||||||
|
desc: "dtype de carga del UNET (uno de 'default', 'fp8_e4m3fn', 'fp8_e4m3fn_fast', 'fp8_e5m2'). fp8 reduce VRAM, clave en GPU de 8GB. keyword-only."
|
||||||
|
- name: sampler_name
|
||||||
|
desc: "Nombre del sampler (Flux usa 'euler'). keyword-only."
|
||||||
|
- name: scheduler
|
||||||
|
desc: "Scheduler del sampler (Flux usa 'simple'). keyword-only."
|
||||||
|
- name: filename_prefix
|
||||||
|
desc: "Prefijo del PNG que SaveImage escribe en output/. keyword-only."
|
||||||
|
output: "dict en API format con node_ids como claves (UNETLoader '10', DualCLIPLoader '11', VAELoader '12', CLIPTextEncode positivo '6', FluxGuidance '13', CLIPTextEncode negativo vacio '7', EmptySD3LatentImage '5', KSampler '3', VAEDecode '8', SaveImage '9'). Listo para comfyui_submit_workflow."
|
||||||
|
tested: true
|
||||||
|
tests: ["class_types esperados (9 nodos de Flux)", "loaders separados UNET+DualCLIP(flux)+VAE", "guidance via FluxGuidance y cfg del KSampler fijado a 1.0", "params width/height/steps/seed reflejados", "filename_prefix en SaveImage", "determinismo: misma entrada -> mismo dict (builder puro)"]
|
||||||
|
test_file_path: "python/functions/ml/tests/test_comfyui_build_flux_workflow.py"
|
||||||
|
file_path: "python/functions/ml/comfyui_build_flux_workflow.py"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```python
|
||||||
|
import sys, os
|
||||||
|
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
|
||||||
|
from ml.comfyui_build_flux_workflow import comfyui_build_flux_workflow
|
||||||
|
|
||||||
|
wf = comfyui_build_flux_workflow(
|
||||||
|
prompt="a red apple on a wooden table, sharp focus, studio lighting",
|
||||||
|
width=1024,
|
||||||
|
height=1024,
|
||||||
|
steps=4, # Flux schnell: ~4 pasos basta
|
||||||
|
seed=42,
|
||||||
|
)
|
||||||
|
# wf["10"]["class_type"] == "UNETLoader" # modelo de difusion suelto
|
||||||
|
# wf["11"]["inputs"]["type"] == "flux" # DualCLIPLoader en modo flux
|
||||||
|
# wf["3"]["inputs"]["positive"] == ["13", 0] # KSampler consume FluxGuidance
|
||||||
|
# wf["3"]["inputs"]["cfg"] == 1.0 # la guia va por FluxGuidance
|
||||||
|
# wf["9"]["class_type"] == "SaveImage"
|
||||||
|
```
|
||||||
|
|
||||||
|
O lanzable directo con: `./fn run comfyui_build_flux_workflow` (imprime el JSON del workflow de ejemplo).
|
||||||
|
|
||||||
|
## Cuando usarla
|
||||||
|
|
||||||
|
Cuando vayas a generar txt2img con un modelo Flux (schnell o dev) y necesites el
|
||||||
|
dict del workflow para `comfyui_submit_workflow`. Usala en lugar de
|
||||||
|
`comfyui_build_txt2img_workflow` siempre que el modelo NO sea un checkpoint
|
||||||
|
todo-en-uno SD1.5/SDXL sino Flux con UNET + text encoders + VAE por separado.
|
||||||
|
Flux schnell es ideal en GPU de poca VRAM (8GB) por el fp8 y los ~4 pasos.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- Es API format (nodos numerados), NO el formato de la UI de ComfyUI (graph con
|
||||||
|
links). No se puede pegar en la UI tal cual; es el formato que acepta POST
|
||||||
|
/prompt.
|
||||||
|
- Flux NO usa el cfg del KSampler para guiar: este builder lo fija a 1.0 y la
|
||||||
|
guia va por el nodo FluxGuidance. Subir el cfg del KSampler con Flux degrada o
|
||||||
|
rompe la imagen.
|
||||||
|
- El negativo es un CLIPTextEncode vacio cableado al KSampler (igual que el
|
||||||
|
template oficial de Flux). Flux schnell es destilado y practicamente ignora el
|
||||||
|
negativo; no esperes que un prompt negativo tenga el efecto de SD1.5/SDXL.
|
||||||
|
- `unet`, `clip_l`, `t5xxl` y `vae` deben existir en los directorios respectivos
|
||||||
|
visibles para el servidor (models/diffusion_models/, models/text_encoders/,
|
||||||
|
models/vae/). Si no, ComfyUI rechaza el workflow con HTTP 400 al enviarlo (no
|
||||||
|
aqui — esta funcion es pura y no valida contra el servidor). Valida antes con
|
||||||
|
`comfyui_validate_workflow`.
|
||||||
|
- `width`/`height` deben ser multiplos de 16 para EmptySD3LatentImage (Flux), no
|
||||||
|
de 8 como en SD1.5/SDXL.
|
||||||
|
- `weight_dtype` debe ser uno de los que admite UNETLoader ('default',
|
||||||
|
'fp8_e4m3fn', 'fp8_e4m3fn_fast', 'fp8_e5m2'). En 8GB usa fp8 o el modelo no
|
||||||
|
cabe en VRAM.
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
"""Construye un workflow ComfyUI txt2img con Flux en "API format" (dict de nodos numerados).
|
||||||
|
|
||||||
|
API format: cada clave es un node_id (string); cada nodo tiene class_type +
|
||||||
|
inputs. Las conexiones entre nodos son listas [node_id, output_index]. Este es
|
||||||
|
el formato que acepta POST /prompt, distinto del formato de la UI (graph con
|
||||||
|
links explicitos).
|
||||||
|
|
||||||
|
A diferencia del builder SD1.5/SDXL (comfyui_build_txt2img_workflow), Flux NO usa
|
||||||
|
un checkpoint todo-en-uno: carga por separado el modelo de difusion (UNETLoader),
|
||||||
|
los dos text encoders (DualCLIPLoader con clip_l + t5xxl, type="flux") y el VAE
|
||||||
|
(VAELoader). La guia no va por el cfg del KSampler (que se fija a 1.0) sino por el
|
||||||
|
nodo FluxGuidance aplicado al condicionamiento positivo. El negativo se deja como
|
||||||
|
un CLIPTextEncode vacio, igual que el template oficial de Flux en ComfyUI.
|
||||||
|
|
||||||
|
Funcion pura: sin red, sin I/O. Determinista para los mismos argumentos.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def comfyui_build_flux_workflow(
|
||||||
|
prompt: str,
|
||||||
|
*,
|
||||||
|
unet: str = "flux1-schnell-fp8-e4m3fn.safetensors",
|
||||||
|
clip_l: str = "clip_l.safetensors",
|
||||||
|
t5xxl: str = "t5xxl_fp8_e4m3fn_scaled.safetensors",
|
||||||
|
vae: str = "ae.safetensors",
|
||||||
|
width: int = 1024,
|
||||||
|
height: int = 1024,
|
||||||
|
steps: int = 4,
|
||||||
|
guidance: float = 3.5,
|
||||||
|
seed: int = 0,
|
||||||
|
weight_dtype: str = "fp8_e4m3fn",
|
||||||
|
sampler_name: str = "euler",
|
||||||
|
scheduler: str = "simple",
|
||||||
|
filename_prefix: str = "comfy_flux",
|
||||||
|
) -> dict:
|
||||||
|
"""Construye el dict del workflow txt2img de Flux (schnell/dev).
|
||||||
|
|
||||||
|
Cadena de nodos: UNETLoader + DualCLIPLoader + VAELoader -> CLIPTextEncode
|
||||||
|
(positivo) -> FluxGuidance, mas un CLIPTextEncode vacio para el negativo y
|
||||||
|
EmptySD3LatentImage -> KSampler -> VAEDecode -> SaveImage.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prompt: prompt positivo (lo que se quiere ver en la imagen).
|
||||||
|
unet: nombre del modelo de difusion en models/diffusion_models/ tal como
|
||||||
|
lo lista comfyui_object_info para UNETLoader (unet_name). Por defecto
|
||||||
|
el Flux schnell fp8 ("flux1-schnell-fp8-e4m3fn.safetensors").
|
||||||
|
clip_l: nombre del encoder CLIP-L en models/text_encoders/ (clip_name2 del
|
||||||
|
DualCLIPLoader). Por defecto "clip_l.safetensors".
|
||||||
|
t5xxl: nombre del encoder T5-XXL en models/text_encoders/ (clip_name1 del
|
||||||
|
DualCLIPLoader). Por defecto "t5xxl_fp8_e4m3fn_scaled.safetensors".
|
||||||
|
vae: nombre del VAE en models/vae/ (vae_name del VAELoader). Por defecto
|
||||||
|
"ae.safetensors" (el autoencoder de Flux).
|
||||||
|
width: ancho del latente/imagen en px (multiplo de 16 para SD3/Flux). keyword-only.
|
||||||
|
height: alto del latente/imagen en px (multiplo de 16 para SD3/Flux). keyword-only.
|
||||||
|
steps: pasos de sampling del KSampler. Flux schnell rinde bien con ~4;
|
||||||
|
Flux dev necesita ~20. keyword-only.
|
||||||
|
guidance: valor del nodo FluxGuidance (no es el cfg clasico). Schnell es
|
||||||
|
poco sensible a este valor; dev responde a 3.0-4.0. keyword-only.
|
||||||
|
seed: semilla del KSampler (0 = determinista; cambia para variar). keyword-only.
|
||||||
|
weight_dtype: dtype de carga del UNET (uno de "default", "fp8_e4m3fn",
|
||||||
|
"fp8_e4m3fn_fast", "fp8_e5m2"). fp8 reduce VRAM (clave en 8GB). keyword-only.
|
||||||
|
sampler_name: nombre del sampler (Flux usa "euler"). keyword-only.
|
||||||
|
scheduler: scheduler del sampler (Flux usa "simple"). keyword-only.
|
||||||
|
filename_prefix: prefijo del PNG que SaveImage escribe en output/. keyword-only.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict en API format listo para comfyui_submit_workflow. Las claves son
|
||||||
|
node_ids (string) y cada valor tiene class_type + inputs.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"10": {
|
||||||
|
"class_type": "UNETLoader",
|
||||||
|
"inputs": {"unet_name": unet, "weight_dtype": weight_dtype},
|
||||||
|
},
|
||||||
|
"11": {
|
||||||
|
"class_type": "DualCLIPLoader",
|
||||||
|
"inputs": {
|
||||||
|
"clip_name1": t5xxl,
|
||||||
|
"clip_name2": clip_l,
|
||||||
|
"type": "flux",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"12": {
|
||||||
|
"class_type": "VAELoader",
|
||||||
|
"inputs": {"vae_name": vae},
|
||||||
|
},
|
||||||
|
"6": {
|
||||||
|
"class_type": "CLIPTextEncode",
|
||||||
|
"inputs": {"text": prompt, "clip": ["11", 0]},
|
||||||
|
},
|
||||||
|
"13": {
|
||||||
|
"class_type": "FluxGuidance",
|
||||||
|
"inputs": {"conditioning": ["6", 0], "guidance": guidance},
|
||||||
|
},
|
||||||
|
"7": {
|
||||||
|
"class_type": "CLIPTextEncode",
|
||||||
|
"inputs": {"text": "", "clip": ["11", 0]},
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
"class_type": "EmptySD3LatentImage",
|
||||||
|
"inputs": {"width": width, "height": height, "batch_size": 1},
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"class_type": "KSampler",
|
||||||
|
"inputs": {
|
||||||
|
"seed": seed,
|
||||||
|
"steps": steps,
|
||||||
|
"cfg": 1.0,
|
||||||
|
"sampler_name": sampler_name,
|
||||||
|
"scheduler": scheduler,
|
||||||
|
"denoise": 1.0,
|
||||||
|
"model": ["10", 0],
|
||||||
|
"positive": ["13", 0],
|
||||||
|
"negative": ["7", 0],
|
||||||
|
"latent_image": ["5", 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"8": {
|
||||||
|
"class_type": "VAEDecode",
|
||||||
|
"inputs": {"samples": ["3", 0], "vae": ["12", 0]},
|
||||||
|
},
|
||||||
|
"9": {
|
||||||
|
"class_type": "SaveImage",
|
||||||
|
"inputs": {"filename_prefix": filename_prefix, "images": ["8", 0]},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import json
|
||||||
|
|
||||||
|
wf = comfyui_build_flux_workflow(
|
||||||
|
prompt="a red apple on a wooden table, sharp focus, studio lighting",
|
||||||
|
seed=42,
|
||||||
|
)
|
||||||
|
print(json.dumps(wf, indent=2))
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
"""Tests de estructura para comfyui_build_flux_workflow (funcion pura)."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||||
|
|
||||||
|
from ml.comfyui_build_flux_workflow import comfyui_build_flux_workflow
|
||||||
|
from _comfyui_wf_assert import assert_api_format, class_types, node_by_ct
|
||||||
|
|
||||||
|
|
||||||
|
def test_estructura_y_class_types():
|
||||||
|
wf = comfyui_build_flux_workflow("POS")
|
||||||
|
assert_api_format(wf)
|
||||||
|
assert class_types(wf) == {
|
||||||
|
"UNETLoader",
|
||||||
|
"DualCLIPLoader",
|
||||||
|
"VAELoader",
|
||||||
|
"CLIPTextEncode",
|
||||||
|
"FluxGuidance",
|
||||||
|
"EmptySD3LatentImage",
|
||||||
|
"KSampler",
|
||||||
|
"VAEDecode",
|
||||||
|
"SaveImage",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_loaders_separados_de_flux():
|
||||||
|
# Flux carga UNET + dos text encoders + VAE por separado (no checkpoint unico).
|
||||||
|
wf = comfyui_build_flux_workflow(
|
||||||
|
"POS",
|
||||||
|
unet="flux1-schnell-fp8-e4m3fn.safetensors",
|
||||||
|
clip_l="clip_l.safetensors",
|
||||||
|
t5xxl="t5xxl_fp8_e4m3fn_scaled.safetensors",
|
||||||
|
vae="ae.safetensors",
|
||||||
|
weight_dtype="fp8_e4m3fn",
|
||||||
|
)
|
||||||
|
unet = node_by_ct(wf, "UNETLoader")["inputs"]
|
||||||
|
assert unet["unet_name"] == "flux1-schnell-fp8-e4m3fn.safetensors"
|
||||||
|
assert unet["weight_dtype"] == "fp8_e4m3fn"
|
||||||
|
dual = node_by_ct(wf, "DualCLIPLoader")["inputs"]
|
||||||
|
assert dual["type"] == "flux"
|
||||||
|
assert dual["clip_name1"] == "t5xxl_fp8_e4m3fn_scaled.safetensors"
|
||||||
|
assert dual["clip_name2"] == "clip_l.safetensors"
|
||||||
|
assert node_by_ct(wf, "VAELoader")["inputs"]["vae_name"] == "ae.safetensors"
|
||||||
|
|
||||||
|
|
||||||
|
def test_guidance_y_cfg_de_flux():
|
||||||
|
# La guia va por FluxGuidance; el cfg del KSampler se fija a 1.0 (schnell).
|
||||||
|
wf = comfyui_build_flux_workflow("POS", guidance=2.5)
|
||||||
|
assert node_by_ct(wf, "FluxGuidance")["inputs"]["guidance"] == 2.5
|
||||||
|
ks = node_by_ct(wf, "KSampler")["inputs"]
|
||||||
|
assert ks["cfg"] == 1.0
|
||||||
|
# KSampler positive consume la salida de FluxGuidance, no la del CLIPTextEncode directo.
|
||||||
|
assert ks["positive"] == ["13", 0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_params_se_reflejan_en_los_nodos():
|
||||||
|
wf = comfyui_build_flux_workflow("POS", width=768, height=512, steps=8, seed=123)
|
||||||
|
ks = node_by_ct(wf, "KSampler")["inputs"]
|
||||||
|
assert ks["seed"] == 123
|
||||||
|
assert ks["steps"] == 8
|
||||||
|
lat = node_by_ct(wf, "EmptySD3LatentImage")["inputs"]
|
||||||
|
assert lat["width"] == 768 and lat["height"] == 512
|
||||||
|
pos = node_by_ct(wf, "FluxGuidance")["inputs"]["conditioning"]
|
||||||
|
assert pos == ["6", 0] # FluxGuidance aplica sobre el CLIPTextEncode positivo
|
||||||
|
|
||||||
|
|
||||||
|
def test_filename_prefix_en_saveimage():
|
||||||
|
wf = comfyui_build_flux_workflow("POS", filename_prefix="demo_flux")
|
||||||
|
assert node_by_ct(wf, "SaveImage")["inputs"]["filename_prefix"] == "demo_flux"
|
||||||
|
|
||||||
|
|
||||||
|
def test_determinista():
|
||||||
|
# Builder puro: misma entrada -> mismo dict (sin red, seed fijo, sin estado).
|
||||||
|
a = comfyui_build_flux_workflow("POS", seed=123)
|
||||||
|
b = comfyui_build_flux_workflow("POS", seed=123)
|
||||||
|
assert a == b
|
||||||
Reference in New Issue
Block a user