Guía de la API de OpenAI con ejemplos en Python
Guía de producción de la API de OpenAI: Responses API, chat completions, streaming, function calling, salidas estructuradas, embeddings, fine-tuning, batch jobs, rate limits y control de costes.
Introducción
La API de OpenAI parece sencilla hasta que la ejecutas en producción: el streaming se queda colgado, los rate limits golpean en el peor momento, una integración que “funciona” quema tokens en silencio y la propia API cambia bajo tus pies. El cambio más grande reciente es que OpenAI consolidó todo alrededor de la Responses API, que absorbe chat, llamadas a herramientas y salida estructurada en una sola interfaz — la Assistants API a la que reemplaza está ahora deprecada.
Esta guía cubre ambas superficies con honestidad: la Responses API como opción por defecto para trabajo nuevo, chat completions porque la mayoría del código en producción (y la mayoría de proveedores compatibles) aún lo usan, además de streaming, embeddings, fine-tuning, batch jobs y el manejo de errores que de verdad necesitarás. Los ejemplos son principalmente Python, con variantes en JavaScript donde difieren de forma relevante. Las versiones ejecutables están en el repositorio complementario.
Responses API
La Responses API es la interfaz recomendada actual de OpenAI. Una sola llamada a responses.create maneja chat simple, herramientas integradas, function calling y salida estructurada, y puede mantener estado en el servidor para no reenviar toda la conversación en cada turno.
Respuesta básica
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-4o",
input="Explain Python decorators in 2 sentences."
)
print(response.output_text)
print(f"Tokens used: {response.usage.total_tokens}")
output_text aplana la respuesta por ti — sin rebuscar en choices[0].message.content. Para conversaciones multi-turno, encadena respuestas con previous_response_id en lugar de gestionar la lista de mensajes a mano:
first = client.responses.create(
model="gpt-4o",
input="You are a Python expert. What is a decorator?"
)
follow_up = client.responses.create(
model="gpt-4o",
previous_response_id=first.id,
input="Show me an example."
)
print(follow_up.output_text)
Salida estructurada con responses.parse
Para salida tipada, responses.parse acepta un modelo Pydantic directamente y valida el resultado:
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
email: str
skills: list[str]
response = client.responses.parse(
model="gpt-4o",
input=[
{"role": "system", "content": "Extract person information."},
{"role": "user", "content": "Jane Smith, 28, jane@test.com. Skills: Python, SQL, Docker."}
],
text_format=Person,
)
person = response.output_parsed
print(f"Name: {person.name}, Age: {person.age}")
Es el camino más limpio cuando necesitas campos garantizados — sin parsear JSON ni validar esquemas a mano.
Function calling en Responses API
Las function tools tienen aquí una forma aplanada (sin la clave function anidada), y las llamadas vuelven como items en response.output:
tools = [
{
"type": "function",
"name": "get_weather",
"description": "Get current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City name, e.g. 'Madrid, Spain'"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["location"]
},
"strict": True
}
]
response = client.responses.create(
model="gpt-4o",
input="What's the weather in Madrid?",
tools=tools
)
for item in response.output:
if item.type == "function_call":
result = get_weather(**json.loads(item.arguments))
# Devolver el resultado de la herramienta como function_call_output
follow = client.responses.create(
model="gpt-4o",
previous_response_id=response.id,
input=[{
"type": "function_call_output",
"call_id": item.call_id,
"output": result
}]
)
print(follow.output_text)
Chat Completions
Chat completions no va a desaparecer: es lo que usa la mayoría del código existente, lo que implementan los proveedores compatibles con OpenAI, y sigue perfectamente soportado. Si mantienes una integración existente no hay urgencia por migrar — para proyectos nuevos, prefiere la Responses API de arriba.
Completion básico
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain Python decorators in 2 sentences."}
],
temperature=0.7,
max_tokens=200
)
print(response.choices[0].message.content)
print(f"Tokens used: {response.usage.total_tokens}")
Conversación multi-turno
A diferencia de la Responses API, chat completions no tiene estado — envías todo el historial en cada llamada. Mantener la conversación en una lista e ir añadiendo ambos lados es el patrón estándar:
conversation = [
{"role": "system", "content": "You are a Python expert."},
{"role": "user", "content": "What is a decorator?"}
]
response = client.chat.completions.create(
model="gpt-4o",
messages=conversation
)
assistant_msg = response.choices[0].message.content
conversation.append({"role": "assistant", "content": assistant_msg})
conversation.append({"role": "user", "content": "Show me an example."})
response = client.chat.completions.create(model="gpt-4o", messages=conversation)
print(response.choices[0].message.content)
Ojo con el coste en tokens: las conversaciones largas reenvían todo el historial en cada llamada, así que truncar o resumir el historial se vuelve obligatorio pasadas unas decenas de turnos.
Selección de modelo
Los precios se mueven, así que toma las cifras como orientativas y consulta la página de precios antes de presupuestar:
| Modelo | Mejor para | Entrada ($/1M) | Salida ($/1M) |
|---|---|---|---|
| gpt-4o | General, multimodal, tareas complejas | ~2.50 | ~10.00 |
| gpt-4o-mini | Tareas simples, alto volumen | ~0.15 | ~0.60 |
| o1 | Razonamiento multi-paso | ~15.00 | ~60.00 |
| o1-mini | Razonamiento de código/mates, más barato | ~3.00 | ~12.00 |
| text-embedding-3-small | Embeddings más baratos | ~0.02 | — |
| text-embedding-3-large | Embeddings de máxima calidad, 3072 dims | ~0.13 | — |
Una regla práctica: enruta todo a gpt-4o-mini y sube a gpt-4o solo donde las evaluaciones demuestren que importa. Para profundizar, consulta la guía de optimización de costes de LLM.
Streaming
El streaming no hace el modelo más rápido — la latencia total es la misma — pero el tiempo hasta el primer token baja a unos pocos cientos de milisegundos, que es lo que el usuario percibe. Haz streaming siempre que un humano esté esperando la salida; sáltalo en trabajos en segundo plano donde solo añade complejidad.
Streaming con Server-Sent Events
from openai import OpenAI
client = OpenAI()
stream = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Write a poem about Python."}],
stream=True
)
full_text = ""
for chunk in stream:
if chunk.choices[0].delta.content is not None:
token = chunk.choices[0].delta.content
full_text += token
print(token, end="", flush=True)
print(f"\n\nFull text length: {len(full_text)}")
Streaming en JavaScript
El SDK de Node expone el mismo patrón como un iterador asíncrono:
import OpenAI from "openai";
const client = new OpenAI();
const stream = await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Explain async programming." }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
Streaming con FastAPI
El patrón de producción: envolver el stream del SDK en un generador y devolverlo como StreamingResponse. Ten en cuenta que el endpoint emite chunks con formato SSE, así que el cliente debe parsear las líneas data: — los eventos del SDK de OpenAI no son texto plano.
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from openai import AsyncOpenAI
import json
app = FastAPI()
client = AsyncOpenAI()
@app.post("/chat/stream")
async def chat_stream(request: dict):
async def generate():
stream = await client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": request["message"]}
],
stream=True
)
async for chunk in stream:
if chunk.choices[0].delta.content:
data = json.dumps({"token": chunk.choices[0].delta.content})
yield f"data: {data}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")
Para una implementación SSE completa incluyendo el consumo desde el cliente, consulta la receta sobre streaming de salida de LLM con SSE.
Function Calling
El function calling convierte al modelo en un enrutador: declaras qué funciones existen, el modelo decide cuándo llamarlas y con qué argumentos, y tu código ejecuta el trabajo real. El modelo nunca ejecuta tu código — solo propone llamadas. El ciclo es este:
Definir funciones en chat completions
from openai import OpenAI
import json
client = OpenAI()
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name, e.g. 'Madrid, Spain'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
}
}
}
]
def get_weather(location: str, unit: str = "celsius") -> str:
# Llamada a API simulada
return f"Weather in {location}: 22°{unit[0].upper()}, sunny"
function_map = {"get_weather": get_weather}
messages = [{"role": "user", "content": "What's the weather in Madrid?"}]
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools
)
tool_calls = response.choices[0].message.tool_calls
if tool_calls:
messages.append(response.choices[0].message)
for call in tool_calls:
func_args = json.loads(call.function.arguments)
result = function_map[call.function.name](**func_args)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": result
})
# Respuesta final con los resultados de las funciones
final = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools
)
print(final.choices[0].message.content)
Dos detalles de producción que la gente se salta: valida siempre los argumentos que devuelve el modelo (es una sugerencia, no una garantía), y acota el bucle — un modelo que sigue proponiendo tool calls puede quemar tokens indefinidamente. La receta de function calling con salida estructurada cubre setups multi-tool en profundidad.
Salidas estructuradas
Existen dos niveles de garantía. El JSON mode fuerza JSON sintácticamente válido pero no dice nada sobre la forma. Las salidas estructuradas con json_schema imponen tu esquema exacto — nombres de campos, tipos, claves requeridas — que es lo que quieres cuando el código posterior consume el resultado.
JSON Mode
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a data extractor. Return valid JSON only."},
{"role": "user", "content": "Extract name, age, and email from: John Doe, 30, john@example.com"}
],
response_format={"type": "json_object"}
)
import json
data = json.loads(response.choices[0].message.content)
print(data) # {"name": "John Doe", "age": 30, "email": "john@example.com"}
Salidas estructuradas con esquema
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
email: str
skills: list[str]
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "Extract person information."},
{"role": "user", "content": "Jane Smith, 28, jane@test.com. Skills: Python, SQL, Docker."}
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "person",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"email": {"type": "string"},
"skills": {"type": "array", "items": {"type": "string"}}
},
"required": ["name", "age", "email", "skills"],
"additionalProperties": False
},
"strict": True
}
}
)
person = Person(**json.loads(response.choices[0].message.content))
print(f"Name: {person.name}, Age: {person.age}, Skills: {person.skills}")
En la Responses API, responses.parse con text_format (mostrado arriba) te da la misma garantía con menos boilerplate. Uses lo que uses, additionalProperties: false y strict: true son lo que realmente impone el esquema — omítelos y el modelo puede añadir campos que no pediste.
Embeddings
Los embeddings convierten texto en vectores para comparar significado numéricamente — búsqueda, retrieval para RAG, deduplicación y clustering se construyen sobre ellos. La lección barata que la mayoría de equipos aprende tarde: los embeddings son casi gratis por llamada, pero se vuelven caros a escala de corpus, así que las dimensiones y el tamaño de batch importan.
Generar embeddings
response = client.embeddings.create(
model="text-embedding-3-small",
input=["Python is a programming language", "Java is also a programming language"]
)
embeddings = [d.embedding for d in response.data]
print(f"Embedding dimensions: {len(embeddings[0])}") # 1536
# Similitud coseno
import numpy as np
sim = np.dot(embeddings[0], embeddings[1]) / (
np.linalg.norm(embeddings[0]) * np.linalg.norm(embeddings[1])
)
print(f"Similarity: {sim:.4f}")
Reducción de dimensionalidad
El parámetro dimensions cambia un poco de recall por ahorro real en almacenamiento y tamaño del índice — útil cuando embebes millones de documentos:
# Reducir de 3072 a 256 dimensiones (ahorro de almacenamiento)
response = client.embeddings.create(
model="text-embedding-3-large",
input="Python programming language",
dimensions=256
)
print(f"Reduced dimensions: {len(response.data[0].embedding)}") # 256
Para patrones de búsqueda por similitud y construcción de índices, consulta embeddings de OpenAI con similitud coseno.
Fine-Tuning
El fine-tuning es la última herramienta, no la primera. El prompt engineering y los ejemplos few-shot te llevan la mayor parte del camino; haz fine-tuning cuando necesites formato/tono consistente a escala, cuando llegues al límite de contexto con ejemplos, o cuando quieras correr un modelo más pequeño (más barato y rápido) con tu dominio incorporado. El coste real no es el job de entrenamiento — es curar los datos.
Preparar los datos de entrenamiento
import json
training_data = [
{
"messages": [
{"role": "system", "content": "You are a customer support agent for TechCorp."},
{"role": "user", "content": "How do I reset my password?"},
{"role": "assistant", "content": "To reset your password: 1) Go to techcorp.com/reset 2) Enter your email 3) Click the link in the email 4) Set a new password."}
]
},
{
"messages": [
{"role": "system", "content": "You are a customer support agent for TechCorp."},
{"role": "user", "content": "What are your business hours?"},
{"role": "assistant", "content": "Our business hours are Monday-Friday, 9 AM to 6 PM CET. We also offer 24/7 email support."}
]
}
]
# Guardar como JSONL
with open("training_data.jsonl", "w") as f:
for item in training_data:
f.write(json.dumps(item) + "\n")
Cincuenta ejemplos excelentes superan a quinientos mediocres — los malos ejemplos enseñan al modelo tus malos hábitos. Planifica la evaluación antes de entrenar, no después.
Crear un modelo fine-tuned
from openai import OpenAI
client = OpenAI()
# Subir el archivo de entrenamiento
file = client.files.create(
file=open("training_data.jsonl", "rb"),
purpose="fine-tune"
)
# Crear el job de fine-tuning
job = client.fine_tuning.jobs.create(
training_file=file.id,
model="gpt-4o-mini",
hyperparameters={
"n_epochs": 3,
"batch_size": 4,
"learning_rate_multiplier": 0.5
}
)
print(f"Fine-tune job ID: {job.id}")
print(f"Status: {job.status}")
# Consultar el estado
import time
while True:
job = client.fine_tuning.jobs.retrieve(job.id)
print(f"Status: {job.status}")
if job.status in ["succeeded", "failed"]:
break
time.sleep(30)
if job.status == "succeeded":
print(f"Fine-tuned model: {job.fine_tuned_model}")
# Usar el modelo fine-tuned
response = client.chat.completions.create(
model=job.fine_tuned_model,
messages=[
{"role": "system", "content": "You are a customer support agent for TechCorp."},
{"role": "user", "content": "How do I cancel my subscription?"}
]
)
print(response.choices[0].message.content)
Para el flujo completo incluyendo validation sets y evaluaciones, consulta la receta de fine-tuning de LLM.
Batch API
La Batch API cambia latencia por precio: los jobs corren en una ventana de 24 horas aproximadamente a mitad de coste estándar. Encaja en todo lo que no necesita respuesta inmediata — clasificación masiva, evaluaciones offline, enriquecimiento nocturno. La parte incómoda es el flujo por archivos: escribes JSONL, lo subes, haces polling y descargas resultados.
Enviar un batch
import json
# Preparar las peticiones del batch
requests = [
{
"custom_id": f"request-{i}",
"method": "POST",
"url": "/v1/chat/completions",
"body": {
"model": "gpt-4o-mini",
"messages": [
{"role": "system", "content": "Classify the sentiment as positive, negative, or neutral."},
{"role": "user", "content": f"Review: {review}"}
]
}
}
for i, review in enumerate(reviews)
]
# Guardar como JSONL
with open("batch_requests.jsonl", "w") as f:
for req in requests:
f.write(json.dumps(req) + "\n")
# Subir el archivo
batch_file = client.files.create(
file=open("batch_requests.jsonl", "rb"),
purpose="batch"
)
# Crear el batch
batch = client.batches.create(
input_file_id=batch_file.id,
endpoint="/v1/chat/completions",
completion_window="24h"
)
print(f"Batch ID: {batch.id}")
print(f"Status: {batch.status}")
# Consultar estado (el batch tarda minutos u horas)
batch = client.batches.retrieve(batch.id)
if batch.status == "completed":
# Descargar resultados
result_file = client.files.content(batch.output_file_id)
results = result_file.text.strip().split("\n")
for line in results:
result = json.loads(line)
if result.get("error"):
print(f"Error: {result['error']}")
else:
content = result["response"]["body"]["choices"][0]["message"]["content"]
print(f"{result['custom_id']}: {content}")
custom_id es lo único que enlaza los resultados con tu entrada — pon ahí tus claves de base de datos, no solo números de secuencia.
Assistants API (deprecada)
La Assistants API — threads, runs y client.beta.assistants — está deprecada y programada para retirada; OpenAI dirige las integraciones nuevas a la Responses API, que cubre los mismos casos de uso (estado persistente, tools, file search) con un modelo más simple. Si mantienes un Assistant existente, la ruta de migración es: los threads se convierten en cadenas de previous_response_id o estado en servidor, el cableado de tools pasa a function tools, y el code interpreter se convierte en herramienta integrada de la Responses API.
Para código legado que aún no migras, el patrón de abajo sigue funcionando — pero no construyas nada nuevo sobre él:
# Patrón legado — migra a Responses API para trabajo nuevo
assistant = client.beta.assistants.create(
name="Code Reviewer",
model="gpt-4o",
instructions="You are a senior code reviewer.",
tools=[{"type": "code_interpreter"}]
)
thread = client.beta.threads.create()
client.beta.threads.messages.create(
thread_id=thread.id, role="user", content="Review: def add(a,b): return a+b"
)
run = client.beta.threads.runs.create(thread_id=thread.id, assistant_id=assistant.id)
Rate Limits y manejo de errores
Los rate limits son por modelo y se dividen en peticiones por minuto y tokens por minuto — puedes reventar el límite de tokens sin acercarte al de peticiones. Toda integración en producción necesita dos cosas: un limitador en el cliente que se mantenga por debajo de tu nivel, y backoff con jitter para cuando lo excedas igualmente.
Manejo de rate limits
import asyncio
import time
from openai import AsyncOpenAI, RateLimitError
client = AsyncOpenAI()
async def call_with_backoff(messages, max_retries=5):
for attempt in range(max_retries):
try:
response = await client.chat.completions.create(
model="gpt-4o",
messages=messages
)
return response
except RateLimitError as e:
# Parsear la cabecera retry-after
retry_after = getattr(e, "retry_after", 2 ** attempt)
print(f"Rate limited. Retrying in {retry_after}s...")
await asyncio.sleep(retry_after)
raise RuntimeError("Max retries exceeded")
# Rate limiter tipo token bucket
class RateLimiter:
def __init__(self, requests_per_minute: int):
self.interval = 60.0 / requests_per_minute
self.last_request = 0.0
async def wait(self):
now = time.time()
elapsed = now - self.last_request
if elapsed < self.interval:
await asyncio.sleep(self.interval - elapsed)
self.last_request = time.time()
limiter = RateLimiter(requests_per_minute=50) # Límite RPM
async def rate_limited_call(messages):
await limiter.wait()
return await client.chat.completions.create(model="gpt-4o", messages=messages)
Tipos de error
Mapea cada excepción a una reacción distinta: AuthenticationError es callejón sin salida (alerta, no reintentes), RateLimitError espera, APITimeoutError/APIConnectionError reintenta, y BadRequestError significa que tu petición está rota — reintentar solo vuelve a fallar más despacio.
from openai import (
APIError,
RateLimitError,
APIConnectionError,
APITimeoutError,
AuthenticationError,
BadRequestError
)
async def robust_call(messages):
try:
return await client.chat.completions.create(model="gpt-4o", messages=messages)
except AuthenticationError:
# API key inválida
raise RuntimeError("Invalid API key. Check OPENAI_API_KEY.")
except BadRequestError as e:
# Petición inválida (modelo o params incorrectos)
raise RuntimeError(f"Bad request: {e}")
except RateLimitError:
# Rate limit alcanzado
await asyncio.sleep(60)
return await robust_call(messages)
except APITimeoutError:
# La petición agotó el timeout
return await robust_call(messages)
except APIConnectionError:
# Error de red
await asyncio.sleep(5)
return await robust_call(messages)
except APIError as e:
# Error genérico de la API
raise RuntimeError(f"OpenAI API error: {e}")
Optimización de costes
La mayoría de problemas de coste se reducen a tres cosas: el modelo equivocado para la tarea, reenviar contexto que no necesitas, y max_tokens sin techo. Antes de cualquier optimización sofisticada, pon un tracker delante del cliente para ver qué llamadas cuestan dinero de verdad — lo que no mides se optimiza a ciegas.
class CostTracker:
PRICING = {
"gpt-4o": {"input": 2.50, "output": 10.00},
"gpt-4o-mini": {"input": 0.15, "output": 0.60},
"text-embedding-3-small": {"input": 0.02, "output": 0.0},
}
def __init__(self):
self.costs: list[dict] = []
def track(self, model: str, input_tokens: int, output_tokens: int):
if model not in self.PRICING:
return
pricing = self.PRICING[model]
cost = (input_tokens / 1_000_000 * pricing["input"]) + \
(output_tokens / 1_000_000 * pricing["output"])
self.costs.append({
"model": model,
"input_tokens": input_tokens,
"output_tokens": output_tokens,
"cost": cost
})
def total_cost(self) -> float:
return sum(c["cost"] for c in self.costs)
def summary(self) -> dict:
by_model = {}
for c in self.costs:
if c["model"] not in by_model:
by_model[c["model"]] = {"calls": 0, "cost": 0.0, "tokens": 0}
by_model[c["model"]]["calls"] += 1
by_model[c["model"]]["cost"] += c["cost"]
by_model[c["model"]]["tokens"] += c["input_tokens"] + c["output_tokens"]
return {
"total_cost": self.total_cost(),
"by_model": by_model,
"total_calls": len(self.costs)
}
# Uso
tracker = CostTracker()
response = client.chat.completions.create(model="gpt-4o", messages=[...])
tracker.track("gpt-4o", response.usage.prompt_tokens, response.usage.completion_tokens)
print(f"Total cost: ${tracker.total_cost():.4f}")
Errores comunes
- Reintentar
BadRequestErroren bucle — una petición malformada falla para siempre; arregla la petición, no la política de reintentos. - Sin techo en los bucles de tool calls — acota las iteraciones; un modelo confundido propone llamadas indefinidamente.
- Reenviar todo el historial en cada turno — trunca o resume pasados ~20 turnos, o usa
previous_response_id. - API key en código de cliente — las claves en el JS que se distribuye se scrapean en horas; pasa las llamadas por tu backend.
- Fine-tuning antes que prompting — tunea solo cuando el prompt engineering se estanque en evaluaciones, no por intuición.
- Contar peticiones, no tokens — los límites TPM saltan por tokens; unos pocos prompts gigantes pueden generar 429 en una app de bajo tráfico.
See Also
- Complete Guide to LLM Application Architecture
- Complete Guide to LLM Cost Optimization
- Complete Guide to LLM Evaluation
- Stream LLM Output with Server-Sent Events (SSE)
- API Rate Limiting — Design Fair and Useful Throttling
Lecturas Recomendadas
- Referencia de la API de OpenAI — endpoints, parámetros y formas de respuesta
- Precios de OpenAI — tarifas por modelo y por nivel para presupuestar
- Guía de rate limits — niveles de uso y cabeceras de límite
- Guía de la Batch API — 50 % de descuento en cargas asíncronas
- Buenas prácticas de producción — seguridad de claves, límites de gasto y escalado
- OpenAI Cookbook — ejemplos ejecutables mantenidos por OpenAI
Preguntas frecuentes
¿Qué modelo debería usar?
Empieza con gpt-4o-mini para tareas simples (clasificación, resúmenes, extracción) y gpt-4o donde importe la calidad o la multimodalidad. Usa o1/o1-mini solo para razonamiento genuinamente multi-paso — son más lentos y mucho más caros. En caso de duda, mide con tus propias evaluaciones en lugar de fiarte de benchmarks.
¿Debería usar la Responses API o chat completions?
Para código nuevo, la Responses API — es la superficie recomendada actual y maneja estado, tools y salida estructurada en un solo lugar. Quédate con chat completions si mantienes una integración existente o apuntas a proveedores compatibles con OpenAI que aún no han adoptado Responses.
¿Cómo reduzco los costes?
Enruta las consultas simples a gpt-4o-mini. Cachea las consultas repetidas. Usa la Batch API para trabajo no urgente (~50 % más barato). Acota max_tokens para frenar la sobre-generación. Mantén los system prompts cortos — cuestan tokens en cada llamada. Y registra el uso por feature para saber de dónde viene la factura.
¿Cuál es la diferencia entre JSON mode y salidas estructuradas?
El JSON mode garantiza JSON sintácticamente válido, nada más. Las salidas estructuradas (json_schema con strict: true) garantizan que la respuesta cumple tu esquema — campos correctos, tipos correctos, nada extra. Si el código posterior parsea el resultado, usa salidas estructuradas.
¿Cómo manejo contextos largos?
gpt-4o soporta una ventana de 128K tokens, pero llenarla sale caro y degrada la calidad del retrieval. Prefiere RAG antes que empaquetar documentos enteros, resume los turnos antiguos de la conversación, cuenta tokens con tiktoken antes de enviar, y acota siempre max_tokens en la salida.
¿Puedo usar la API de OpenAI en Europa bajo el RGPD?
Sí, con matices: firma el Data Processing Agreement de OpenAI, consigue base legal antes de enviar datos personales y aplica minimización de datos. Para cargas sensibles, comprueba si una opción de retención cero o residencia regional encaja antes de enviar datos de usuarios.
Recursos Relacionados
Arquitectura de Aplicaciones LLM
Construir aplicaciones LLM en produccion end-to-end. Cubre capas de API, gestion de prompts, streaming, caching, guardrails, observabilidad, evaluacion y patrones de deployment para sistemas LLM confiables.
GuideOptimización de Costos LLM
Optimizar costos LLM en produccion. Cubre model routing, prompt compression, caching, batch API, token management, semantic caching, prompt engineering para costos, monitoring y budget control para aplicaciones LLM.
GuideEvaluación de LLM
Evaluar aplicaciones LLM en produccion. Cubre RAGAS, LLM-as-judge, evaluacion humana, A/B testing, deteccion de hallucinations, toxicity scoring, regression testing y pipelines de evaluacion automatizados.
RecipeSalida JSON estructurada con OpenAI Function Calling
Usa function calling y structured outputs de OpenAI para obtener JSON confiable de LLMs con validacion Pydantic y manejo de errores
RecipeCompara similitud semantica de texto con embeddings de
Genera embeddings de texto con OpenAI y calcula similitud coseno para medir similitud semantica entre textos para busqueda, deduplicacion y clustering
GuideRate Limiting de APIs — Diseña Throttling Justo y Útil
Guía práctica para rate limiting de APIs: algoritmos de token bucket, leaky bucket, ventana deslizante, elección de límites e implementación de throttling resiliente para APIs.