Una migración de cliente de OpenAI puede parecer completa después de dos cambios de configuración: reemplazar la clave de API y apuntar el SDK a una nueva URL base. La primera solicitud se completa correctamente, la forma de la respuesta resulta familiar y el pull request parece listo para fusionarse.
Eso demuestra compatibilidad de interfaz. No demuestra comportamiento en producción.
La parte más difícil de una migración de cliente de OpenAI es preservar lo que ocurre cuando el tráfico se vuelve irregular: las solicitudes llegan en ráfagas, los prompts crecen, los streams duran más de lo esperado, un proveedor devuelve 429 o una respuesta agota el tiempo después de que el trabajo quizá ya haya comenzado. Si el SDK, tu aplicación y una cola de trabajos reintentan de forma independiente, una llamada fallida puede convertirse en varios intentos casi simultáneos.
Este tutorial muestra cómo migrar una integración existente de Python o TypeScript con estilo OpenAI a una pasarela unificada, dejando explícitos el comportamiento frente a límites de tasa y los reintentos. Los ejemplos usan la URL base compatible con OpenAI de Flatkey, pero el método de revisión se aplica a cualquier migración de pasarela.
Respuesta rápida: ¿qué debería cambiar?
Para una migración de cliente de OpenAI segura, revisa estos ajustes juntos en lugar de tratar la URL base como si fuera todo el cambio.
| Superficie de migración | Qué revisar | Decisión inicial segura |
|---|---|---|
| Endpoint de API | URL base y autenticación | Cambiar mediante variables de entorno, no con literales dispersos |
| Selección de modelo | Identificadores exactos de modelo y parámetros compatibles | Fijar un modelo conocido para el canary |
| Reintentos del SDK | Número de reintentos automáticos y códigos de estado reintentables | Elegir si el SDK o tu aplicación se encarga de los reintentos |
| Reintentos de la aplicación | Backoff, jitter, límite de intentos y presupuesto de reintentos | Mantener un único responsable de reintentos y registrar cada intento |
| Control de RPM | Tasa de llegada de solicitudes y tamaño de ráfaga | Añadir un límite de concurrencia o de cola antes del corte |
| Control de TPM | Prompt más tokens de salida esperados | Probar prompts grandes realistas, no solo una prueba rápida de una línea |
| Timeouts | Duración de conexión, lectura y total de la solicitud | Establecer valores explícitos para llamadas síncronas y en streaming |
| Observabilidad | IDs de solicitud, intentos, tokens, latencia y resultado final | Comparar los logs del cliente con los logs de uso de la pasarela |
Si primero necesitas la explicación del acrónimo, lee Límites de tasa de LLM explicados: RPM, TPM y reintentos. Esta guía empieza donde termina esa explicación: en el diff de migración y el plan de pruebas de producción.
Por qué cambiar solo la URL base es necesario pero insuficiente
La guía de inicio rápido de Flatkey documenta el cambio mínimo del cliente: mantener el patrón de solicitud del SDK de OpenAI y establecer la URL base en https://router.flatkey.ai/v1. También recomienda revisar los Usage Logs después de la solicitud para poder verificar el modelo, los recuentos de tokens, la latencia y el coste.
Esa es la prueba rápida correcta. Una migración de cliente de OpenAI en producción necesita cuatro preguntas adicionales:
- ¿El SDK reintenta automáticamente
429, errores de tiempo de espera o errores del servidor? - ¿Otra capa también reintenta la misma operación fallida?
- ¿La concurrencia está limitada por la tasa de solicitudes, la tasa de tokens o ambas?
- ¿Puedes distinguir una operación lógica de sus intentos individuales?
La documentación oficial de los SDK de Python y Node de OpenAI actualmente indica que ciertos fallos se reintentan dos veces de forma predeterminada, incluidas las respuestas 429, los errores de conexión, los tiempos de espera y algunos errores del servidor. Ambos SDK exponen configuración de reintentos y tiempo de espera. Ese valor predeterminado es conveniente para una integración directa, pero puede convertirse en una amplificación invisible cuando tu propio código ya implementa retroceso.
El objetivo de la migración no es “desactivar todos los reintentos”. El objetivo es “saber qué capa es la dueña del reintento”.
Paso 1: inventaria cada capa de reintento antes de cambiar el código
Empieza dibujando la ruta real de la llamada.
acción del usuario o trabajo
-> envoltorio de reintento de la aplicación
-> reintento de entrega de la cola
-> reintento del SDK de OpenAI
-> gateway
-> proveedor
Para cada capa, registra:
- Qué errores desencadenan otro intento.
- El número máximo de intentos.
- Si el retraso usa espera fija, retroceso exponencial o jitter.
- Si se respeta un valor
Retry-Afterproporcionado por el servidor. - Si se conserva el mismo identificador de operación entre intentos.
- Si se asume que una solicitud con tiempo de espera agotado falló antes de que ocurriera cualquier trabajo.
La última suposición es arriesgada. Un tiempo de espera del cliente solo te dice que el cliente dejó de esperar. El sistema aguas arriba puede haber aceptado o completado la solicitud. Para contenido generado, un reintento puede crear por tanto otro resultado y otra solicitud facturable incluso cuando tu aplicación solo observó una tarea lógica.
Estima la amplificación en el peor caso
Supón que una cola puede entregar un trabajo tres veces, el envoltorio de la aplicación permite tres intentos y el SDK realiza la llamada inicial más dos reintentos. En el peor caso, un trabajo lógico puede desencadenar:
3 entregas de la cola × 3 intentos de la aplicación × 3 intentos del SDK = 27 intentos HTTP
Puede que nunca alcances el número completo, pero la multiplicación explica por qué un breve 429 puede convertirse en una tormenta de reintentos. Escribe el número en la revisión de migración. Hace visibles los valores predeterminados ocultos.
Paso 2: mueve la configuración del endpoint a configuración
Mantén reversible el diff de migración. No reemplaces las cadenas de endpoint por todo el código base.
Python antes y después
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["LLM_API_KEY"],
base_url=os.environ.get("LLM_BASE_URL", "https://api.openai.com/v1"),
max_retries=0,
timeout=45.0,
)
Para el canario de Flatkey, configura:
export LLM_API_KEY="$FLATKEY_API_KEY"
export LLM_BASE_URL="https://router.flatkey.ai/v1"
TypeScript antes y después
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.LLM_API_KEY,
baseURL: process.env.LLM_BASE_URL ?? "https://api.openai.com/v1",
maxRetries: 0,
timeout: 45_000,
});
Estos ejemplos establecen los reintentos del SDK en cero porque la siguiente sección asigna explícitamente a la aplicación la responsabilidad de reintentar. Si tu aplicación no tiene una capa de reintentos, puedes mantener en su lugar reintentos limitados del SDK. No mantengas ambas por accidente.
Para la lista de compatibilidad más amplia, consulta Gateway de API compatible con OpenAI: lista de verificación de migración para cambios mínimos de código.
Step 3: give one layer explicit retry ownership
Una política de reintentos útil tiene cinco partes:
- Una lista breve de fallos que se pueden reintentar.
- Un límite estricto de intentos.
- Un tiempo total máximo de reintentos.
- Backoff exponencial con jitter.
- Registros estructurados para cada intento.
Aquí tienes un pequeño wrapper de Python para una llamada síncrona de chat:
import random
import time
from openai import APITimeoutError, APIConnectionError, APIStatusError, RateLimitError
RETRYABLE_STATUS_CODES = {408, 409, 429, 500, 502, 503, 504}
def create_chat_with_retry(client, *, model, messages, max_attempts=4):
started_at = time.monotonic()
for attempt in range(1, max_attempts + 1):
try:
return client.chat.completions.create(
model=model,
messages=messages,
)
except (RateLimitError, APITimeoutError, APIConnectionError) as error:
retryable = True
caught_error = error
except APIStatusError as error:
retryable = error.status_code in RETRYABLE_STATUS_CODES
caught_error = error
if not retryable or attempt == max_attempts:
raise caught_error
exponential_delay = min(2 ** (attempt - 1), 16)
jitter = random.uniform(0, 0.5 * exponential_delay)
sleep_seconds = exponential_delay + jitter
print({
"event": "llm_retry",
"attempt": attempt,
"next_delay_seconds": round(sleep_seconds, 2),
"elapsed_seconds": round(time.monotonic() - started_at, 2),
"error_type": type(caught_error).__name__,
})
time.sleep(sleep_seconds)
raise RuntimeError("unreachable")
Trátalo como un punto de partida revisable, no como una política universal. En producción, analiza y respeta un encabezado de respuesta Retry-After válido antes de recurrir al retardo calculado localmente. Añade un presupuesto de tiempo total transcurrido para que los reintentos no superen la latencia que tu producto puede tolerar.
Do not retry every error
| Failure | Default action | Why |
|---|---|---|
400 invalid request |
No reintentar sin cambios | El payload debe cambiar |
401 authentication |
No reintentar sin cambios | La clave o el encabezado deben cambiar |
404 model not found |
No reintentar sin cambios | El identificador del modelo o el acceso deben cambiar |
429 rate limit |
Reintentar con retardo y jitter | La capacidad puede quedar disponible |
500 or 503 |
Reintentar dentro de un pequeño presupuesto | El fallo puede ser temporal |
| Client timeout | Reintentar con cautela | La solicitud ascendente puede haberse ejecutado ya |
La guía de inicio rápido de Flatkey ofrece la misma orientación general para 429: reintentar con backoff exponencial y jitter. La adición específica de la migración es asegurarse de que solo una capa aplique esa política.
Step 4: dimensiona la concurrencia para RPM y TPM
Una migración de cliente de OpenAI puede conservar la sintaxis de las solicitudes mientras cambia el límite de capacidad. RPM y TPM restringen cargas de trabajo distintas:
- RPM se convierte en el cuello de botella cuando envías muchas solicitudes pequeñas.
- TPM se convierte en el cuello de botella cuando los prompts, las salidas o las evaluaciones en paralelo son grandes.
Usa el tráfico observado en lugar de un único promedio. Recopila al menos:
- Solicitudes por minuto en mediana y pico.
- Tokens de entrada en p50, p95 y máximo.
- Tokens de salida en p50 y p95.
- Duración de la solicitud en mediana y p95.
- Número de flujos concurrentes.
Se puede estimar un límite superior aproximado de concurrencia a partir de cada restricción:
Concurrencia basada en RPM ≈ (RPM / 60) × segundos promedio por solicitud
Concurrencia basada en TPM ≈ (TPM / tokens promedio por solicitud / 60)
× segundos promedio por solicitud
Usa el resultado más bajo como límite inicial y luego deja margen para ráfagas y reintentos.
Ejemplo: supón que una ruta permite 600 RPM y 300,000 TPM, que la solicitud promedio usa 1,500 tokens totales y que la duración promedio es de 3 segundos.
Límite por RPM: (600 / 60) × 3 = 30 solicitudes concurrentes
Límite por TPM: (300,000 / 1,500 / 60) × 3 = 10 solicitudes concurrentes
TPM es la restricción más estricta en este ejemplo. Empezar con 30 solicitudes concurrentes porque RPM parecía generoso crearía respuestas 429 evitables.
Este cálculo es orientativo, no una garantía del proveedor. Los proveedores pueden usar ventanas deslizantes, buckets de tokens, límites separados de tokens de entrada y de salida, grupos específicos por modelo o controles de aceleración. El plan de pruebas debe verificar el comportamiento real del modelo y la cuenta seleccionados.
Step 5: prueba el comportamiento de streaming y de tiempo de espera por separado
No consideres que una llamada sin streaming exitosa prueba que el streaming sea seguro.
Para solicitudes de streaming, prueba:
- Tiempo hasta el primer token.
- Intervalo máximo de silencio entre fragmentos.
- Tiempo de espera de lectura del cliente.
- Comportamiento cuando el consumidor se desconecta.
- Si tu wrapper de reintentos puede iniciar accidentalmente un segundo stream.
- Si la salida parcial se conserva, se descarta o se muestra al usuario.
Un stream que falla después de una salida parcial no es equivalente a una solicitud que falló antes de generar cualquier salida. Reintentarla automáticamente puede mostrar texto duplicado o producir una continuación diferente. Decide si el producto debe reintentar, preguntar al usuario o mostrar el resultado parcial.
Recuerda también que los tiempos de espera del SDK y los de la infraestructura pueden diferir. Un proxy inverso, una plataforma serverless, un worker de tareas o una conexión del navegador pueden finalizar antes de que la biblioteca del cliente alcance su propio tiempo de espera. Durante la migración de cliente de OpenAI, registra el tiempo de espera más pequeño en la ruta completa de la solicitud.
Step 6: ejecuta una matriz canary antes de ampliar el tráfico
Usa un modelo fijado y un pequeño porcentaje del tráfico. El primer canary debe responder si la nueva ruta conserva el comportamiento, no si todos los modelos funcionan.
| Caso de prueba | Entrada | Evidencia esperada |
|---|---|---|
| Autenticación | Claves válidas e inválidas | Éxito más un 401 sin reintento |
| Validación del modelo | IDs de modelo válidos y mal escritos | Éxito más un error de modelo sin reintento |
| Ráfaga pequeña de solicitudes | Muchos prompts cortos | Cola controlada sin un pico de reintentos |
| Ráfaga grande de prompts | Menos prompts de alto token | La presión de TPM es visible y está acotada |
429 forzado |
Supera temporalmente el límite de canary | Un único propietario del reintento, retrasos con jitter, intentos limitados |
| Timeout forzado | Establece un timeout del cliente intencionalmente corto | Timeout registrado sin repetición ilimitada |
| Interrupción de streaming | Desconecta durante un stream | Comportamiento explícito de salida parcial |
| Error del servidor | Inyecta o simula 503 |
Reintentos acotados e informe final de error |
| Rollback | Restaura la URL base anterior | El rollback solo de configuración tiene éxito |
Para cada operación lógica, registra:
operation_id
attempt_number
base_url_name
model_requested
http_status
input_tokens
output_tokens
latency_ms
retry_delay_ms
final_outcome
Luego compara los registros de la aplicación con los Flatkey Usage Logs. Los conteos deberían tener sentido juntos. Si una operación de la aplicación se asigna a varias solicitudes de gateway, tu instrumentación de reintentos debería explicar por qué.
Step 7: define los umbrales de despliegue y rollback
Una migración de cliente de OpenAI debería tener condiciones numéricas de parada antes de que comience el primer canary.
Umbrales de ejemplo:
- Haz rollback si la tasa final de errores aumenta más de un porcentaje acordado.
- Pausa si los intentos por operación superan el presupuesto de reintentos esperado.
- Pausa si la latencia p95 supera el presupuesto de timeout del producto.
- Pausa si el uso de tokens por operación exitosa cambia inesperadamente.
- Amplía el tráfico solo después de que tanto las rutas de streaming como las no streaming pasen.
Evita comparar solo los conteos brutos de 429. Una buena cola puede reducir los errores finales mientras aumenta temporalmente las solicitudes retrasadas. Rastrea tanto los resultados a nivel de intento como a nivel de operación.
Checklist de la pull request de migración
Copia esta checklist en la PR de implementación.
- La base URL y la clave provienen de variables de entorno.
- El canary usa un identificador de modelo exacto y verificado.
- Una sola capa controla los reintentos.
- Los valores predeterminados de reintento del SDK están documentados en la PR.
- El comportamiento de
429, timeout y5xxtiene intentos acotados. - El backoff incluye jitter y respeta
Retry-Aftercuando está presente. - Los límites de RPM y TPM se estiman a partir del tráfico observado.
- El streaming tiene una prueba de fallo separada.
- Cada intento comparte un único
operation_idlógico. - Los registros de uso y los registros de la aplicación se comparan.
- Los umbrales de despliegue y rollback se escriben antes del lanzamiento.
- El endpoint anterior puede restaurarse sin otro cambio de código.
Errores comunes de migración
Conservar los reintentos del SDK y los reintentos de la aplicación sin calcular el total
Este es el hallazgo de revisión más importante. Los valores predeterminados siguen siendo comportamiento, incluso cuando no son visibles en la función local.
Probar solo un prompt diminuto
Una solicitud de una línea demuestra las credenciales y la compatibilidad de la respuesta. No dice casi nada sobre la presión de TPM, los límites de salida, las transmisiones largas o la latencia p95.
Reintentar errores de autenticación y validación
El backoff no puede reparar una clave no válida, un parámetro no compatible o un modelo mal escrito. Reintentar cargas útiles sin cambios desperdicia capacidad y oculta el defecto real.
Tomar un timeout como prueba de que no se ejecutó ninguna solicitud
El cliente puede dejar de esperar después de que el upstream aceptó la llamada. Diseña los reintentos y la contabilidad teniendo en cuenta esa ambigüedad.
Cambiar endpoint, modelos, prompts y política de reintentos en una sola versión
Eso hace que los fallos sean difíciles de atribuir. Migra primero una forma de solicitud conocida, luego amplía la elección de modelo después de que la ruta sea observable.
Una definición más segura de “compatible con OpenAI”
Para la planificación de la migración, “compatible con OpenAI” debería significar que el patrón de interacción es lo suficientemente familiar como para reducir los cambios de código. No debería interpretarse como una promesa de que cada proveedor comparte cuotas idénticas, conteo de tokens, semántica de errores, latencia, comportamiento de streaming o compatibilidad de parámetros.
Esa distinción hace que una migración de cliente de OpenAI sea más fácil de revisar. Mantén la interfaz estable donde ayude, pero prueba el contrato operativo donde los proveedores y las rutas puedan diferir.
Flatkey centraliza el acceso y la facturación detrás de una única URL base compatible con OpenAI, lo que puede simplificar el diff del cliente y la expansión posterior de modelos. El trabajo de ingeniería sigue siendo hacer explícitos los reintentos, el rendimiento y la observabilidad antes de mover el tráfico de producción.
Esa es la norma que debe cumplir una migración de cliente de OpenAI en producción: un pequeño cambio de interfaz respaldado por evidencia operativa explícita.
Revisa la página de precios de Flatkey al seleccionar los modelos para tu canary, y luego aprueba la migración solo después de que la lista de verificación pase la revisión de código y el comportamiento de la ruta sea visible en los registros.
Preguntas frecuentes
¿Debería desactivar los reintentos del SDK de OpenAI durante la migración?
Desactívalos si tu aplicación o cola ya gestiona los reintentos. Si ninguna otra capa reintenta, unos reintentos limitados del SDK pueden ser razonables. La regla importante es evitar múltiples responsables de reintentos independientes.
¿Cuál es la diferencia entre RPM y TPM durante una migración?
RPM limita la frecuencia de solicitudes, mientras que TPM limita el rendimiento de tokens. Las llamadas pequeñas y frecuentes pueden alcanzar primero RPM; menos prompts o salidas grandes pueden alcanzar primero TPM. Prueba ambos patrones de carga.
¿Siempre se debe reintentar un 429?
Solo dentro de un presupuesto limitado de reintentos y latencia. Respeta Retry-After cuando esté disponible; de lo contrario, usa backoff exponencial con jitter. Detente si la operación ya no puede cumplir el objetivo de latencia del producto.
¿Puedo reintentar de forma segura una generación con timeout?
No con certeza. Es posible que la solicitud upstream se haya ejecutado aunque el cliente haya agotado el tiempo de espera. Trata el reintento como una posible solicitud duplicada y registra la relación entre intentos.
¿Cuál es el canary mínimo seguro?
Usa un único modelo fijado, una única forma de solicitud, propiedad explícita de los reintentos, un límite de concurrencia y pruebas para 429, timeout, interrupción de streaming y rollback. Compara los intentos del lado del cliente con los registros de uso del gateway antes de ampliar el tráfico.



