Diseño de arquitectura para aplicaciones con LLM APIs

Lectura
25 min~5 min lectura

Concepto clave

El diseño de arquitectura para aplicaciones con LLM APIs se refiere a la estructura organizada de componentes que integran modelos de lenguaje como GPT o Claude en sistemas full stack. Imagina construir una fábrica inteligente: no basta con tener máquinas potentes (los LLMs), necesitas cintas transportadoras (APIs), controles de calidad (validación de prompts) y líneas de ensamblaje (flujos de trabajo) que funcionen en armonía. En desarrollo de software, esto significa crear una arquitectura escalable donde los prompts no sean instrucciones sueltas, sino partes de un sistema que maneje contexto, estado y lógica de negocio.

La clave está en separar responsabilidades: una capa para gestión de prompts, otra para comunicación con APIs, y una más para procesamiento de respuestas. Piensa en cómo un restaurante de alta cocina organiza su cocina: el chef (LLM) recibe pedidos estructurados (prompts optimizados) de los sous-chefs (sistema de prompting), quienes a su vez coordinan con meseros (interfaz de usuario) y almacén (base de datos). Esta separación permite mantenibilidad y adaptabilidad, crucial cuando trabajas con modelos que evolucionan o cuando necesitas cambiar proveedores de APIs.

Cómo funciona en la práctica

Vamos a diseñar una arquitectura para un asistente de soporte técnico integrado en una aplicación web. Paso 1: Identifica los componentes principales. Necesitarás: un módulo de interfaz (frontend), un servidor (backend), un servicio de gestión de prompts, y el cliente de API del LLM. Paso 2: Define el flujo de datos. El usuario escribe una consulta en el frontend, que envía al backend; el servicio de prompts estructura la consulta con contexto (ej., historial de conversación) y la envía al LLM; la respuesta se valida y procesa antes de devolverla al usuario.

Paso 3: Implementa la separación de capas. Por ejemplo, crea un directorio en tu proyecto con archivos como promptManager.js para manejar plantillas de prompts, llmService.js para llamadas a la API, y responseHandler.js para limpiar y estructurar respuestas. Esto evita el anti-patrón de mezclar lógica de negocio con llamadas directas al LLM, que puede llevar a código espagueti difícil de depurar.

Código en acción

Aquí un ejemplo básico de cómo estructurar el servicio de prompts en Node.js, mostrando el antes y después de refactorizar:

Antes (código acoplado):

// En el archivo principal del backend
export async function handleSupportQuery(userInput) {
  const prompt = `Responde como soporte técnico: ${userInput}`;
  const response = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: { 'Authorization': 'Bearer API_KEY', 'Content-Type': 'application/json' },
    body: JSON.stringify({ model: 'gpt-4', messages: [{ role: 'user', content: prompt }] })
  });
  const data = await response.json();
  return data.choices[0].message.content;
}

Después (arquitectura separada):

// promptManager.js
export class PromptManager {
  constructor(context) {
    this.context = context; // ej., historial de usuario
  }
  
  generateSupportPrompt(userInput) {
    return `Eres un asistente de soporte técnico experto. Contexto previo: ${this.context}. Usuario pregunta: ${userInput}. Responde de manera clara y concisa.`;
  }
}

// llmService.js
export class LLMService {
  constructor(apiKey, model = 'gpt-4') {
    this.apiKey = apiKey;
    this.model = model;
  }
  
  async callAPI(prompt) {
    const response = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({ model: this.model, messages: [{ role: 'user', content: prompt }] })
    });
    return await response.json();
  }
}

// Uso en el backend principal
import { PromptManager } from './promptManager.js';
import { LLMService } from './llmService.js';

export async function handleSupportQuery(userInput, userContext) {
  const promptManager = new PromptManager(userContext);
  const llmService = new LLMService(process.env.API_KEY);
  const prompt = promptManager.generateSupportPrompt(userInput);
  const apiResponse = await llmService.callAPI(prompt);
  return apiResponse.choices[0].message.content;
}

Errores comunes

  • Acoplamiento excesivo: Integrar llamadas al LLM directamente en la lógica de negocio, lo que dificulta cambios y pruebas. Solución: Usa servicios separados como en el ejemplo de código.
  • Falta de gestión de contexto: Enviar prompts sin historial o estado, llevando a respuestas inconsistentes. Solución: Implementa un sistema que almacene y inyecte contexto relevante en cada prompt.
  • Ignorar límites de tokens: Superar los límites de longitud de prompts, causando errores o costos elevados. Solución: Recorta o resume entradas largas antes de enviarlas.
  • No manejar errores de API: Asumir que el LLM siempre responde, sin plan para fallos de red o cuotas excedidas. Solución: Agrega reintentos, timeouts y respuestas de respaldo.
  • Prompting estático: Usar los mismos prompts para todos los usuarios, perdiendo personalización. Solución: Dinamiza prompts basados en datos de usuario o analytics.

Checklist de dominio

  1. ¿He separado la lógica de prompting en módulos independientes del resto de la aplicación?
  2. ¿Mi arquitectura permite cambiar fácilmente entre diferentes LLM APIs (ej., de GPT a Claude)?
  3. ¿He implementado validación y sanitización de respuestas del LLM antes de mostrarlas al usuario?
  4. ¿El sistema maneja adecuadamente el estado y contexto entre interacciones?
  5. ¿He incluido mecanismos de fallback para cuando la API del LLM no esté disponible?
  6. ¿Los prompts están parametrizados y son fáciles de ajustar sin modificar código?
  7. ¿He considerado la escalabilidad, como el uso de colas para manejar picos de solicitudes?

Diseña e implementa un servicio de prompting para un chatbot de e-commerce

En este ejercicio, crearás una arquitectura básica para un chatbot que ayuda en un sitio de e-commerce. Sigue estos pasos:

  1. Configura un proyecto Node.js simple con Express o un framework similar.
  2. Crea tres módulos: promptBuilder.js para generar prompts, llmClient.js para comunicarte con una API de LLM (usa un mock o una API real si tienes clave), y responseProcessor.js para limpiar respuestas.
  3. En promptBuilder.js, define una función que tome un producto y una pregunta del usuario, y devuelva un prompt estructurado para el LLM (ej., incluye detalles del producto como contexto).
  4. En llmClient.js, implementa una función que envíe el prompt a la API y devuelva la respuesta cruda.
  5. En responseProcessor.js, añade lógica para extraer información clave o formatear la respuesta.
  6. Integra estos módulos en una ruta de tu servidor que maneje consultas POST desde un frontend simulado.
  7. Prueba el flujo completo con al menos dos consultas diferentes.
Pistas
  • Usa variables de entorno para almacenar claves de API y evitar hardcodear.
  • Considera agregar un sistema de caché para respuestas frecuentes y reducir costos.
  • Si no tienes acceso a una API real, simula respuestas con un objeto JSON para probar la arquitectura.

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.