Saltar al contenido principal

Patrones de Arquitectura para IA

Catálogo exhaustivo de patrones arquitectónicos específicos para sistemas basados en IA. Cada patrón incluye cuándo usarlo, tradeoffs, y ejemplos de implementación.

🧠 Patrones de Agentes

Reactor Pattern

Agente que responde reactivamente a eventos externos.

class ReactorAgent {
private eventQueue: Event[] = [];
private tools: Map<string, Tool> = new Map();

async processEvent(event: Event): Promise<void> {
// 1. Analizar evento con LLM
const analysis = await this.llm.analyze(event);

// 2. Determinar acción basada en análisis
const action = this.determineAction(analysis);

// 3. Ejecutar tool correspondiente
if (action.tool && this.tools.has(action.tool)) {
const result = await this.tools.get(action.tool).execute(action.params);
await this.handleResult(result);
}
}

private determineAction(analysis: Analysis): Action {
// Lógica simple: mapear análisis directo a acción
switch (analysis.type) {
case 'user_query': return { tool: 'search', params: analysis.data };
case 'error': return { tool: 'log_error', params: analysis.data };
default: return { tool: 'unknown', params: {} };
}
}
}

Cuándo usar:

  • ✅ Sistemas de monitoreo
  • ✅ Chatbots reactivos
  • ✅ Alertas automáticas
  • ✅ Procesamiento de eventos en tiempo real

Tradeoffs:

  • Pros: Simple, rápido, bajo overhead
  • Cons: No planifica, limitado a respuestas directas

Ejemplo real: Agente que responde a errores en logs de aplicación.


Plan-Execute-Synthesize Pattern

Agente que planifica antes de actuar, ejecuta sistemáticamente, y sintetiza resultados.

interface PlanStep {
id: string;
description: string;
tool: string;
params: any;
dependencies: string[]; // IDs de steps que deben completarse antes
}

class PlanningAgent {
async execute(task: string): Promise<SynthesisResult> {
// 1. PLAN: Descomponer tarea en steps
const plan = await this.createPlan(task);

// 2. EXECUTE: Ejecutar steps en orden correcto
const results = await this.executePlan(plan);

// 3. SYNTHESIZE: Combinar resultados en respuesta final
return this.synthesizeResults(results);
}

private async createPlan(task: string): Promise<PlanStep[]> {
const prompt = `Create a detailed plan for: ${task}
Return as JSON array of steps with dependencies.`;

const response = await this.llm.generate(prompt);
return JSON.parse(this.extractJSON(response));
}

private async executePlan(plan: PlanStep[]): Promise<StepResult[]> {
const completed = new Map<string, StepResult>();
const pending = [...plan];

while (pending.length > 0) {
// Encontrar steps ready para ejecutar
const ready = pending.filter(step =>
step.dependencies.every(dep => completed.has(dep))
);

// Ejecutar en paralelo si no hay dependencias entre ellos
const results = await Promise.all(
ready.map(step => this.executeStep(step))
);

// Marcar como completados
results.forEach(result => completed.set(result.stepId, result));

// Remover de pending
pending.splice(0, ready.length);
}

return Array.from(completed.values());
}
}

Cuándo usar:

  • ✅ Tareas complejas multi-step
  • ✅ Investigación y análisis
  • ✅ Automatización de workflows
  • ✅ Reportes y síntesis

Tradeoffs:

  • Pros: Sistemático, confiable, maneja complejidad
  • Cons: Más lento, mayor uso de tokens, overhead de planificación

Ejemplo real: Agente investigador que busca información, la analiza, y genera reportes.


Chain Pattern

Múltiples agentes especializados conectados en secuencia.

class ChainAgent {
private agents: Agent[] = [];

addAgent(agent: Agent): void {
this.agents.push(agent);
}

async execute(input: any): Promise<any> {
let currentInput = input;

for (const agent of this.agents) {
// Cada agente procesa output del anterior
currentInput = await agent.process(currentInput);

// Validar que el output es válido para siguiente agente
if (!this.validateTransition(currentInput)) {
throw new Error(`Invalid transition between agents`);
}
}

return currentInput;
}
}

// Ejemplo: Chain para procesamiento de código
const codeChain = new ChainAgent();
codeChain.addAgent(new CodeAnalyzerAgent()); // Analiza código
codeChain.addAgent(new SecurityCheckerAgent()); // Verifica seguridad
codeChain.addAgent(new OptimizerAgent()); // Optimiza
codeChain.addAgent(new FormatterAgent()); // Formatea

Cuándo usar:

  • ✅ Pipelines de procesamiento
  • ✅ Validación en capas
  • ✅ Transformaciones secuenciales
  • ✅ QA automatizado

Tradeoffs:

  • Pros: Modular, testable, debugging fácil
  • Cons: Latencia acumulada, fallo en un paso para todo

Collaborative Pattern

Múltiples agentes trabajando en paralelo y combinando resultados.

class CollaborativeAgent {
private agents: Agent[] = [];

async execute(task: Task): Promise<CollaborativeResult> {
// Dividir tarea en subtareas
const subtasks = await this.divideTask(task);

// Ejecutar agentes en paralelo
const promises = this.agents.map((agent, index) =>
agent.execute(subtasks[index])
);

// Esperar todos los resultados
const results = await Promise.all(promises);

// Combinar resultados
return this.combineResults(results);
}

private async divideTask(task: Task): Promise<Subtask[]> {
const prompt = `Divide this task into ${this.agents.length} subtasks: ${task.description}`;
const response = await this.llm.generate(prompt);
return this.parseSubtasks(response);
}

private combineResults(results: AgentResult[]): CollaborativeResult {
// Votación mayoritaria para decisiones
// Promedio para métricas
// Concatenación para texto
return {
consensus: this.findConsensus(results),
alternatives: results,
confidence: this.calculateConfidence(results)
};
}
}

Cuándo usar:

  • ✅ Tareas paralelizables
  • ✅ Validación cruzada
  • ✅ Ensemble methods
  • ✅ Búsqueda exhaustiva

Tradeoffs:

  • Pros: Paralelo, robusto, mejor calidad
  • Cons: Costoso, coordinación compleja

Hierarchical Pattern

Agentes organizados en estructura maestro-subordinado.

class HierarchicalAgent {
private masterAgent: MasterAgent;
private workerAgents: WorkerAgent[] = [];

async execute(complexTask: ComplexTask): Promise<Result> {
// Master descompone tarea compleja
const subtasks = await this.masterAgent.decompose(complexTask);

// Asigna subtareas a workers apropiados
const assignments = this.assignSubtasks(subtasks);

// Workers ejecutan en paralelo
const results = await Promise.all(
assignments.map(assignment =>
assignment.worker.execute(assignment.subtask)
)
);

// Master sintetiza resultados finales
return this.masterAgent.synthesize(results);
}

private assignSubtasks(subtasks: Subtask[]): Assignment[] {
return subtasks.map(subtask => ({
subtask,
worker: this.findBestWorker(subtask)
}));
}
}

Cuándo usar:

  • ✅ Organizaciones complejas
  • ✅ Especialización por dominio
  • ✅ Control centralizado
  • ✅ Escalabilidad horizontal

Tradeoffs:

  • Pros: Escalabilidad, especialización
  • Cons: Complejidad de coordinación, latencia

🔧 Patrones de Integración LLM

Tool Use Pattern

LLM que decide dinámicamente qué herramientas usar.

interface Tool {
name: string;
description: string;
parameters: JSONSchema;
execute: (params: any) => Promise<any>;
}

class ToolUseAgent {
private tools: Tool[] = [];

async execute(userQuery: string): Promise<string> {
const messages = [{ role: 'user', content: userQuery }];

while (true) {
// Pedir al LLM que elija tool
const response = await this.llm.chat({
messages,
tools: this.tools.map(t => ({
name: t.name,
description: t.description,
parameters: t.parameters
}))
});

if (response.toolCalls) {
// Ejecutar tools
for (const call of response.toolCalls) {
const tool = this.tools.find(t => t.name === call.name);
if (tool) {
const result = await tool.execute(call.parameters);
messages.push({
role: 'tool',
content: JSON.stringify(result)
});
}
}
} else {
// Respuesta final
return response.content;
}
}
}
}

Cuándo usar:

  • ✅ Agentes versátiles
  • ✅ Integración con sistemas externos
  • ✅ Automatización flexible

Retrieval Augmented Generation (RAG)

LLM potenciado con base de conocimiento externa.

class RAGSystem {
private vectorDB: VectorDatabase;
private embedder: Embedder;

async query(question: string): Promise<string> {
// 1. Convertir pregunta a embedding
const questionEmbedding = await this.embedder.embed(question);

// 2. Buscar documentos relevantes
const relevantDocs = await this.vectorDB.search(questionEmbedding, {
limit: 5,
threshold: 0.8
});

// 3. Crear contexto con documentos encontrados
const context = relevantDocs.map(doc => doc.content).join('\n\n');

// 4. Generar respuesta con contexto
const prompt = `Context:\n${context}\n\nQuestion: ${question}\n\nAnswer:`;

return this.llm.generate(prompt);
}

async addDocument(content: string): Promise<void> {
const embedding = await this.embedder.embed(content);
await this.vectorDB.insert({ content, embedding });
}
}

Cuándo usar:

  • ✅ Conocimiento actualizado
  • ✅ Dominios específicos
  • ✅ Reducción de hallucinations
  • ✅ Transparencia de fuentes

Fine-tuning Pattern

Modelo personalizado entrenado con datos específicos.

class FineTunedAgent {
private baseModel: LLM;
private fineTunedModel: LLM;

async shouldUseFineTuned(query: string): Promise<boolean> {
// Decidir si usar modelo fine-tuned vs base
const analysis = await this.baseModel.analyze(query);

return analysis.domainSpecificity > 0.8 &&
analysis.complexity > 0.7;
}

async execute(query: string): Promise<string> {
if (await this.shouldUseFineTuned(query)) {
return this.fineTunedModel.generate(query);
} else {
return this.baseModel.generate(query);
}
}
}

// Training data format
interface TrainingExample {
prompt: string;
completion: string;
domain: string;
quality: number;
}

Cuándo usar:

  • ✅ Terminología específica
  • ✅ Estilo consistente
  • ✅ Rendimiento mejorado
  • ✅ Costo reducido a largo plazo

Ensemble Pattern

Múltiples LLMs votando para mejor respuesta.

class EnsembleAgent {
private models: LLM[] = [];

async execute(query: string): Promise<EnsembleResult> {
// Ejecutar todos los modelos en paralelo
const responses = await Promise.all(
this.models.map(model => model.generate(query))
);

// Aplicar estrategias de ensemble
const consensus = this.findConsensus(responses);
const confidence = this.calculateConfidence(responses);

return {
answer: consensus,
confidence,
alternatives: responses,
reasoning: this.explainConsensus(responses)
};
}

private findConsensus(responses: string[]): string {
// Votación mayoritaria para respuestas categóricas
// Promedio para respuestas numéricas
// Concatenación inteligente para texto

const scores = new Map<string, number>();
responses.forEach(response => {
const normalized = this.normalizeResponse(response);
scores.set(normalized, (scores.get(normalized) || 0) + 1);
});

return Array.from(scores.entries())
.sort((a, b) => b[1] - a[1])[0][0];
}
}

Cuándo usar:

  • ✅ Alta confiabilidad requerida
  • ✅ Reducción de errores
  • ✅ Validación cruzada

Fallback Pattern

LLM principal con respaldo automático.

class FallbackAgent {
private primaryLLM: LLM;
private fallbackLLM: LLM;

async execute(query: string): Promise<string> {
try {
const result = await this.primaryLLM.generate(query);

// Validar calidad de respuesta
if (this.isValidResponse(result)) {
return result;
} else {
return await this.fallback(query, 'Low quality response');
}
} catch (error) {
return this.fallback(query, error.message);
}
}

private async fallback(query: string, reason: string): Promise<string> {
console.warn(`Fallback triggered: ${reason}`);

// Usar modelo más conservador o con instrucciones diferentes
const fallbackPrompt = `Please provide a careful, conservative answer: ${query}`;

return this.fallbackLLM.generate(fallbackPrompt);
}

private isValidResponse(response: string): boolean {
return response.length > 10 &&
!response.includes('uncertain') &&
this.hasConcreteAnswer(response);
}
}

Cuándo usar:

  • ✅ Alta disponibilidad
  • ✅ Sistemas críticos
  • ✅ Manejo de fallos

📊 Patrones de Estado

Stateless Pattern

Cada request es independiente, sin estado entre llamadas.

class StatelessAgent {
async execute(query: string, context?: any): Promise<Response> {
// Toda la información necesaria viene en el request
const prompt = this.buildPrompt(query, context);

const response = await this.llm.generate(prompt);

// No se guarda estado entre requests
return { answer: response, sessionId: null };
}
}

Cuándo usar:

  • ✅ APIs públicas
  • ✅ Escalabilidad máxima
  • ✅ Simplicidad

Tradeoffs:

  • Pros: Simple, escalable, stateless
  • Cons: Repetición, no aprendizaje, limitado contexto

Lightweight State Pattern

Estado temporal en memoria durante sesión.

class SessionAgent {
private sessions: Map<string, Session> = new Map();

async execute(query: string, sessionId: string): Promise<Response> {
let session = this.sessions.get(sessionId);

if (!session) {
session = this.createSession(sessionId);
this.sessions.set(sessionId, session);
}

// Actualizar estado basado en query
session.history.push(query);
session.lastActivity = Date.now();

const context = this.buildContext(session);
const response = await this.llm.generate(context + query);

// Actualizar estado con respuesta
session.history.push(response);

return { answer: response, sessionId };
}

private createSession(sessionId: string): Session {
return {
id: sessionId,
history: [],
createdAt: Date.now(),
lastActivity: Date.now(),
metadata: {}
};
}
}

Cuándo usar:

  • ✅ Conversaciones
  • ✅ Contexto temporal
  • ✅ Sesiones de usuario

Persistent State Pattern

Estado durable en base de datos.

class PersistentAgent {
private db: Database;

async execute(query: string, userId: string): Promise<Response> {
// Cargar estado del usuario
const userState = await this.db.getUserState(userId);

// Actualizar estado
userState.lastQuery = query;
userState.queryCount++;
userState.preferences = this.updatePreferences(userState, query);

// Generar respuesta con contexto histórico
const context = this.buildContext(userState);
const response = await this.llm.generate(context + query);

// Persistir cambios
await this.db.saveUserState(userId, userState);

return { answer: response, userId };
}
}

Cuándo usar:

  • ✅ Personalización
  • ✅ Historial largo
  • ✅ Aprendizaje continuo

Hybrid State Pattern

Cache rápido + base de datos para durabilidad.

class HybridAgent {
private cache: Cache;
private db: Database;

async execute(query: string, userId: string): Promise<Response> {
// Intentar cache primero
let userState = await this.cache.get(`user:${userId}`);

if (!userState) {
// Fallback a DB
userState = await this.db.getUserState(userId);

// Popular cache
await this.cache.set(`user:${userId}`, userState, 3600); // 1 hora
}

// Procesar como siempre
const response = await this.processWithState(query, userState);

// Actualizar ambos stores
await Promise.all([
this.cache.set(`user:${userId}`, userState, 3600),
this.db.saveUserState(userId, userState)
]);

return response;
}
}

Cuándo usar:

  • ✅ Alto rendimiento
  • ✅ Alta disponibilidad
  • ✅ Datos críticos

🔒 Patrones de Seguridad

Sandbox Pattern

Ejecución aislada de herramientas potencialmente peligrosas.

class SandboxedAgent {
async executeTool(toolName: string, params: any): Promise<any> {
// Crear container aislado
const container = await this.createSandbox();

try {
// Ejecutar tool dentro del sandbox
const result = await container.execute(toolName, params);

// Validar resultado antes de devolver
return this.validateResult(result);
} finally {
// Limpiar sandbox
await container.destroy();
}
}

private async createSandbox(): Promise<Sandbox> {
return Docker.createContainer({
image: 'secure-runtime',
network: 'isolated',
memory: '256MB',
cpu: '0.5',
readOnly: true,
tmpfs: { '/tmp': '' }
});
}
}

Cuándo usar:

  • ✅ Tools no confiables
  • ✅ Código de usuario
  • ✅ Acceso a filesystem

Validation Pattern

Validación estricta de inputs y outputs.

class ValidatedAgent {
private inputSchema: Schema;
private outputSchema: Schema;

async execute(input: any): Promise<any> {
// Validar input
const validatedInput = this.validateInput(input);

// Procesar
const rawOutput = await this.llm.generate(validatedInput);

// Validar output
const validatedOutput = this.validateOutput(rawOutput);

return validatedOutput;
}

private validateInput(input: any): ValidatedInput {
try {
return this.inputSchema.parse(input);
} catch (error) {
throw new ValidationError(`Invalid input: ${error.message}`);
}
}

private validateOutput(output: any): ValidatedOutput {
// Intentar parsear como JSON
try {
const parsed = JSON.parse(output);
return this.outputSchema.parse(parsed);
} catch {
// Si no es JSON válido, intentar extraer información útil
return this.fallbackValidation(output);
}
}
}

Cuándo usar:

  • ✅ APIs públicas
  • ✅ Datos de usuario
  • ✅ Integraciones críticas

Rate Limiting Pattern

Protección contra abuso y sobrecarga.

class RateLimitedAgent {
private limiter: RateLimiter;

async execute(query: string, userId: string): Promise<Response> {
// Verificar rate limit
const allowed = await this.limiter.check(userId, {
windowMs: 60000, // 1 minuto
maxRequests: 10 // 10 requests por minuto
});

if (!allowed) {
throw new RateLimitError('Too many requests');
}

// Procesar normalmente
return this.processQuery(query);
}
}

// Multi-level rate limiting
class MultiLevelLimiter {
async check(userId: string, config: RateLimitConfig): Promise<boolean> {
const checks = await Promise.all([
this.userLimiter.check(userId, config),
this.globalLimiter.check('global', config),
this.endpointLimiter.check('endpoint', config)
]);

return checks.every(check => check);
}
}

Cuándo usar:

  • ✅ APIs públicas
  • ✅ Recursos limitados
  • ✅ Prevención de abuso

Logging & Audit Pattern

Trazabilidad completa de todas las operaciones.

class AuditedAgent {
private logger: StructuredLogger;
private auditTrail: AuditTrail;

async execute(query: string, context: RequestContext): Promise<Response> {
const requestId = generateId();
const startTime = Date.now();

// Log entrada
await this.logger.info('Agent execution started', {
requestId,
userId: context.userId,
query: this.sanitizeQuery(query),
timestamp: startTime
});

try {
const result = await this.processQuery(query, context);

// Log éxito
await this.logger.info('Agent execution completed', {
requestId,
duration: Date.now() - startTime,
resultSize: JSON.stringify(result).length
});

// Audit trail
await this.auditTrail.record({
requestId,
userId: context.userId,
action: 'agent_execute',
status: 'success',
metadata: { queryLength: query.length }
});

return result;

} catch (error) {
// Log error
await this.logger.error('Agent execution failed', {
requestId,
error: error.message,
stack: error.stack,
duration: Date.now() - startTime
});

// Audit trail de error
await this.auditTrail.record({
requestId,
userId: context.userId,
action: 'agent_execute',
status: 'error',
metadata: { errorType: error.constructor.name }
});

throw error;
}
}
}

Cuándo usar:

  • ✅ Sistemas críticos
  • ✅ Cumplimiento regulatorio
  • ✅ Debugging de producción
  • ✅ Análisis forense

Decision Tree: ¿Qué Patrón Usar?

¿Es una tarea simple y directa?
├── Sí → Reactor Pattern
└── No → ¿Requiere planificación?
├── Sí → Plan-Execute-Synthesize
└── No → ¿Es una pipeline secuencial?
├── Sí → Chain Pattern
└── No → ¿Se puede paralelizar?
├── Sí → Collaborative Pattern
└── No → Hierarchical Pattern

Conclusión

Los patrones no son recetas rígidas, sino herramientas para pensar arquitectónicamente. El mejor patrón es aquel que:

  • Resuelve tu problema específico
  • Tiene tradeoffs aceptables
  • Se puede implementar con tus recursos
  • Evoluciona con tus necesidades

Pregunta clave: ¿Qué patrón mejor modela cómo quieres que tu sistema IA se comporte?

"La arquitectura de IA no se trata de usar el patrón más moderno, sino del patrón que mejor expresa la esencia de tu problema."

Próximos: Decisiones de Diseño - tradeoffs concretos para cada aspecto de sistemas IA. ⚖️