Volver al blog

API y SDKGuía

Timeout y reintentos del SDK de OpenAI para Python: configúralos para CheapTokens

Configura timeout, max_retries y max_tokens en el SDK de OpenAI para Python, captura los errores correctos y evita que una petición atascada se ejecute durante 10 minutos.

Por CheapTokens TeamPublicado el 5 min de lectura

Código Python con timeout=30.0 y max_retries=2 sobre un fondo oscuro
En esta página
  1. Qué hace el SDK cuando no configuras nada
  2. Configura timeout y max_retries en el cliente
  3. Sobrescríbelo para una petición
  4. Captura los errores que importan
  5. Limita max_tokens para que una respuesta larga no se descontrole
  6. Qué le hacen los reintentos a la factura
  7. Antes de publicar

Tu script llamó al modelo, no volvió nada y diez minutos después sigue esperando. Ese es el comportamiento predeterminado del SDK de OpenAI para Python: un timeout de 10 minutos y 2 reintentos. Para cambiarlo, pasa timeout=30.0 y max_retries=2 a OpenAI(...), sobrescríbelos por llamada con client.with_options(...) y limita la respuesta con max_tokens.

El README del SDK (consultado el 27 de octubre de 2026) documenta todo esto. A continuación se aplica al endpoint de CheapTokens, https://api.cheaptokens.dev/v1, que habla el formato OpenAI Chat Completions. Los detalles de esa configuración están en Tu primera petición a la API con CheapTokens y en la documentación.

Qué hace el SDK cuando no configuras nada#

Dos valores predeterminados importan. El timeout es de 10 minutos. Los reintentos son 2, con un breve backoff exponencial, para errores de conexión y para respuestas 408, 409, 429 y 5xx, y solo cuando el cuerpo de la petición se puede reenviar de forma segura. Una petición que agota el tiempo también se reintenta.

Así que cada intento tiene su propio timeout, y el peor caso es aproximadamente el timeout multiplicado por el número de intentos, más las pausas entre ellos:

  • predeterminado: 600 × 3 = 1800 segundos, media hora
  • timeout=30.0, max_retries=2: 30 × 3 = 90 segundos

Supón que un endpoint web tiene que responder en 30 segundos. Con los valores predeterminados puede quedarse colgado media hora antes de que tu código vea un error. Pruébalo con otra configuración antes de seguir leyendo.

Adivina el número

Un cliente tiene timeout=45.0 y max_retries=3. ¿Cuál es la espera en el peor caso en segundos antes de la excepción, sin contar las pausas entre intentos?

Mostrar respuesta

Respuesta: 180

45 × (3 reintentos + 1 primer intento) = 180 segundos; las pausas entre intentos añaden un poco más.

Configura timeout y max_retries en el cliente#

La solución más simple es configurar ambos una vez, al crear el cliente:

python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.cheaptokens.dev/v1",
    api_key=os.environ["CHEAPTOKENS_API_KEY"],
    timeout=30.0,      # seconds per attempt
    max_retries=2,     # 0 turns retries off
)

resp = client.chat.completions.create(
    model="deepseek-ai/DeepSeek-V4-Flash-0731",
    messages=[{"role": "user", "content": "Summarize this ticket in two sentences: ..."}],
    max_tokens=800,
)
print(resp.choices[0].message.content)
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)

max_retries debe ser un entero no negativo. timeout acepta un float, o un objeto httpx Timeout si quieres límites separados para conectar y leer. El README de la rama main actual importa httpx2 para eso, las versiones anteriores usaban httpx, así que ejecuta pip show openai y comprueba cuál usa tu instalación. Un float simple no necesita import y funciona en ambos casos.

La ruta de la petición con timeout=30 y dos reintentos
Una petición: un intento, un timeout, hasta dos reintentos y luego una excepción

Usa el id completo del modelo de la documentación de CheapTokens, deepseek-ai/DeepSeek-V4-Flash-0731 aquí: los ids difieren entre proveedores. Este modelo admite hasta 32.768 tokens de salida por respuesta y tiene un contexto de 380K, por eso la línea max_tokens de arriba está ahí a propósito. Más sobre ello abajo, y sobre el modelo en DeepSeek barato.

Sobrescríbelo para una petición#

Un cliente, distintos trabajos. Una respuesta de chat debería rendirse rápido, un resumen nocturno puede esperar. En lugar de crear un segundo cliente, deriva uno para la llamada:

python
quick = client.with_options(timeout=5.0, max_retries=0)
resp = quick.chat.completions.create(
    model="deepseek-ai/DeepSeek-V4-Flash-0731",
    messages=[{"role": "user", "content": "Reply with one word: ok"}],
    max_tokens=5,
)

with_options devuelve una copia con los nuevos valores y deja client intacto. Configurar max_retries=0 es lo correcto cuando tu propio código ya reintenta, por ejemplo una cola de trabajos que reencola la tarea. De lo contrario, las dos capas se multiplican: 3 intentos del SDK dentro de 3 intentos de la cola son 9 peticiones para una tarea.

Captura los errores que importan#

Cuando se agotan los reintentos, el SDK lanza una excepción. Todas las clases de error heredan de openai.APIError, y las peticiones fallidas llevan request_id, que es lo que registras:

python
import openai

def ask(prompt: str) -> str | None:
    try:
        r = client.chat.completions.create(
            model="deepseek-ai/DeepSeek-V4-Flash-0731",
            messages=[{"role": "user", "content": prompt}],
            max_tokens=800,
        )
        return r.choices[0].message.content
    except openai.APITimeoutError:
        print("timed out after retries")
    except openai.RateLimitError as e:
        print("429, slow down:", e.request_id)
    except openai.APIConnectionError:
        print("cannot reach the API")
    except openai.APIStatusError as e:
        print("HTTP", e.status_code, e.request_id)
    return None

El orden va de lo específico a lo general. APIStatusError cubre cualquier respuesta 4xx o 5xx, así que si fuera primero se tragaría RateLimitError. Lo mismo para el timeout: está por encima del error de conexión general. En un script por lotes, return None es lo esencial: registra el prompt, pasa al siguiente y vuelve a ejecutar los fallos al final.

Cuando se dispara el timeout y se agotan los reintentos, así es como se ve la excepción:

El mensaje de APITimeoutError después de agotar los reintentos
Cómo se ve un timeout capturado en el log

Para el caso 429 en detalle, incluida la espera entre intentos, consulta 429 Too Many Requests de una API de IA.

Limita max_tokens para que una respuesta larga no se descontrole#

Un timeout deja de esperar. No hace que el modelo escriba menos. Sin max_tokens, un modelo con un límite de 32.768 tokens de respuesta es libre de escribir una respuesta muy larga, y una respuesta larga es lenta. Configura un límite que encaje con el trabajo: 800 para un resumen de ticket, 5 para un sí/no.

El coste es que una respuesta puede quedar cortada. Comprueba resp.choices[0].finish_reason: length significa que se alcanzó el límite antes de que el modelo terminara. ¿Por qué una respuesta de IA se corta a mitad de frase? lo cubre. Si en su lugar transmites la respuesta en streaming, las primeras palabras llegan antes y la espera se siente más corta; consulta Respuestas en streaming en Python.

Qué le hacen los reintentos a la factura#

Un reintento es una nueva petición. Si un intento se procesó, es una petición separada con sus propios tokens. Supón que un resumen de ticket son 1.500 tokens de entrada y 400 de salida: una petición cuesta $0.000063 a los precios actuales, y la tabla de precios completa tiene los demás modelos. Los precios son fijos por modelo, sin horas punta, así que un reintento a las 3 de la madrugada cuesta lo mismo que uno al mediodía.

Mueve los controles a tu propio lote:

Calculadora de costos

Un lote de 1.000 resúmenes de tickets

Tokens de entrada por solicitud: 1500 · Tokens de salida por solicitud: 400 · Solicitudes por día: 1000

ModeloPor díaPor mes
GLM-5.3-Flash$0.046$1.38
DeepSeek-V4-Flash$0.0625$1.88
MiniMax-M2.7$0.135$4.05

La tabla muestra una pasada. Si una décima parte del lote necesita un segundo intento, añade una décima parte. Los reintentos son un seguro barato cuando los fallos son raros, y un hábito caro cuando configuras max_retries=10 contra un endpoint que falla continuamente.

Antes de publicar#

Lista de verificación

Timeouts y reintentos, listos para producción