Sebastian Gomez
Menos humo y más arquitectura: Patrones de diseño para sistemas con agentes de IA
A lo largo de nuestras carreras como desarrolladores y arquitectos de software, hemos visto el mismo fenómeno repetirse una y otra vez: surge una tecnología con un potencial transformador genuino y, de la noche a la mañana, el ecosistema se llena de una densa capa de humo. Términos rimbombantes, librerías que reinventan la rueda y conferencias donde parece que programar ahora requiere olvidar tres décadas de buenas prácticas de ingeniería.
En el mundo de la inteligencia artificial esto se ha vuelto el pan de cada día. Hoy todo el mundo habla de loop engineering, graph engineering, orquestación multiagente o "diagramas de diamante", presentándolos como si fueran paradigmas alienígenas recién descubiertos.
La realidad es mucho más terrenal y tranquilizadora: la inmensa mayoría de los sistemas agénticos que funcionan con éxito en producción no son magia negra; son patrones clásicos de diseño de software y mensajería asíncrona adaptados a las particularidades de los Modelos de Lenguaje (LLMs).
Si entiendes la separación de responsabilidades, la composición modular y el flujo de datos, ya tienes el 80% del camino recorrido. En este artículo vamos a desmitificar el ruido, explorar los 4 patrones de diseño agénticos fundamentales con ejemplos de código claros, y discutir las trampas de costes, latencia y observabilidad que debes evitar al llevarlos a producción.
De Enterprise Integration Patterns a los Agentes de IA
En 2003, Gregor Hohpe y Bobby Woolf publicaron Enterprise Integration Patterns, un libro de cabecera que catalogó 65 patrones de integración y mensajería en sistemas distribuidos. Si abres ese libro hoy y comparas sus diagramas con los flujos de trabajo de IA que compañías como Anthropic describen en su guía Building Effective Agents, el paralelismo es inmediato:
- El clásico Pipes and Filters hoy lo llamamos Prompt Chaining o Pipeline.
- El Content-Based Router hoy es un Router Agéntico.
- El Scatter-Gather (Fan-out / Fan-in) hoy lo bautizan con glamour como "el Diamante Agéntico" o Orchestrator-Workers.
- Los bucles de control y refinamiento con pruebas hoy son los Evaluator-Optimizer Loops.
Comprender que estamos ante patrones arquitectónicos conocidos nos da una ventaja enorme: nos libera de la dependencia ciega de frameworks pesados y nos permite implementar soluciones directas, legibles y mantenibles usando código estándar y los SDKs oficiales de los modelos.
Veamos cada uno de estos cuatro patrones en detalle.
1. El Patrón Pipeline (Prompt Chaining): La cadena de montaje
El patrón más elemental es la pipeline o cadena de tareas lineales. Consiste en descomponer un problema complejo en una secuencia de etapas deterministas donde la salida de una etapa se convierte en la entrada inyectada de la siguiente.
Un paralelismo directo en el desarrollo tradicional es el Spec-Driven Development: primero defines el diseño, ese diseño genera la especificación técnica, la especificación se divide en tareas y finalmente se pasa a desarrollo y revisión.
[Input Usuario] ──> [ Etapa 1: Esquema ] ──> [ Etapa 2: Redacción ] ──> [ Etapa 3: Título ] ──> [Resultado]Caso de uso: Generador estructurado de artículos técnicos
Imagina que quieres crear un post técnico. Si le pides a un LLM en un único prompt gigante "Escríbeme un post sobre WebSockets con arquitectura, código y título" el resultado suele ser superficial. Al dividirlo en una pipeline, especializas cada paso:
- Paso 1 (Planificador): Genera la estructura de secciones y temas clave.
- Paso 2 (Redactor): Toma el esquema anterior y desarrolla el contenido técnico en profundidad.
- Paso 3 (Optimizador / Editor): Recibe el borrador completo y genera un titular atractivo y síntesis final.
Ejemplo en código
import OpenAI from "openai";
const openai = new OpenAI();
async function runPrompt(systemPrompt, userPrompt, model = "gpt-4o-mini") {
const response = await openai.chat.completions.create({
model,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt }
],
temperature: 0.7,
});
return response.choices[0].message.content;
}
async function generateTechnicalPost(topic) {
console.log(`[1/3] Generando esquema para: "${topic}"...`);
const outline = await runPrompt(
"Eres un arquitecto de software y docente técnico. Genera un esquema detallado con títulos de secciones para un post técnico.",
`Tema: ${topic}`
);
console.log(`[2/3] Redactando contenido a partir del esquema...`);
const draft = await runPrompt(
"Eres un redactor técnico especializado. Desarrolla el contenido completo de cada sección de este esquema de forma clara y pragmática.",
`Esquema base:\n${outline}`
);
console.log(`[3/3] Optimizando título y resumen ejecutivo...`);
const finalPost = await runPrompt(
"Eres un editor de publicaciones de ingeniería. Genera 3 opciones de títulos atractivos y añade un resumen ejecutivo al inicio.",
`Borrador del post:\n${draft}`
);
return finalPost;
}2. El Patrón Router (Enrutador): La bifurcación inteligente
En sistemas del mundo real, no todas las peticiones siguen la misma ruta. El patrón Router intercepta la entrada del usuario, analiza su intención semántica y la despacha al agente o flujo especializado adecuado.
┌──> [ Agente Facturación ] ──> Output 1
│
[ Query Usuario ] ──> [ Router ] ──> [ Agente Soporte Técnico ] ──> Output 2
│
└──> [ Fallback: Agente Humano ]¿Cómo se implementa un Router?
No siempre necesitas una llamada a un LLM para enrutar:
- Por LLM Clasificador (Structured Output): Le pides a un modelo rápido que devuelva un JSON con el departamento de destino y un índice de confianza ($0.0$ a $1.0$).
- Por Búsqueda Semántica (Embeddings): Conviertes la consulta en un vector y calculas la similitud de coseno contra vectores de referencia de cada departamento (mucho más rápido y barato).
- Por Lógica de Contexto / UI: Si el usuario ya está navegando en la pantalla
/billing, el router es una simple variable de entorno en tu frontend/backend.
Ejemplo en código con Fallback Humano
async function routeCustomerQuery(userMessage) {
// 1. Clasificación con modelo rápido
const classificationPrompt = `
Eres un clasificador de soporte al cliente. Clasifica la intención del usuario en uno de estos departamentos:
- BILLING (problemas con cobros, facturas, tarjetas)
- TECH_SUPPORT (bugs, caídas de servicio, errores de API)
- REFUNDS (solicitudes de devolución)
Responde ÚNICAMENTE en formato JSON: {"department": "BILLING" | "TECH_SUPPORT" | "REFUNDS" | "UNKNOWN", "confidence": 0.0 - 1.0}
`;
const rawDecision = await runPrompt(
classificationPrompt,
`Mensaje del cliente: "${userMessage}"`,
"gpt-4o-mini"
);
const decision = JSON.parse(rawDecision);
console.log(`[Router] Clasificado como ${decision.department} con confianza ${decision.confidence}`);
// 2. Guardrail y Fallback a Humano (Human-in-the-Loop)
if (decision.confidence < 0.75 || decision.department === "UNKNOWN") {
return {
status: "ESCALATED_TO_HUMAN",
message: "Tu consulta requiere asistencia personalizada. Hemos transferido el ticket a un agente de nuestro equipo."
};
}
// 3. Ejecución del agente especializado
const departmentPrompts = {
BILLING: "Eres un especialista financiero de soporte. Resuelve dudas sobre cobros y cargos duplicados.",
TECH_SUPPORT: "Eres un ingeniero de soporte L2. Diagnostica problemas técnicos y errores de integración.",
REFUNDS: "Eres un agente de retención y reembolsos. Aplica las políticas comerciales de devolución."
};
const response = await runPrompt(departmentPrompts[decision.department], userMessage);
return { status: "RESOLVED", department: decision.department, response };
}3. El Patrón Planner-Executor (Diamante o Fan-Out / Fan-In)
Cuando una tarea es demasiado amplia o incierta para resolverse en un solo paso, pasarle el control absoluto a un único agente para que genere código o tome decisiones suele terminar en alucinaciones y pérdidas de contexto.
El patrón Planner-Executor separa el trabajo en dos roles bien definidos:
- El Cerebro (Planner / Orquestador): Un modelo con alta capacidad de razonamiento que recibe la meta, evalúa las herramientas y agentes disponibles, y genera un plan estructurado de sub-tareas.
- Los Ejecutores (Workers en Paralelo): Agentes especializados que ejecutan las sub-tareas de forma concurrente (Fan-Out).
- El Sintetizador (Gather): Recoge los resultados parciales y emite un veredicto o artefacto consolidado (Fan-In).
┌──> [ Worker 1: Viabilidad Técnica ] ──┐
│ │
[ Meta ] ──> [ Planner ] ──> [ Worker 2: Mercado y Costes ] ───┼──> [ Sintetizador ] ──> [Veredicto]
│ │
└──> [ Worker 3: Riesgos y Seguridad ] ──┘Ejemplo en código: Evaluación paralela de ideas de producto
async function evaluateProductIdea(ideaDescription) {
console.log("[Planner] Desplegando comité de evaluación en paralelo...");
// Fan-Out: Ejecución concurrente con Promise.all
const [techEval, marketEval, riskEval] = await Promise.all([
runPrompt(
"Eres un Principal Engineer. Evalúa la viabilidad técnica y complejidad arquitectónica de esta idea en 2 párrafos.",
ideaDescription
),
runPrompt(
"Eres un Product Manager. Evalúa la necesidad de mercado, monetización y público objetivo en 2 párrafos.",
ideaDescription
),
runPrompt(
"Eres un Auditor de Seguridad y Compliance. Evalúa los riesgos operativos y regulatorios en 2 párrafos.",
ideaDescription
)
]);
console.log("[Gather] Sintetizando veredicto del comité...");
// Fan-In: Consolidación final
const synthesis = await runPrompt(
"Eres el Director de Estrategia. Integra las 3 evaluaciones de tu comité y emite una recomendación ejecutiva final con veredicto GO / NO-GO.",
`IDEA: ${ideaDescription}\n\nEVALUACIÓN TÉCNICA:\n${techEval}\n\nEVALUACIÓN DE MERCADO:\n${marketEval}\n\nRIESGOS:\n${riskEval}`
);
return synthesis;
}4. El Patrón Evaluator-Optimizer: El bucle adversario con rúbrica
El patrón Evaluator-Optimizer (o ciclo generador-crítico) aplica una de las dinámicas más potentes de la IA: el uso de agentes adversarios con responsabilidades separadas.
Un agente genera una primera versión del contenido o código, y un segundo agente (el evaluador) evalúa el resultado contra una rúbrica de calidad estricta. Si la crítica encuentra fallos, el generador recibe las observaciones exactas y reescribe corrigiendo solo lo señalado. El ciclo se repite hasta que el evaluador da el visto bueno o se alcanza el límite de rondas.
[ Input ] ──> [ Generador ] <──── Feedback ────┐
│ │
▼ │
[ Evaluador ] ── (¿Cumple Rúbrica?) ┘
│ (Sí)
▼
[ Salida ]Ejemplo en código: Refinador de fichas de producto para E-commerce
async function optimizeProductDescription(rawDraft, rubric, maxRounds = 3) {
let currentText = rawDraft;
for (let round = 1; round <= maxRounds; round++) {
console.log(`\n--- Ronda de optimización ${round}/${maxRounds} ---`);
// 1. El Crítico evalúa contra la rúbrica
const evaluation = await runPrompt(
`Eres un editor de catálogo muy estricto. Evalúa el texto contra esta RÚBRICA:
${rubric}
Responde en formato JSON:
{
"approved": boolean,
"defects": ["lista de problemas específicos encontrados"],
"notes": "explicación concisa"
}`,
`Texto a evaluar:\n"${currentText}"`,
"gpt-4o-mini"
);
const result = JSON.parse(evaluation);
if (result.approved) {
console.log(`[Crítico] ¡Aprobado en la ronda ${round}!`);
return { text: currentText, rounds: round, status: "APPROVED" };
}
console.log(`[Crítico] Rechazado. Defectos señalados:`, result.defects);
// 2. El Optimizador reescribe corrigiendo solo los defectos
currentText = await runPrompt(
`Reescribe el texto corrigiendo ÚNICAMENTE los defectos señalados por el editor. Conserva los datos correctos y la marca intacta.`,
`Texto actual: "${currentText}"\nDefectos a corregir: ${result.defects.join(", ")}`
);
}
return { text: currentText, rounds: maxRounds, status: "MAX_ROUNDS_REACHED" };
}Las trampas de producción que debes vigilar (Failure Modes)
Cuando pasas de la demo local a un producto utilizado por miles de usuarios, estos patrones se enfrentan a desafíos operacionales reales:
1. El fallo silencioso (Silent Failure) y los errores en cascada
En sistemas de software tradicionales, si un servicio falla recibes un código de estado HTTP 500 o una excepción clara. En sistemas agénticos, el fallo suele ser silencioso: el modelo responde con un texto gramaticalmente perfecto pero basado en una alucinación o un dato incorrecto. En una pipeline o en un patrón planner-executor, ese error se propaga e intoxica a todos los agentes aguas abajo.
- Mitigación: Valida siempre las salidas intermedias con esquemas estrictos (JSON Schema / Zod) y aserciones deterministas antes de alimentar el siguiente paso.
2. Bucles infinitos (Runaway Loops) y explosión de factura
En el patrón Evaluator-Optimizer, si tu rúbrica contiene directrices contradictorias (por ejemplo: "sé extremadamente conciso" y a la vez "explica todos los detalles históricos"), el crítico y el optimizador pueden entrar en un bucle circular infinito de correcciones.
- Mitigación: Coloca un Circuit Breaker con un número máximo inquebrantable de iteraciones (máximo 2 o 3 rondas) y alertas de consumo de tokens.
3. Latencia innecesaria en el Router
Usar modelos pesados de razonamiento (como GPT-4o o Claude Sonnet) exclusivamente para decidir si un mensaje va a Soporte o a Ventas añade de 2 a 3 segundos de latencia a cada interacción del usuario.
- Mitigación: Usa modelos ligeros y rápidos (Flash / Mini / Haiku) para enrutar, o recurre a embeddings vectoriales locales para clasificaciones de alta frecuencia.
¿Qué opinas? ¿Cuál de estos patrones estás utilizando ya en tus proyectos de IA o cuál te ha causado más dolores de cabeza en producción?
Déjame tus comentarios abajo y sigamos conversando sobre arquitectura e inteligencia artificial práctica. ¡Hasta la próxima!
Sebastian Gomez
Creador de contenido principalmente acerca de tecnología.