Cómo integrar LLMs en una aplicación real: guía práctica

Integrar un LLM en una aplicación ya no es algo reservado a equipos de investigación. Con las APIs actuales, cualquier developer puede sumar capacidades de AI a su stack en cuestión de horas. El problema aparece después: el prototipo funciona, pero el historial se pierde, las respuestas tardan demasiado, el modelo inventa datos o la factura sube sin aviso. Esta guía cubre los patrones más habituales para integrar LLMs en un producto real, con código y los errores que conviene evitar desde el principio.
Por dónde empezar: elegir API y entender el rol
Antes de escribir código hay que elegir proveedor. Las opciones más populares son OpenAI (GPT-4o y GPT-4o-mini), con una API madura y una comunidad enorme; Anthropic (Claude), que destaca en contextos largos y en el seguimiento de instrucciones; Google (Gemini), competitivo en precio y natural para equipos que ya usan Google Cloud; y proveedores de inferencia rápida como Groq, Together AI o Fireworks, pensados para modelos open source.
Esta guía usa la API de OpenAI como referencia, pero los patrones son equivalentes en todos los proveedores. Lo que cambia es el SDK, no la lógica de la integración.
Esta tarea tiene nombre propio en el mercado: es el trabajo del AI Engineer. Un AI Engineer construye y opera sistemas de AI sobre modelos que ya existen. No entrena modelos desde cero ni investiga arquitecturas nuevas; toma una API, la conecta con datos y herramientas propias, y se encarga de que el sistema funcione en producción, con costos controlados y resultados medibles. Si te preguntas qué es un AI engineer, la respuesta práctica es esta: alguien que hace exactamente lo que vas a leer en las próximas secciones.
Tu primera llamada al modelo
El SDK oficial se instala con pip install openai en Python o npm install openai en JavaScript/TypeScript. Una llamada básica tiene tres partes: el modelo, un mensaje de sistema que define el rol y el mensaje del usuario.
python
import os
from openai import OpenAI
# La clave se lee desde una variable de entorno, nunca se escribe en el código
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "Eres un asistente técnico experto en Python."},
{"role": "user", "content": "¿Cómo itero sobre un diccionario en Python?"},
],
max_tokens=500,
temperature=0.7,
)
print(response.choices[0].message.content)Los parámetros que más pesan en el resultado son estos:
| Parámetro | Qué controla | Valores habituales |
|---|---|---|
model | Qué modelo responde | gpt-4o, gpt-4o-mini, claude-3-5-sonnet-20241022 |
temperature | Cuánta variación hay en las respuestas | 0 (determinista) a 1 (más creativo) |
max_tokens | Largo máximo de la respuesta | 256 a 4096, según el caso |
top_p | Muestreo alternativo a temperature | 0.9 a 1.0 (se recomienda tocar solo uno de los dos) |
presence_penalty | Penaliza repetir temas ya tratados | 0 a 2 |
Para tareas que requieren precisión, como extraer datos o responder según una política, conviene bajar la temperatura. Para redacción o ideas, puede subirse. No hay un valor mágico: se ajusta probando.
Respuestas en tiempo real con streaming
Nadie quiere esperar diez segundos a que aparezca toda la respuesta de golpe. El streaming muestra los tokens a medida que se generan, igual que una interfaz de chat. En Python alcanza con activar stream=True y recorrer los fragmentos:
python
stream = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Resume los patrones de diseño más usados en Python."}],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content is not None:
print(chunk.choices[0].delta.content, end="", flush=True)Si tu backend es una API REST, puedes reenviar esos fragmentos al frontend usando Server-Sent Events con FastAPI. En el navegador, el stream se consume con EventSource o con fetch y ReadableStream. Esta es la diferencia entre una demo y un producto que se siente rápido.
Function calling: que el modelo use tu código
El function calling es una de las capacidades más potentes de los LLMs modernos. Le describes al modelo las funciones que tu sistema puede ejecutar, y él decide cuándo llamarlas y con qué parámetros, extraídos del mensaje del usuario. Tu código ejecuta la función y le devuelve el resultado para que redacte la respuesta final.
python
import json
from openai import OpenAI
client = OpenAI()
tools = [{
"type": "function",
"function": {
"name": "get_order_status",
"description": "Devuelve el estado de un pedido a partir de su número",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "Número de pedido, por ejemplo 'A-1042'"}
},
"required": ["order_id"],
},
},
}]
def get_order_status(order_id: str) -> dict:
# Aquí iría la consulta real a tu base de datos o a tu API interna
return {"order_id": order_id, "status": "en camino"}
messages = [{"role": "user", "content": "¿Dónde está mi pedido A-1042?"}]
response = client.chat.completions.create(model="gpt-4o", messages=messages, tools=tools)
message = response.choices[0].message
if message.tool_calls:
call = message.tool_calls[0]
args = json.loads(call.function.arguments)
messages.append(message)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(get_order_status(**args)),
})
final = client.chat.completions.create(model="gpt-4o", messages=messages)
print(final.choices[0].message.content)Este patrón es la base de los agentes: sistemas que combinan un LLM con herramientas externas para resolver tareas más complejas. Es también el punto donde más se nota la diferencia entre improvisar y diseñar: cada función expuesta al modelo es una superficie que hay que validar y limitar.
Historial de conversación y ventana de contexto
Los LLMs no recuerdan nada entre llamadas. Para mantener una conversación coherente, tu aplicación debe enviar el historial completo cada vez. Una clase sencilla lo resuelve:
python
class ConversationManager:
def __init__(self, system_prompt: str):
self.client = OpenAI()
self.messages = [{"role": "system", "content": system_prompt}]
def chat(self, user_message: str) -> str:
self.messages.append({"role": "user", "content": user_message})
response = self.client.chat.completions.create(
model="gpt-4o",
messages=self.messages,
max_tokens=1000,
)
assistant_message = response.choices[0].message.content
self.messages.append({"role": "assistant", "content": assistant_message})
return assistant_message
def clear(self):
"""Reinicia la conversación manteniendo el system prompt."""
self.messages = [self.messages[0]]El problema aparece cuando la conversación crece y supera la ventana de contexto del modelo. Hay tres estrategias comunes. La primera es FIFO: descartar los mensajes más antiguos. La segunda es resumir: pedirle al propio modelo un resumen de los turnos previos antes de eliminarlos. La tercera es usar embeddings y una base vectorial para guardar todos los mensajes y recuperar solo los relevantes para cada consulta. En proyectos reales, la elección depende de cuánta memoria necesita el producto y de cuánto costo estás dispuesto a asumir.
Errores comunes y cómo evitarlos
El primero es no manejar los rate limits. Las APIs tienen límites de peticiones por minuto, y un sistema sin reintentos se cae en el primer pico de tráfico. La solución es un backoff exponencial con un máximo de reintentos, que espera cada vez más entre intentos y agrega un poco de aleatoriedad para no reintentar todos a la vez.
El segundo es hardcodear la clave de API. Nunca debe aparecer en el código fuente ni subirse a un repositorio. Usa variables de entorno o un gestor de secretos, y rota la clave si alguna vez quedó expuesta.
El tercero es no medir el uso de tokens. Cada llamada tiene un costo, y el objeto response.usage trae la cantidad de tokens consumidos. Registrarlo desde el primer día permite detectar qué flujo está gastando de más antes de que llegue a la factura.
El cuarto es escribir prompts vagos. Cuanto más específico y estructurado sea el pedido, mejor será el resultado. Pedir “extrae los datos” deja demasiado libre; pedir “extrae nombre, email y teléfono en formato JSON, y usa null si un campo no aparece” produce una salida que tu código puede procesar.
El quinto, y el más caro, es confiar ciegamente en el output. Los modelos pueden alucinar con total seguridad. Valida el formato de salida, sobre todo cuando esperas JSON, con una herramienta como pydantic. Y, sobre todo, mide la calidad con evals: un conjunto de casos con respuestas esperadas que corres cada vez que cambias un prompt. Sin esas pruebas, no sabes si tu sistema mejora o empeora.
Quién construye estas integraciones y cómo seguir
Las integraciones que acabas de ver son el trabajo cotidiano del AI Engineer, y el rol aparece como el de mayor crecimiento en el ranking Jobs on the Rise 2026 de LinkedIn. Conviene leer ese dato con precisión. El mercado de software se inclinó hacia perfiles senior: en el relevamiento de búsquedas que usamos para diseñar esta carrera, los avisos senior llegaron a cerca del 70% del total en el primer trimestre de 2026, mientras que los de nivel inicial apenas alcanzaron el 5%. Solo alrededor de 2,5% de los avisos de AI Engineer apunta a profesionales con entre cero y dos años de experiencia, y la mediana de experiencia de quienes son contratados ronda los 3,7 años.
Por eso, el perfil que más se beneficia es el de un developer con al menos tres años de experiencia en backend, full-stack o data, que ya llevó algún producto de punta a punta y quiere construir con AI en serio. Algunos buscan reposicionarse como AI Engineer; otros quieren sumar AI a su rol actual, por ejemplo para construir sus propios agentes.
Si ya tienes esa base, la forma más rápida de avanzar es repetir este recorrido en un proyecto propio: una llamada, luego streaming, después function calling, evals y un despliegue para que alguien más lo use. Si prefieres un camino guiado, Henry ofrece la carrera AI Engineering: un programa part time de 14 semanas, con clases en vivo y un proyecto que crece a lo largo de los módulos. Puedes conocer el plan de estudios completo en la landing de AI Engineering.
Preguntas frecuentes
¿Qué es un AI Engineer?
Es el profesional que diseña, construye y opera sistemas de AI sobre modelos que ya existen. No entrena modelos desde cero: se encarga de que un LLM funcione dentro de un producto real, con datos propios, herramientas, costos controlados y evaluaciones que demuestren que el sistema responde bien.
¿En qué se diferencia de un Data Scientist o de un ML Engineer?
El Data Scientist suele analizar datos y construir modelos para responder preguntas de negocio. El ML Engineer se concentra en entrenar, optimizar y desplegar modelos propios. El AI Engineer parte de modelos ya entrenados, sean de un proveedor o open source, y se enfoca en cómo se integran con la aplicación, qué contexto reciben y cómo se mide su calidad.
¿Cuánta experiencia previa se necesita?
El perfil más común en los procesos de selección es el de un developer con varios años de experiencia en backend, full-stack o data. En la carrera de Henry, el requisito es de al menos tres años programando. Quien empieza de cero necesita primero una base sólida de programación antes de encarar este camino.
¿Qué es una eval y por qué es tan importante?
Una eval es una prueba que verifica si el sistema responde bien según criterios definidos de antemano. Como un LLM puede dar respuestas distintas ante la misma pregunta, las evals permiten detectar regresiones cuando cambian un prompt, un modelo o los datos. Son una de las habilidades más valoradas en este rol.
¿Qué es RAG y en qué casos se usa?
RAG (Retrieval-Augmented Generation) consiste en buscar información relevante en documentos propios y entregársela al modelo como contexto antes de que responda. Se usa en asistentes de soporte, buscadores internos y sistemas que deben responder con base en documentación específica de una empresa.