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