Módulo 4: Generación de Imágenes
4. Comparación: DALL-E 3 vs Stable Diffusion
Descripción
Elegir entre DALL-E 3 y Stable Diffusion no es una cuestión de "cuál es mejor" — es una decisión de ingeniería basada en contexto: presupuesto, volumen, nivel de control necesario, velocidad de iteración, y requisitos de reproducibilidad. Esta cápsula te da las herramientas para tomar esa decisión con datos: tabla comparativa detallada, código de benchmark que envía el mismo prompt a ambos, análisis de costos por escenario, y un árbol de decisión programático.
Por qué importa: En producción, es común usar ambos modelos en un solo sistema: DALL-E 3 como generador principal (calidad) y Stable Diffusion como fallback (costo, disponibilidad). Entender sus diferencias te permite diseñar arquitecturas resilientes y optimizar costos sin sacrificar calidad.
Tabla Comparativa Detallada
Criterios técnicos
| Criterio | DALL-E 3 (OpenAI) | Stable Diffusion (SDXL vía Replicate) |
|---|---|---|
| Costo por imagen | $0.04-0.12 | $0.002-0.02 |
| Calidad base | Excelente, consistente | Buena, variable según parámetros |
| Comprensión del prompt | Superior (reescribe + interpreta) | Literal (lo que escribes es lo que genera) |
| Negative prompts | No soportado | Sí, altamente efectivo |
| Control de parámetros | Limitado (size, quality, style) | Extenso (steps, cfg, seed, scheduler, etc.) |
| Reproducibilidad | Baja (no hay seed expuesto) | Alta (seed = determinista) |
| Velocidad | 10-25 segundos | 5-40 segundos (depende del modelo/steps) |
| Resoluciones | 3 fijas (1024², 1792x1024, 1024x1792) | Flexible (múltiplos de 64) |
| Texto en imágenes | Aceptable | Pobre |
| Consistencia entre generaciones | Alta | Media (requiere seed) |
| API | OpenAI oficial, estable | Replicate, Stability AI, múltiples |
| Modelos disponibles | dall-e-3, dall-e-2 | SDXL, SD3, Flux, cientos de variantes |
| Inpainting nativo | Solo con dall-e-2 | Sí, modelos especializados |
| Open source | No | Sí |
| Funciona offline/local | No | Sí (con GPU) |
| Rate limits | Strict (por tier de OpenAI) | Basado en créditos/balance |
| Content policy | Estricta, rechaza prompts sensibles | Más permisiva (depende del hosting) |
| Prompt rewriting | Sí (revised prompt) | No |
Criterios de negocio
| Criterio | DALL-E 3 | Stable Diffusion |
|---|---|---|
| Setup inicial | Mínimo (API key de OpenAI) | Bajo (API key de Replicate) |
| Curva de aprendizaje | Baja (pocos parámetros) | Media (muchos parámetros para optimizar) |
| Escalabilidad de costos | Lineal, predecible | Lineal, mucho más bajo |
| Vendor lock-in | Alto (solo OpenAI) | Bajo (múltiples APIs, ejecución local) |
| Soporte empresarial | Sí (OpenAI Enterprise) | Limitado (Stability AI, o self-hosted) |
| SLA / uptime | 99.9% (OpenAI) | Variable (depende del proveedor) |
| Compliance | SOC 2, datos no usados para training | Depende del hosting (Replicate, self-hosted) |
Benchmark: Mismo Prompt en Ambos Modelos
Setup del benchmark
Este código envía el mismo prompt a DALL-E 3 y Stable Diffusion, mide tiempo y guarda resultados para comparación visual:
import time
import json
import requests
from pathlib import Path
from openai import OpenAI
import replicate
client = OpenAI()
SDXL_MODEL = "stability-ai/sdxl:39ed52f2a40e4be0e682a3e7d0645ef75e93dd3bd23a9c5fe73e589d1a3adc3b"
def benchmark_dalle3(prompt: str, save_path: str) -> dict:
start = time.time()
response = client.images.generate(
model="dall-e-3",
prompt=prompt,
size="1024x1024",
quality="standard",
style="vivid",
n=1
)
elapsed = time.time() - start
url = response.data[0].url
img_data = requests.get(url, timeout=30).content
Path(save_path).parent.mkdir(parents=True, exist_ok=True)
Path(save_path).write_bytes(img_data)
return {
"model": "dall-e-3",
"time_seconds": round(elapsed, 2),
"cost_usd": 0.04,
"revised_prompt": response.data[0].revised_prompt,
"saved_to": save_path,
}
def benchmark_sd(prompt: str, save_path: str) -> dict:
start = time.time()
output = replicate.run(
SDXL_MODEL,
input={
"prompt": prompt,
"negative_prompt": "blurry, low quality, distorted, deformed, ugly, watermark",
"width": 1024,
"height": 1024,
"num_inference_steps": 25,
"guidance_scale": 7.5,
}
)
elapsed = time.time() - start
url = output[0] if isinstance(output, list) else str(output)
img_data = requests.get(url, timeout=30).content
Path(save_path).parent.mkdir(parents=True, exist_ok=True)
Path(save_path).write_bytes(img_data)
return {
"model": "sdxl",
"time_seconds": round(elapsed, 2),
"cost_usd": 0.005,
"saved_to": save_path,
}
Ejecutar benchmark
def run_benchmark(prompts: list[str], output_dir: str = "generated/benchmark") -> list[dict]:
Path(output_dir).mkdir(parents=True, exist_ok=True)
results = []
for i, prompt in enumerate(prompts):
print(f"\n--- Prompt {i+1}/{len(prompts)} ---")
print(f"'{prompt[:80]}...'")
dalle_result = benchmark_dalle3(prompt, f"{output_dir}/dalle_{i:02d}.png")
print(f" DALL-E 3: {dalle_result['time_seconds']}s, ${dalle_result['cost_usd']}")
sd_result = benchmark_sd(prompt, f"{output_dir}/sd_{i:02d}.png")
print(f" SDXL: {sd_result['time_seconds']}s, ${sd_result['cost_usd']}")
results.append({
"prompt": prompt,
"dalle": dalle_result,
"sd": sd_result,
"time_ratio": round(dalle_result["time_seconds"] / max(sd_result["time_seconds"], 0.1), 2),
"cost_ratio": round(dalle_result["cost_usd"] / max(sd_result["cost_usd"], 0.001), 1),
})
report_path = f"{output_dir}/benchmark_report.json"
Path(report_path).write_text(json.dumps(results, indent=2, ensure_ascii=False))
print(f"\nReporte guardado en: {report_path}")
return results
benchmark_prompts = [
"A professional headshot portrait with studio lighting, neutral background",
"A colorful abstract painting with geometric shapes and bold colors",
"A technical architecture diagram showing microservices with arrows",
"A photorealistic landscape of mountains reflected in a lake at sunset",
"A cute cartoon robot holding a book, children's illustration style",
]
results = run_benchmark(benchmark_prompts)
Análisis de Costos por Escenario
Escenarios reales
def cost_analysis(scenario: str, images_per_month: int, dalle_config: str = "standard") -> dict:
dalle_prices = {
"standard": 0.04,
"hd": 0.08,
"landscape_standard": 0.08,
"landscape_hd": 0.12,
}
sd_price = 0.005
dalle_cost = images_per_month * dalle_prices.get(dalle_config, 0.04)
sd_cost = images_per_month * sd_price
savings = dalle_cost - sd_cost
savings_pct = (savings / dalle_cost) * 100 if dalle_cost > 0 else 0
return {
"scenario": scenario,
"images_per_month": images_per_month,
"dalle_monthly": round(dalle_cost, 2),
"sd_monthly": round(sd_cost, 2),
"monthly_savings": round(savings, 2),
"savings_pct": round(savings_pct, 1),
"annual_savings": round(savings * 12, 2),
}
scenarios = [
cost_analysis("Startup - Blog thumbnails", 100),
cost_analysis("E-commerce - Product photos", 500, "hd"),
cost_analysis("Marketing - Campaign creatives", 1000, "landscape_standard"),
cost_analysis("Enterprise - Document diagrams", 5000),
cost_analysis("Platform - User-generated content", 50000),
]
print(f"{'Escenario':<40} {'Imgs/mes':>10} {'DALL-E':>10} {'SD':>10} {'Ahorro':>10} {'Ahorro %':>10}")
print("-" * 90)
for s in scenarios:
print(
f"{s['scenario']:<40} {s['images_per_month']:>10,} "
f"${s['dalle_monthly']:>8,.2f} ${s['sd_monthly']:>8,.2f} "
f"${s['monthly_savings']:>8,.2f} {s['savings_pct']:>9.1f}%"
)
print(f"{'':>40} {'Ahorro anual:':<20} ${s['annual_savings']:>8,.2f}")
Punto de equilibrio: ¿cuándo vale la pena la calidad de DALL-E?
| Factor | Elige DALL-E 3 | Elige Stable Diffusion |
|---|---|---|
| Volumen | < 1,000 imgs/mes | > 1,000 imgs/mes |
| Uso | Brand/enterprise/marketing | Batch, prototipo, experimental |
| Equipo | Sin expertise en tuning de SD | Puede optimizar parámetros |
| Control | No necesita negative prompts | Necesita reproducibilidad, seeds |
| Vendor | Ya usa OpenAI, una sola API | Prefiere independencia de proveedor |
| Edición | No necesita inpainting avanzado | Necesita ControlNet, inpainting |
Árbol de Decisión Programático
Versión simple
def choose_generator(
budget_per_image: float = 0.05,
need_negative_prompt: bool = False,
need_reproducibility: bool = False,
images_per_month: int = 100,
use_case: str = "general",
) -> str:
if need_negative_prompt:
return "sd"
if need_reproducibility:
return "sd"
if budget_per_image < 0.02:
return "sd"
if images_per_month > 5000:
return "sd"
if use_case in ["brand", "product", "enterprise", "marketing"]:
return "dalle"
return "dalle"
Versión con scoring
def choose_generator_scored(
budget_per_image: float = 0.05,
need_negative_prompt: bool = False,
need_reproducibility: bool = False,
need_inpainting: bool = False,
images_per_month: int = 100,
quality_priority: str = "high",
use_case: str = "general",
) -> dict:
dalle_score = 0
sd_score = 0
if budget_per_image >= 0.04:
dalle_score += 2
elif budget_per_image >= 0.02:
dalle_score += 1
sd_score += 1
else:
sd_score += 3
if need_negative_prompt:
sd_score += 3
if need_reproducibility:
sd_score += 2
if need_inpainting:
sd_score += 3
if images_per_month > 5000:
sd_score += 2
elif images_per_month > 1000:
sd_score += 1
quality_scores = {"high": 2, "medium": 0, "low": -1}
dalle_score += quality_scores.get(quality_priority, 0)
use_case_dalle = {"brand", "product", "enterprise", "marketing", "editorial"}
use_case_sd = {"prototype", "batch", "experimental", "inpainting", "gaming"}
if use_case in use_case_dalle:
dalle_score += 2
elif use_case in use_case_sd:
sd_score += 2
recommendation = "dalle" if dalle_score > sd_score else "sd"
confidence = abs(dalle_score - sd_score) / max(dalle_score + sd_score, 1)
return {
"recommendation": recommendation,
"dalle_score": dalle_score,
"sd_score": sd_score,
"confidence": round(confidence, 2),
"reasoning": (
f"DALL-E: {dalle_score} pts, SD: {sd_score} pts. "
f"{'Alta' if confidence > 0.3 else 'Baja'} confianza."
),
}
print(choose_generator_scored(
budget_per_image=0.10,
images_per_month=200,
quality_priority="high",
use_case="brand",
))
print(choose_generator_scored(
budget_per_image=0.01,
images_per_month=10000,
need_negative_prompt=True,
need_reproducibility=True,
quality_priority="medium",
use_case="batch",
))
Sistema de Fallback: DALL-E + Stable Diffusion
El patrón más útil en producción: intentar con DALL-E 3 primero (mejor calidad), y si falla, caer a Stable Diffusion automáticamente.
import time
from openai import BadRequestError, RateLimitError, APIError
def generate_image_with_fallback(
prompt: str,
save_path: str | None = None,
prefer: str = "dalle",
) -> dict:
generators = {
"dalle": _try_dalle,
"sd": _try_sd,
}
order = ["dalle", "sd"] if prefer == "dalle" else ["sd", "dalle"]
for gen_name in order:
result = generators[gen_name](prompt, save_path)
if result["status"] == "success":
result["generator_used"] = gen_name
result["was_fallback"] = gen_name != order[0]
return result
print(f" {gen_name} falló: {result.get('error', 'unknown')}")
return {"status": "error", "error": "Todos los generadores fallaron", "prompt": prompt}
def _try_dalle(prompt: str, save_path: str | None) -> dict:
try:
response = client.images.generate(
model="dall-e-3",
prompt=prompt,
size="1024x1024",
quality="standard",
n=1,
)
url = response.data[0].url
result = {
"status": "success",
"url": url,
"revised_prompt": response.data[0].revised_prompt,
}
if save_path:
img_data = requests.get(url, timeout=30).content
Path(save_path).parent.mkdir(parents=True, exist_ok=True)
Path(save_path).write_bytes(img_data)
result["saved_to"] = save_path
return result
except (BadRequestError, RateLimitError, APIError) as e:
return {"status": "error", "error": str(e)}
def _try_sd(prompt: str, save_path: str | None) -> dict:
try:
output = replicate.run(
SDXL_MODEL,
input={
"prompt": prompt,
"negative_prompt": "blurry, low quality, distorted, deformed",
"width": 1024,
"height": 1024,
"num_inference_steps": 25,
"guidance_scale": 7.5,
}
)
url = output[0] if isinstance(output, list) else str(output)
result = {"status": "success", "url": url}
if save_path:
img_data = requests.get(url, timeout=60).content
Path(save_path).parent.mkdir(parents=True, exist_ok=True)
Path(save_path).write_bytes(img_data)
result["saved_to"] = save_path
return result
except Exception as e:
return {"status": "error", "error": str(e)}
result = generate_image_with_fallback(
"A modern office space with natural lighting and green plants",
save_path="generated/fallback_test.png"
)
print(f"Status: {result['status']}")
print(f"Generator: {result.get('generator_used')}")
print(f"Was fallback: {result.get('was_fallback')}")
Calidad vs Costo: Visualización
def quality_cost_matrix() -> list[dict]:
configs = [
{"name": "DALL-E 3 HD Landscape", "cost": 0.120, "quality": 9.5, "generator": "dalle"},
{"name": "DALL-E 3 HD Square", "cost": 0.080, "quality": 9.0, "generator": "dalle"},
{"name": "DALL-E 3 Std Square", "cost": 0.040, "quality": 8.5, "generator": "dalle"},
{"name": "SD3 (Stability)", "cost": 0.030, "quality": 8.0, "generator": "sd"},
{"name": "SDXL (Replicate)", "cost": 0.005, "quality": 7.5, "generator": "sd"},
{"name": "Flux Dev", "cost": 0.015, "quality": 8.5, "generator": "sd"},
{"name": "Flux Schnell", "cost": 0.003, "quality": 7.0, "generator": "sd"},
{"name": "DALL-E 2", "cost": 0.020, "quality": 6.0, "generator": "dalle"},
]
configs.sort(key=lambda x: x["quality"] / max(x["cost"], 0.001), reverse=True)
print(f"{'Configuración':<25} {'Costo':>8} {'Calidad':>8} {'Calidad/$':>10}")
print("-" * 55)
for c in configs:
ratio = c["quality"] / c["cost"]
bar = "█" * int(ratio / 50)
print(f"{c['name']:<25} ${c['cost']:>6.3f} {c['quality']:>7.1f} {ratio:>9.0f} {bar}")
return configs
quality_cost_matrix()
La relación calidad/precio favorece masivamente a los modelos SD/Flux. DALL-E 3 gana en calidad absoluta, pero con rendimiento decreciente por dólar.
Cuándo Usar Cada Uno: Guía Rápida
Elige DALL-E 3 cuando:
- La calidad visual impacta directamente el revenue (brand, e-commerce premium)
- No tienes tiempo para iterar parámetros (necesitas "generar y listo")
- Ya usas OpenAI y quieres una sola factura/API
- El volumen es bajo-medio (< 1,000/mes) y el costo absoluto es aceptable
- Necesitas la mejor comprensión semántica de prompts complejos
Elige Stable Diffusion cuando:
- El volumen es alto (> 1,000/mes) y el ahorro justifica la complejidad
- Necesitas control fino (negative prompts, seeds, schedulers)
- Necesitas reproducibilidad (mismo seed = misma imagen)
- Tienes capacidades de inpainting o edición avanzada
- No quieres vendor lock-in con OpenAI
- Estás experimentando y necesitas iterar rápido sin preocuparte por costos
Usa ambos (fallback) cuando:
- Construyes un producto donde la disponibilidad es crítica
- Quieres calidad DALL-E para el flujo normal, SD como respaldo
- Diferentes features requieren diferentes generadores (brand → DALL-E, batch → SD)
Troubleshooting
| Problema | Con DALL-E 3 | Con Stable Diffusion |
|---|---|---|
| Imagen borrosa | Cambiar quality="hd" | Subir steps a 30+, guidance a 8 |
| No se parece al prompt | Revisar revised_prompt; ser más explícito | Subir guidance_scale; mejorar prompt |
| Manos deformadas | Agregar "with correct hands" al prompt | Agregar "bad hands, extra fingers" al negative |
| Texto ilegible en imagen | DALL-E 3 es limitado con tipografía | SD es peor; evitar texto en imágenes |
| Content policy rejection | Reformular; ver cápsula 2 (reprompting) | Cambiar a SD que es más permisivo |
| Muy lento | No hay control de velocidad en DALL-E | Reducir steps; usar Flux Schnell |
| Muy caro | Reducir resolución; usar standard | Ya es económico; reducir steps si es necesario |
| Resultados inconsistentes | Esperado (no hay seed en DALL-E 3) | Fijar seed para reproducibilidad |
| Fallback no funciona | Verificar error handling en _try_dalle | Verificar REPLICATE_API_TOKEN |
Ejercicios
Ejercicio 1: Benchmark visual con análisis automático
Implementa un benchmark que envíe 3 prompts a ambos generadores, descargue las imágenes, y genere un reporte JSON con tiempos, costos y paths de las imágenes para comparación manual.
Ver solución
import json
import time
from datetime import datetime
from pathlib import Path
def full_benchmark(prompts: list[str], output_dir: str = "generated/full_benchmark") -> dict:
Path(output_dir).mkdir(parents=True, exist_ok=True)
results = []
for i, prompt in enumerate(prompts):
print(f"\n[{i+1}/{len(prompts)}] {prompt[:60]}...")
entry = {"prompt": prompt, "index": i}
start = time.time()
try:
dalle_response = client.images.generate(
model="dall-e-3", prompt=prompt, size="1024x1024", quality="standard", n=1
)
dalle_time = time.time() - start
dalle_url = dalle_response.data[0].url
dalle_path = f"{output_dir}/dalle_{i:02d}.png"
Path(dalle_path).write_bytes(requests.get(dalle_url, timeout=30).content)
entry["dalle"] = {
"time": round(dalle_time, 2),
"cost": 0.04,
"path": dalle_path,
"revised_prompt": dalle_response.data[0].revised_prompt[:100],
}
print(f" DALL-E 3: {dalle_time:.1f}s")
except Exception as e:
entry["dalle"] = {"error": str(e)}
print(f" DALL-E 3: ERROR - {e}")
start = time.time()
try:
sd_output = replicate.run(
SDXL_MODEL,
input={
"prompt": prompt,
"negative_prompt": "blurry, low quality, distorted",
"width": 1024, "height": 1024,
"num_inference_steps": 25, "guidance_scale": 7.5,
}
)
sd_time = time.time() - start
sd_url = sd_output[0] if isinstance(sd_output, list) else str(sd_output)
sd_path = f"{output_dir}/sd_{i:02d}.png"
Path(sd_path).write_bytes(requests.get(sd_url, timeout=60).content)
entry["sd"] = {"time": round(sd_time, 2), "cost": 0.005, "path": sd_path}
print(f" SDXL: {sd_time:.1f}s")
except Exception as e:
entry["sd"] = {"error": str(e)}
print(f" SDXL: ERROR - {e}")
results.append(entry)
report = {
"timestamp": datetime.now().isoformat(),
"total_prompts": len(prompts),
"results": results,
"totals": {
"dalle_cost": sum(r["dalle"].get("cost", 0) for r in results),
"sd_cost": sum(r["sd"].get("cost", 0) for r in results),
"dalle_avg_time": round(
sum(r["dalle"].get("time", 0) for r in results) / len(results), 2
),
"sd_avg_time": round(
sum(r["sd"].get("time", 0) for r in results) / len(results), 2
),
},
}
report_path = f"{output_dir}/report.json"
Path(report_path).write_text(json.dumps(report, indent=2, ensure_ascii=False))
print(f"\nReporte: {report_path}")
return report
full_benchmark([
"A minimalist logo for a coffee shop, flat design, warm colors",
"An aerial photograph of a coastal city at golden hour",
"A watercolor illustration of a cat reading a book in a library",
])
Ejercicio 2: Calculadora de costos interactiva
Crea una función que reciba un escenario de uso (volumen mensual, configuración preferida, porcentaje de imágenes HD) y retorne una comparación de costos entre DALL-E 3 y SD, incluyendo proyección anual y recomendación.
Ver solución
def cost_calculator(
monthly_volume: int,
hd_percentage: float = 0.2,
landscape_percentage: float = 0.3,
dalle_config: str = "mixed",
) -> dict:
standard_square = monthly_volume * (1 - hd_percentage) * (1 - landscape_percentage)
hd_square = monthly_volume * hd_percentage * (1 - landscape_percentage)
standard_landscape = monthly_volume * (1 - hd_percentage) * landscape_percentage
hd_landscape = monthly_volume * hd_percentage * landscape_percentage
dalle_monthly = (
standard_square * 0.04 +
hd_square * 0.08 +
standard_landscape * 0.08 +
hd_landscape * 0.12
)
sd_monthly = monthly_volume * 0.005
recommendation = "sd" if monthly_volume > 500 and dalle_monthly > 30 else "dalle"
if dalle_monthly < 10:
recommendation = "dalle"
result = {
"monthly_volume": monthly_volume,
"dalle": {
"monthly": round(dalle_monthly, 2),
"annual": round(dalle_monthly * 12, 2),
"per_image_avg": round(dalle_monthly / max(monthly_volume, 1), 4),
},
"sd": {
"monthly": round(sd_monthly, 2),
"annual": round(sd_monthly * 12, 2),
"per_image_avg": 0.005,
},
"savings_monthly": round(dalle_monthly - sd_monthly, 2),
"savings_annual": round((dalle_monthly - sd_monthly) * 12, 2),
"savings_pct": round((1 - sd_monthly / max(dalle_monthly, 0.01)) * 100, 1),
"recommendation": recommendation,
}
print(f"=== Análisis de Costos ({monthly_volume:,} imgs/mes) ===")
print(f"\nDALL-E 3: ${result['dalle']['monthly']:,.2f}/mes (${result['dalle']['annual']:,.2f}/año)")
print(f"SD (SDXL): ${result['sd']['monthly']:,.2f}/mes (${result['sd']['annual']:,.2f}/año)")
print(f"\nAhorro con SD: ${result['savings_monthly']:,.2f}/mes ({result['savings_pct']}%)")
print(f"Ahorro anual: ${result['savings_annual']:,.2f}")
print(f"\nRecomendación: {'DALL-E 3' if recommendation == 'dalle' else 'Stable Diffusion'}")
return result
cost_calculator(100, hd_percentage=0.5)
cost_calculator(5000, hd_percentage=0.1, landscape_percentage=0.5)
Ejercicio 3: Sistema de fallback con métricas
Extiende el sistema de fallback para que registre métricas: cuántas veces se usó cada generador, cuántos fallbacks ocurrieron, tiempo promedio, y costo acumulado.
Ver solución
from dataclasses import dataclass, field
@dataclass
class GeneratorMetrics:
dalle_calls: int = 0
dalle_successes: int = 0
dalle_failures: int = 0
sd_calls: int = 0
sd_successes: int = 0
sd_failures: int = 0
fallback_count: int = 0
total_cost: float = 0.0
dalle_times: list = field(default_factory=list)
sd_times: list = field(default_factory=list)
def record(self, generator: str, success: bool, time_s: float, cost: float, was_fallback: bool):
if generator == "dalle":
self.dalle_calls += 1
if success:
self.dalle_successes += 1
self.dalle_times.append(time_s)
else:
self.dalle_failures += 1
else:
self.sd_calls += 1
if success:
self.sd_successes += 1
self.sd_times.append(time_s)
else:
self.sd_failures += 1
if was_fallback:
self.fallback_count += 1
if success:
self.total_cost += cost
def summary(self) -> dict:
return {
"dalle": {
"calls": self.dalle_calls,
"success_rate": round(self.dalle_successes / max(self.dalle_calls, 1) * 100, 1),
"avg_time": round(sum(self.dalle_times) / max(len(self.dalle_times), 1), 2),
},
"sd": {
"calls": self.sd_calls,
"success_rate": round(self.sd_successes / max(self.sd_calls, 1) * 100, 1),
"avg_time": round(sum(self.sd_times) / max(len(self.sd_times), 1), 2),
},
"fallback_rate": round(self.fallback_count / max(self.dalle_calls + self.sd_calls, 1) * 100, 1),
"total_cost": round(self.total_cost, 4),
}
metrics = GeneratorMetrics()
def generate_with_metrics(prompt: str, save_path: str | None = None) -> dict:
start = time.time()
try:
response = client.images.generate(
model="dall-e-3", prompt=prompt, size="1024x1024", quality="standard", n=1
)
elapsed = time.time() - start
url = response.data[0].url
if save_path:
Path(save_path).parent.mkdir(parents=True, exist_ok=True)
Path(save_path).write_bytes(requests.get(url, timeout=30).content)
metrics.record("dalle", True, elapsed, 0.04, False)
return {"status": "success", "generator": "dalle", "url": url, "time": round(elapsed, 2)}
except Exception as e:
elapsed = time.time() - start
metrics.record("dalle", False, elapsed, 0, False)
start = time.time()
try:
output = replicate.run(SDXL_MODEL, input={
"prompt": prompt, "negative_prompt": "blurry, low quality",
"width": 1024, "height": 1024, "num_inference_steps": 25,
})
elapsed = time.time() - start
url = output[0] if isinstance(output, list) else str(output)
if save_path:
Path(save_path).parent.mkdir(parents=True, exist_ok=True)
Path(save_path).write_bytes(requests.get(url, timeout=60).content)
metrics.record("sd", True, elapsed, 0.005, True)
return {"status": "success", "generator": "sd", "url": url, "time": round(elapsed, 2), "was_fallback": True}
except Exception as e:
elapsed = time.time() - start
metrics.record("sd", False, elapsed, 0, True)
return {"status": "error", "error": str(e)}
test_prompts = [
"A futuristic car design concept, metallic blue",
"A cozy winter cabin in the mountains with snow",
"Abstract digital art with flowing neon colors",
]
for i, prompt in enumerate(test_prompts):
result = generate_with_metrics(prompt, save_path=f"generated/metrics_{i}.png")
print(f"[{i+1}] {result.get('generator', 'none')}: {result['status']}")
print("\n=== Métricas ===")
print(json.dumps(metrics.summary(), indent=2))
Recursos Adicionales
- OpenAI Pricing — Precios actualizados de DALL-E
- Replicate Pricing — Modelo de pago por segundo de GPU
- Stability AI Pricing — Precios de la API oficial de SD
- OpenAI Rate Limits — Límites por tier
- Replicate SDXL — Documentación del modelo en Replicate