Skip to main content
ai5 de octubre de 20259 min de lectura

Cómo integrar funciones de IA en productos existentes

Una guía práctica para integrar IA en productos que ya tienen usuarios — desde elegir el modelo adecuado hasta patrones de despliegue en producción.

aillmintegration
Cómo integrar funciones de IA en productos existentes

Agregar IA a un producto que ya tiene usuarios es fundamentalmente diferente a construir una startup centrada en IA desde cero. Tienes infraestructura existente, patrones de UX establecidos, expectativas reales de los usuarios y — lo más importante — cosas que pueden romperse. Este artículo cubre los patrones que he encontrado confiables después de lanzar funciones de IA en múltiples aplicaciones en producción.

Eligiendo entre proveedores de modelos

La decisión del proveedor de modelos no es permanente, y no debería tratarse como tal. Cada proveedor tiene fortalezas distintas que importan en producción.

OpenAI (GPT-4o, GPT-4.1) sigue siendo la opción más probada en batalla para generación de texto de propósito general. La API es estable, la documentación es completa y el ecosistema de herramientas a su alrededor es maduro. Si necesitas function calling, salida JSON estructurada o amplio soporte multilingüe, OpenAI es una opción segura por defecto.

Anthropic (Claude) sobresale en seguimiento de instrucciones matizadas y tareas de contexto largo. Cuando tu función involucra procesar documentos extensos, mantener system prompts complejos o manejar tareas donde el modelo necesita decir "no lo sé" en lugar de alucinar, Claude tiende a rendir mejor. Las capacidades de razonamiento en los modelos de Claude también son fuertes para tareas analíticas de múltiples pasos.

Google Gemini vale la pena considerarlo cuando tu función involucra entrada multimodal — particularmente cuando necesitas procesar imágenes, video o audio junto con texto dentro de la misma solicitud. La arquitectura multimodal nativa de Gemini evita la sensación de funciones de visión añadidas a modelos centrados en texto. Los precios para casos de uso de alto rendimiento también son competitivos.

La respuesta práctica: comienza con el proveedor que tu equipo mejor conozca, pero diseña tu sistema para poder cambiar. La dependencia del proveedor es el riesgo real, no elegir el modelo "equivocado" el primer día.

El patrón API Wrapper

Toda integración de IA debería estar detrás de una capa de abstracción. No porque definitivamente cambiarás de proveedor, sino porque definitivamente necesitarás agregar logging, caché, rate limiting y lógica de fallback — y no querrás hacer eso en 40 lugares diferentes.

interface AIProvider {
  generateText(prompt: string, options?: GenerateOptions): Promise<AIResponse>;
  generateStream(prompt: string, options?: GenerateOptions): AsyncGenerator<string>;
  generateStructured<T>(prompt: string, schema: z.ZodSchema<T>, options?: GenerateOptions): Promise<T>;
}

interface GenerateOptions {
  model?: string;
  temperature?: number;
  maxTokens?: number;
  systemPrompt?: string;
}

interface AIResponse {
  content: string;
  usage: { promptTokens: number; completionTokens: number };
  model: string;
  latencyMs: number;
}

La implementación concreta para un proveedor dado se mantiene delgada:

class AnthropicProvider implements AIProvider {
  private client: Anthropic;

  constructor(apiKey: string) {
    this.client = new Anthropic({ apiKey });
  }

  async generateText(prompt: string, options?: GenerateOptions): Promise<AIResponse> {
    const start = Date.now();
    const response = await this.client.messages.create({
      model: options?.model ?? "claude-sonnet-4-20250514",
      max_tokens: options?.maxTokens ?? 1024,
      temperature: options?.temperature ?? 0.7,
      system: options?.systemPrompt,
      messages: [{ role: "user", content: prompt }],
    });

    const textBlock = response.content.find((b) => b.type === "text");
    return {
      content: textBlock?.text ?? "",
      usage: {
        promptTokens: response.usage.input_tokens,
        completionTokens: response.usage.output_tokens,
      },
      model: response.model,
      latencyMs: Date.now() - start,
    };
  }

  // ... generateStream, generateStructured
}

Luego una capa de servicio maneja las preocupaciones transversales:

class AIService {
  constructor(
    private provider: AIProvider,
    private cache: CacheStore,
    private logger: Logger,
    private fallbackProvider?: AIProvider
  ) {}

  async generate(prompt: string, options?: GenerateOptions): Promise<AIResponse> {
    const cacheKey = this.buildCacheKey(prompt, options);
    const cached = await this.cache.get<AIResponse>(cacheKey);
    if (cached) return cached;

    try {
      const response = await this.provider.generateText(prompt, options);
      this.logger.info("ai_generation", {
        model: response.model,
        tokens: response.usage,
        latencyMs: response.latencyMs,
      });
      await this.cache.set(cacheKey, response, { ttl: 3600 });
      return response;
    } catch (error) {
      if (this.fallbackProvider) {
        this.logger.warn("ai_fallback_triggered", { error: String(error) });
        return this.fallbackProvider.generateText(prompt, options);
      }
      throw error;
    }
  }
}

Este patrón se paga solo en la primera semana. Cuando OpenAI tiene una caída (y la tendrá), cambias al proveedor de respaldo. Cuando necesitas depurar un problema de prompt en producción, los logs ya están ahí.

Prompt engineering en producción

Los prompts en producción no son strings en tu código fuente. Son una preocupación separada que necesita versionado, pruebas y observabilidad.

El sistema de plantillas que uso es directo:

interface PromptTemplate {
  id: string;
  version: number;
  system: string;
  user: string;
  variables: string[];
}

const LISTING_DESCRIPTION: PromptTemplate = {
  id: "listing-description",
  version: 3,
  system: `You are a professional copywriter for a restaurant platform.
Write compelling menu item descriptions.
Rules:
- Max 2 sentences
- Mention key ingredients
- Never use the word "delicious" or "mouth-watering"
- Match the restaurant's tone: {{tone}}`,
  user: `Write a description for: {{itemName}}
Category: {{category}}
Ingredients: {{ingredients}}`,
  variables: ["tone", "itemName", "category", "ingredients"],
};

function renderPrompt(
  template: PromptTemplate,
  vars: Record<string, string>
): { system: string; user: string } {
  let system = template.system;
  let user = template.user;

  for (const key of template.variables) {
    const value = vars[key];
    if (!value) throw new Error(`Missing variable: ${key}`);
    system = system.replaceAll(`{{${key}}}`, value);
    user = user.replaceAll(`{{${key}}}`, value);
  }

  return { system, user };
}

El número de versión importa. Cuando cambias un prompt, incrementa la versión y regístrala junto con cada solicitud. Cuando un usuario reporta que la salida de la IA cambió, puedes rastrearlo hasta la versión exacta del prompt. Almacena las plantillas de prompts en una base de datos o archivo de configuración — no hardcodeadas — para que puedas actualizarlas sin redesplegar.

Prueba tus prompts como pruebas tu código. Mantén un conjunto de fixtures de entrada/salida. Cuando cambies un prompt, ejecuta las fixtures y revisa manualmente el diff. La evaluación automatizada está mejorando, pero la revisión humana de cambios en prompts todavía detecta problemas que las métricas pasan por alto.

Respuestas en streaming para la UX

Los usuarios tolerarán una espera de 3 segundos para una respuesta completa. No tolerarán mirar un spinner durante 15 segundos. El streaming resuelve esto.

async function* streamAIResponse(
  provider: AIProvider,
  prompt: string,
  options?: GenerateOptions
): AsyncGenerator<string> {
  const stream = provider.generateStream(prompt, options);

  for await (const chunk of stream) {
    yield chunk;
  }
}

// In your API route (Next.js example)
export async function POST(request: Request) {
  const { prompt, options } = await request.json();

  const encoder = new TextEncoder();
  const stream = new ReadableStream({
    async start(controller) {
      try {
        for await (const chunk of streamAIResponse(aiProvider, prompt, options)) {
          controller.enqueue(encoder.encode(`data: ${JSON.stringify({ text: chunk })}\n\n`));
        }
        controller.enqueue(encoder.encode("data: [DONE]\n\n"));
        controller.close();
      } catch (error) {
        controller.enqueue(
          encoder.encode(`data: ${JSON.stringify({ error: "Generation failed" })}\n\n`)
        );
        controller.close();
      }
    },
  });

  return new Response(stream, {
    headers: {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
      Connection: "keep-alive",
    },
  });
}

En el lado del cliente, consume el stream y actualiza la interfaz progresivamente. La diferencia en rendimiento percibido es dramática — los usuarios ven el contenido aparecer en 200-400ms en lugar de esperar la generación completa.

Un detalle de implementación que importa: almacena en buffer las palabras parciales en el cliente. Algunos proveedores envían tokens que cortan a mitad de palabra. Acumula un pequeño buffer y solo renderiza palabras completas para evitar parpadeo visual.

Control de costos y estrategias de caché

Los costos de APIs de IA pueden sorprenderte. Una función que cuesta $2/día en staging puede costar $2,000/día en producción si no has pensado en caché.

Caché semántico es la optimización de mayor impacto. Si dos usuarios hacen preguntas funcionalmente idénticas, sirve la respuesta cacheada. No necesitas una base de datos vectorial para esto — comienza con caché de coincidencia exacta en entradas normalizadas. Hashea el prompt (después de inyectar variables) y almacena la respuesta con un TTL.

Enrutamiento escalonado de modelos ahorra dinero sin degradar la calidad. No toda solicitud necesita tu modelo más caro. Enruta tareas simples de clasificación a un modelo más pequeño y reserva el modelo grande para generación compleja:

function selectModel(task: AITask): string {
  switch (task.complexity) {
    case "classification":
    case "extraction":
      return "gpt-4o-mini"; // fast, cheap
    case "generation":
      return "claude-sonnet-4-20250514"; // balanced
    case "reasoning":
      return "claude-opus-4-20250514"; // maximum quality
  }
}

Establece límites de presupuesto fijos. La mayoría de los proveedores soportan límites de uso a nivel de API key. Úsalos. También implementa rate limiting a nivel de aplicación por usuario — un usuario abusivo no debería consumir tu presupuesto mensual en una tarde.

Rastrea el costo por función, no solo el gasto total. Etiqueta cada llamada a la API con la función que la activó. Cuando llegue la factura, necesitas saber que la función de "auto-generar descripciones SEO" es el 60% de tu gasto, no solo que gastaste $X en total.

Degradación elegante

Las funciones de IA van a caer. Las caídas de proveedores suceden. Los rate limits se alcanzan. Las solicitudes de red expiran. Tu producto necesita seguir funcionando.

El principio: las funciones de IA deberían mejorar la experiencia, no bloquearla. Si la búsqueda potenciada por IA no está disponible, recurre a búsqueda por palabras clave. Si la generación de contenido con IA falla, muestra al usuario un formulario de entrada manual. Nunca pongas IA en una ruta crítica sin alternativa.

Implementación práctica:

  • Timeouts. Establece timeouts agresivos en las llamadas de IA (10-15 segundos máximo). Una respuesta lenta es peor que no tener respuesta para la mayoría de los flujos de UX.
  • Circuit breakers. Después de N fallos consecutivos, deja de llamar al proveedor durante un período de enfriamiento. Esto previene fallos en cascada y evita quemar dinero en solicitudes que fallarán.
  • Fallbacks pre-generados. Para funciones como descripciones de productos o recomendaciones, mantén un conjunto de fallbacks basados en plantillas que funcionen sin IA. No serán tan buenos, pero serán algo.
  • Comunicación en la interfaz. Dile al usuario qué pasó. "Las sugerencias de IA no están disponibles temporalmente" es mucho mejor que un error genérico o un spinner infinito.

Ejemplos del mundo real

Generación de contenido con IA es el punto de integración más común. Para una plataforma de marketing, esto significó construir un pipeline que toma un brief de producto, genera variaciones de copy publicitario, las evalúa contra las directrices de marca (usando una segunda llamada de IA) y presenta los mejores candidatos a un revisor humano. La clave: la IA genera, los humanos curan. La función que permite a los usuarios editar y refinar la salida de la IA es tan importante como la generación en sí.

Visión por computadora para diseño de interiores requiere una arquitectura diferente. Procesar fotos de habitaciones para análisis de estilo y detección de muebles involucra enviar imágenes a un modelo de visión, analizar la salida estructurada y emparejar resultados contra un catálogo de productos. La latencia es mayor, así que el patrón de UX cambia a procesamiento asíncrono con notificaciones push en lugar de esperar y mostrar sincrónicamente.

Búsqueda inteligente reemplaza la coincidencia tradicional por palabras clave con comprensión semántica. Para una plataforma de restaurantes, esto significó indexar los elementos del menú con embeddings, para que una búsqueda de "algo picante y vegetariano" devuelva resultados relevantes incluso si esas palabras exactas no aparecen en ningún listado. La generación de embeddings ocurre en el momento de escritura (cuando se actualizan los menús), no en el momento de consulta — esto mantiene la búsqueda rápida independientemente de la latencia del proveedor de IA.

En cada caso, los mismos principios aplican: envuelve el proveedor, versiona los prompts, cachea agresivamente y siempre ten un fallback.

Lanzando funciones de IA responsablemente

La brecha entre una demo de IA y una función de IA en producción es enorme. Las demos no necesitan caché, manejo de errores, controles de costos ni degradación elegante. La producción sí. Los patrones en este artículo no son teóricos — provienen de lanzar funciones de IA de las que usuarios reales dependen diariamente.

Desde rediseño de habitaciones potenciado por IA hasta estudios de contenido automatizados, he lanzado funciones de IA en aplicaciones móviles, plataformas SaaS y sistemas backend. La tecnología es genuinamente poderosa, pero la disciplina de ingeniería a su alrededor es lo que determina si los usuarios aman la función o aprenden a evitarla.

Comienza con el patrón wrapper, agrega observabilidad desde el primer día, cachea todo lo que pueda cachearse y siempre dale a los usuarios un camino hacia adelante cuando la IA no esté disponible. Los modelos seguirán mejorando. Tu trabajo es asegurarte de que la integración sea lo suficientemente sólida para aprovechar eso.

DU

Danil Ulmashev

Full Stack Developer

Interesado en trabajar juntos?