Fallos silenciosos en pipelines de IA: por qué el estado verde de un contenedor miente, y cómo recortamos un 90% el gasto en LLM
Una rama de respaldo dentro de un bloque catch, sin contador ni alerta propios, puede enmascarar un fallo total del sistema: un nuevo pipeline agéntico de tres modelos nunca llegó a ejecutarse correctamente en producción durante meses, mientras la CLI se quedaba colgada con configuraciones OAuth vacías de servidores MCP. Añadir un prefiltro determinista de titulares y un deduplicador por trigramas antes de la llamada al LLM y la generación de medios redujo el ruido informativo en un 76,5% y recortó el gasto diario en API en aproximadamente un 90% (de más de 100 a 22 llamadas por ejecución). Usar DEFAULT 0 en lugar de DEFAULT NULL durante la migración de una columna de puntuación eliminó silenciosamente 678 artículos de calidad del feed, y un ciclo de sleep antes de la primera ejecución de una tarea en segundo plano reiniciaba la actualización del tipo de cambio en cada despliegue.

- Ahorro de tokens con el prefiltrado: la proporción de gasto en vano cayó del 88% a prácticamente cero; las llamadas al LLM bajaron de más de 100 a 22 por ejecución (-90% de presupuesto)
- Eficiencia del scoring determinista: se filtró el 76,5% del flujo entrante irrelevante con cero llamadas a la API, liberando 4,9 GB de disco
- El coste de un error en la migración de base de datos: 678 artículos perdidos por usar `DEFAULT 0` en lugar de `DEFAULT NULL`, con la base aprobada creciendo de 341 a 943 tras el backfill
- Recursos ocultos: 2.244 archivos de audio narrados (6 GB, 69% de la base) que nunca llegaron a la interfaz por culpa de un campo ausente en un `SELECT`
La clase de bug más cara en sistemas de IA autónomos no es un crash con un stack trace en rojo, sino el fallo silencioso. El contenedor aparece como `Up` en `docker ps`, el cron termina con código `0`, los artículos siguen publicándose en el feed... y el código nuevo en el que invertiste una semana entera nunca llegó a ejecutarse, ni una sola vez.
Eso es exactamente lo que pasó en mi pipeline de noticias con IA tras migrar de una combinación de `n8n + Airtable` a mi propia infraestructura de servidor (40 horas de migración, un `MIGRATION.md` de más de 600 líneas para conservar el contexto entre sesiones). Construí una cadena de tres agentes (`EditorAgent`, `WriterAgent`, `QAAgent`) y la envolví en un `try / catch` limpio que recurría al antiguo `AIProcessor` como respaldo.
¿Por qué un bloque catch con rama de respaldo puede enmascarar un
fallo de cinco capas durante meses?
Como la rama de respaldo seguía publicando contenido sin problemas, el sistema parecía sano desde fuera. Solo meses después detecté en los logs una línea recurrente: `Agent pipeline failed. Falling back to AIProcessor`. Cuando empezamos a desmontar capa por capa este fallo (la sesión de depuración requirió 1.320 mensajes y 517 ejecuciones de subagentes), apareció una muñeca rusa con cinco causas:
1. El token de autenticación de corta duración de la CLI caducaba cada pocas horas. 2. El parámetro `timeout` lo aceptaba el constructor del agente base, pero nunca se propagaba a la llamada hija. 3. El modelo pesado usado en la fase de curación se quedaba sin tiempo en lotes grandes (cambiar a un modelo más rápido aceleró el paso entre 3 y 4 veces). 4. Un binario de la CLI actualizado dejó de aceptar el flag obsoleto `--max-tokens`. 5. Dentro del contenedor, la CLI se quedaba colgada permanentemente en la línea `Fetched 4 servers`, al intentar inicializar conectores MCP en la nube (Notion, Figma, Linear, Sentry) con configuraciones OAuth vacías.
La lección es inapelable: cualquier mecanismo de respaldo sin su propio contador y su propia alerta no es tolerancia a fallos, es un disfraz para ocultar una caída. El mismo principio aplica a un contenedor con estado `Up` mientras un script de Python interno queda atrapado en un bucle de crash por un `SyntaxError`, consumiendo el 98% de CPU: el chequeo real es un `PID` estable con más de 60 segundos de vida, junto con una ejecución de `python3 -m py_compile`.
¿Cómo reordenar las fases del pipeline y aplicar deduplicación por
trigramas ahorra un 90% del presupuesto en modelos?
La segunda revisión destapó un agujero financiero: de 3.235 noticias procesadas, solo 340 llegaban a publicarse, pero las llamadas caras al LLM, las portadas y la narración (TTS) se ejecutaban antes del filtrado final (el 88% del presupuesto se quemaba en vano, y un crontab duplicado disparaba el recolector 288 veces al día en vez de 48). Pusimos delante del modelo un prefiltro gratuito basado en expresiones regulares y ponderación de fuentes (más de 100 patrones en inglés, ruso y chino, usando `includes()` en lugar de `\b` para idiomas sin separación por espacios como el chino):
```javascript function scoreArticle(title, sourceName) { let score = 0; const t = (title || '').toLowerCase(); const s = (sourceName || '').toLowerCase(); if (SOURCE_TIERS.tier1.some(x => s.includes(x))) score += 4; else if (SOURCE_TIERS.tier2.some(x => s.includes(x))) score += 3; let kw = 0; for (const rx of AI_KEYWORDS) { if (rx.test(title)) kw++; } score += Math.min(kw, 4); if (AI_COMPANIES.some(c => t.includes(c))) score += 2; if (title && title.length > 20) score += 1; if (/\d/.test(title)) score += 1; return score; } ```
Este filtro elimina el 76,5% del ruido entrante con un coste de cero tokens. También sacamos la detección de duplicados del LLM y la pasamos a un híbrido de similitud de Jaccard por trigramas y distancia de Levenshtein (bajando el umbral de Jaccard para la morfología rusa de 0,65 a 0,40, cruzado con entidades nombradas). El gasto en API cayó un 90% (de más de 100 a 22 llamadas por ejecución), y limpiar medios huérfanos liberó 4,9 GB (1.764 imágenes y 1.398 audios).
¿Por qué un DEFAULT 0 en una migración de base de datos y un sleep
antes de la primera ejecución esconden tus datos?
El tercer fallo silencioso costó un mes de investigación: al añadir una columna con `ALTER TABLE ... ADD COLUMN hot_score INTEGER DEFAULT 0`, los 678 artículos antiguos quedaron con puntuación `0`, y SQL dejó de poder distinguir entre "todavía sin puntuar" y "puntuado con cero". Cambiar a `DEFAULT NULL` y hacer un backfill devolvió 581 artículos al feed, triplicando la base aprobada de 341 a 943.
Junto a esto apareció un `LIMIT 5` olvidado en la consulta del orquestador y un campo `audio_path` ausente en un `SELECT`, que ocultaba 2.244 archivos de audio ya terminados, con un total de 6 GB (69% de la base). Por último, una tarea en segundo plano para el tipo de cambio empezaba con `await asyncio.
sleep(86400)` antes de su primera ejecución, y con despliegues diarios nunca llegó a disparase ni una sola vez (solucionado con `FIRST_RUN_DELAY_S = 90` y un `last_success_at`); mientras tanto, un chequeo de salud de archivos estáticos mediante `curl`, que validaba el código `200`, recibía `index.html` en lugar de detectar PDFs desaparecidos, por culpa de la configuración SPA de nginx.
Estos invariantes de observabilidad están integrados en la plataforma [Nomad AI](https://nomadnet.ai) y en el runtime en la nube [agent.nomadnet.ai](https://agent.nomadnet.ai). Hay un curso práctico sobre depuración de pipelines autónomos disponible en [Hamidun Academy](https://academy.hamidun.com) (prueba de 72 horas), y los scripts de auditoría están en [Vibe Club](https://hamidun.com/club).
Delegate Deep Research, Coding & Multi-Step Workflows to Nomad Agent
Deploy autonomous task execution with Nomad Agent (agent.nomadnet.ai) — automated web research, data synthesis, code generation, and MCP tool orchestration.
Lo esencial de la IA — una vez por semana
Siete historias que de verdad importaron, elegidas a mano. Sin ruido ni notas de prensa.
¡Listo! Revisa tu correo para la confirmación.