IA · IA para desarrolladores
¿Cómo se llama a una API de LLM?
Respuesta corta
Envía una petición HTTPS POST con tu clave de API en una cabecera y un cuerpo JSON que indique el modelo y la lista de mensajes. La respuesta es un JSON con el texto generado y el recuento de tokens. Muchos proveedores aceptan el formato de chat completions al estilo de OpenAI, mientras que Anthropic y Gemini tienen además sus propios formatos. Guarda la clave en tu servidor y paga por token.
Abrir Formateador JSONQué ocurre en una llamada a la API
Una API de LLM es un servicio web. Tu programa envía texto, los servidores del proveedor ejecutan el modelo y el resultado vuelve como JSON. No hay memoria entre llamadas: cada petición debe incluir toda la conversación que el modelo debe ver.
Necesitas tres cosas: una cuenta con una clave de API, la URL del endpoint y un ID de modelo de la lista de modelos del proveedor. En los ejemplos de abajo, MODEL_ID es un marcador de posición. Los nombres de los modelos cambian a menudo, así que copia el ID actual de la documentación del proveedor y no de una entrada de blog.
Una petición mínima en curl, Python y JavaScript
El formato de chat completions, publicado por primera vez por OpenAI, es la estructura de petición más copiada. Muchos otros proveedores y servidores locales lo aceptan, a veces solo con una URL base distinta. Esta es la petición útil más pequeña:
curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "MODEL_ID",
"messages": [
{"role": "system", "content": "You answer in one short paragraph."},
{"role": "user", "content": "What is a context window?"}
]
}'La lista messages contiene la conversación. Cada mensaje tiene un role y un content. El mensaje system fija las reglas de la respuesta (los modelos más nuevos de OpenAI también aceptan el nombre developer para este rol). Los mensajes user son las peticiones y los mensajes assistant son respuestas anteriores, que reenvías para continuar una conversación. Una respuesta abreviada tiene este aspecto:
{
"id": "chatcmpl-...",
"object": "chat.completion",
"model": "MODEL_ID",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "A context window is ..."},
"finish_reason": "stop"
}
],
"usage": {"prompt_tokens": 27, "completion_tokens": 64, "total_tokens": 91}
}El texto está en choices[0].message.content. El objeto usage cuenta los tokens que se te facturan. finish_reason indica por qué se detuvo el modelo: stop significa que terminó, mientras que length significa que alcanzó tu límite de tokens y la respuesta quedó cortada.
En Python, la misma llamada con tiempo de espera y reintentos tiene este aspecto. Usa la biblioteca común requests. Los SDK oficiales de cada proveedor hacen el mismo trabajo y añaden los reintentos por ti.
import os, random, time
import requests
URL = "https://api.openai.com/v1/chat/completions"
API_KEY = os.environ["OPENAI_API_KEY"] # read from the server environment
def chat(messages, model="MODEL_ID", max_attempts=5):
body = {"model": model, "messages": messages,
"max_completion_tokens": 500} # many compatible APIs: "max_tokens"
headers = {"Authorization": f"Bearer {API_KEY}"}
for attempt in range(max_attempts):
last = attempt == max_attempts - 1
try:
r = requests.post(URL, headers=headers, json=body, timeout=60)
except (requests.Timeout, requests.ConnectionError):
if last:
raise
time.sleep(2 ** attempt + random.random()) # network error: retry
continue
if r.status_code == 429 or r.status_code >= 500:
if last:
break
backoff = 2 ** attempt + random.random() # exponential + jitter
try:
wait = max(float(r.headers.get("Retry-After", 0)), backoff)
except ValueError:
wait = backoff
time.sleep(wait)
continue
r.raise_for_status() # 400, 401, 403: fix the request, do not retry
data = r.json()
return data["choices"][0]["message"]["content"], data["usage"]
raise RuntimeError("API still failing after retries")
text, usage = chat([{"role": "user", "content": "What is a context window?"}])
print(text)
print(usage["prompt_tokens"], "in /", usage["completion_tokens"], "out")Los proveedores difieren: compatible con OpenAI, Anthropic y Gemini
La estructura de chat completions es común, pero no universal. La Messages API de Anthropic y la API de Gemini de Google tienen sus propios formatos nativos. Ambos proveedores publican también opciones de compatibilidad, pero sus API nativas exponen primero todas las funciones. Las diferencias principales:
| Elemento | Chat completions al estilo OpenAI | Messages API de Anthropic | API de Gemini (nativa) |
|---|---|---|---|
| Endpoint | POST /v1/chat/completions | POST /v1/messages | POST /v1beta/models/{model}:generateContent |
| Cabecera de la clave de API | Authorization: Bearer <key> | x-api-key más una cabecera obligatoria de fecha anthropic-version | x-goog-api-key |
| Instrucciones del sistema | Un mensaje system o developer | Un campo system de nivel superior | Un campo systemInstruction de nivel superior |
| Campos obligatorios | model, messages | model, max_tokens, messages | contents (el modelo va en la URL) |
| Texto de la respuesta | choices[0].message.content | Bloques de texto en el array content | candidates[0].content.parts[].text |
| Streaming | "stream": true, eventos enviados por el servidor que terminan con [DONE] | "stream": true, eventos enviados por el servidor con nombre | El método streamGenerateContent con alt=sse |
Gemini ofrece una URL base compatible con OpenAI, y muchos otros proveedores, entre ellos DeepSeek y varios alojadores chinos y de modelos abiertos, aceptan el formato de chat completions. La compatibilidad suele ser parcial: funciones avanzadas como la salida estructurada, la llamada a herramientas o la entrada de imágenes pueden diferir. Lee la página de compatibilidad de cada proveedor antes de cambiar solo con modificar la URL.
Guarda la clave de API en el servidor
Cualquiera que tenga tu clave de API puede gastar tu dinero. No la pongas nunca en JavaScript del navegador, en una aplicación móvil, en un repositorio público ni en un archivo de entorno del front-end que se incluya en la página. Todo eso puede leerlo cualquier usuario.
- Guarda la clave en una variable de entorno o en un gestor de secretos de tu servidor.
- Haz que tu aplicación web o móvil llame a un endpoint de tu propio backend.
- En el backend, comprueba al usuario (inicio de sesión, límite de peticiones por usuario, longitud de la entrada) y llama entonces a la API del LLM con la clave.
- Devuelve al cliente solo el resultado.
- Fija un límite de gasto mensual en el panel del proveedor, usa claves distintas para cada proyecto y rota una clave de inmediato si se filtra.
Streaming: muestra la respuesta a medida que se escribe
Una respuesta larga puede tardar muchos segundos. Con "stream": true, el servidor envía pequeños fragmentos como eventos enviados por el servidor, de modo que tu interfaz puede mostrar el texto según va apareciendo. Cada línea de evento empieza con data: y contiene un fragmento con un delta. El formato de OpenAI termina con data: [DONE]. Este ejemplo se ejecuta en un servidor Node.js:
// Node.js 18+ (server side). Prints the reply as it arrives.
const res = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: "MODEL_ID",
stream: true,
messages: [{ role: "user", content: "Write a haiku about rain." }],
}),
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
const decoder = new TextDecoder();
let buffer = "";
for await (const chunk of res.body) {
buffer += decoder.decode(chunk, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop(); // keep a partial line for later
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
const data = line.slice(6);
if (data === "[DONE]") continue; // end of stream
const delta = JSON.parse(data).choices[0]?.delta?.content;
if (delta) process.stdout.write(delta);
}
}Para mostrar el stream en un navegador, reenvía los fragmentos desde tu servidor a la página. No llames al proveedor desde el navegador con tu clave. En el formato de OpenAI, el uso no se incluye en los fragmentos transmitidos a menos que establezcas stream_options con include_usage, así que actívalo si necesitas el recuento de tokens para la facturación.
Obtener JSON en lugar de prosa
Cuando el código vaya a leer la respuesta, pide JSON. Hay dos niveles. El modo JSON ("response_format": {"type": "json_object"}) promete un JSON válido, pero no qué campos tendrá. La salida estructurada con un esquema JSON (JSON Schema) hace que el modelo siga tus campos y tipos exactos, en los modelos que la admiten:
{
"model": "MODEL_ID",
"messages": [
{"role": "user", "content": "Extract the total and currency: 'Amount due: 1.250,00 EUR'"}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "invoice_total",
"strict": true,
"schema": {
"type": "object",
"properties": {
"total": {"type": "number"},
"currency": {"type": "string"}
},
"required": ["total", "currency"],
"additionalProperties": false
}
}
}
}Incluso con un esquema, valida en tu código el resultado analizado. Los valores pueden seguir siendo erróneos, por ejemplo un número mal leído, aunque la forma sea correcta. Anthropic y Gemini admiten esquemas y definiciones de herramientas en sus propios formatos. Cuando depures respuestas JSON largas, un formateador como la herramienta json de cv.cm facilita la lectura de la salida anidada.
Límites de tasa, reintentos y errores
Los proveedores limitan tanto las peticiones por minuto (RPM) como los tokens por minuto (TPM), y se aplica el que alcances primero. Cuando superas un límite, recibes el estado HTTP 429. Trata los errores según su tipo:
| Estado | Significado | Qué hacer |
|---|---|---|
| 400 | Petición incorrecta: campo erróneo, demasiados tokens | Corrige la petición. No reintentes. |
| 401 / 403 | Clave ausente, incorrecta o sin autorización | Comprueba la clave y los permisos. No reintentes. |
| 429 | Límite de tasa o, a veces, cuota agotada | Espera y reintenta con backoff. Si menciona cuota o facturación, detente y arregla la cuenta. |
| 500 / 503 | Error del servidor o sobrecarga | Reintenta con backoff. |
| Timeout | No hay respuesta a tiempo | Reintenta una o dos veces. Usa streaming para respuestas largas. |
Reintenta con backoff exponencial: espera aproximadamente 1 segundo, luego 2, 4, 8, más un pequeño retardo aleatorio (jitter) para que muchos clientes no reintenten en el mismo instante. Si la respuesta incluye una cabecera Retry-After, espera al menos ese tiempo. Limita el número de intentos. Las peticiones fallidas también pueden contar para tu límite, así que reintentar al instante en un bucle cerrado empeora las cosas. Si usas un SDK oficial, recuerda que ya reintenta algunos errores.
El coste se cuenta en tokens
Pagas por los tokens de entrada (todo lo que envías, incluidos el prompt del sistema y todo el historial de la conversación) y por los tokens de salida (lo que escribe el modelo). Los tokens de salida suelen tener un precio mayor que los de entrada. La fórmula es sencilla:
cost = input_tokens / 1,000,000 * input_price_per_million
+ output_tokens / 1,000,000 * output_price_per_million
Example with made-up prices (2.00 in, 8.00 out per million tokens):
27 input + 64 output tokens
= 0.000054 + 0.000512 = 0.000566 per call
x 100,000 calls a month = about 56.60- Lee los recuentos reales de
usageen cada respuesta y regístralos. No los estimes a partir del número de palabras. - Limita la longitud de cada respuesta:
max_completion_tokensen OpenAI (el antiguomax_tokensestá obsoleto allí),max_tokensen Anthropic y en muchas API compatibles. - El historial del chat crece con cada turno, así que el mensaje número 20 cuesta más que el primero. Recorta o resume los turnos antiguos.
- Usa un modelo más pequeño para tareas sencillas como la clasificación, y uno más grande solo donde la calidad lo requiera.
- Algunos proveedores aplican descuento a los prefijos de prompt repetidos (prompt caching) u ofrecen un procesamiento por lotes más barato para trabajos que pueden esperar.
Preguntas frecuentes
¿Puedo llamar a una API de LLM directamente desde el navegador?
No de forma segura con tu propia clave, porque cualquier visitante puede leerla en la página. Llama a tu propio servidor, que añade la clave. La excepción es una clave que el usuario introduce para su propia cuenta, o un modelo que se ejecuta íntegramente en el navegador.
¿Por qué la API olvida lo que dije antes?
La API no guarda estado. Cada petición debe incluir los mensajes anteriores que quieres que vea el modelo. Las aplicaciones de chat almacenan el historial y lo reenvían cada vez.
¿Qué hace la temperatura y debería configurarla?
La temperatura controla lo aleatoria que es la elección de palabras: más baja es más predecible, más alta es más variada. Algunos proveedores y modelos nuevos restringen o eliminan los parámetros de muestreo, así que consulta la referencia de la API antes de configurarla.
¿Por qué mi respuesta se corta a la mitad?
La respuesta alcanzó el límite de tokens que fijaste o la salida máxima del modelo. Comprueba finish_reason (o stop_reason en Anthropic). Sube el límite o pide una respuesta más corta.
¿Existe una API de LLM gratuita?
Varios proveedores ofrecen planes gratuitos o créditos de prueba con límites de tasa bajos, y los modelos abiertos pueden ejecutarse en tu propio equipo detrás de un servidor local compatible con OpenAI. Los planes gratuitos y sus condiciones cambian a menudo, así que consulta la página actual del proveedor.