Límites de tasa y reintentos: construir llamadas a LLM resilientes
Los LLM alojados fallan de formas corrientes: límites, tiempos de espera, errores transitorios. Un poco de disciplina de reintentos vuelve fiable una integración frágil.
La primera versión de casi toda integración con un LLM funciona en el portátil del desarrollador y se cae la primera tarde ajetreada en producción. La razón rara vez es el modelo. Es que una API alojada es un servicio de red compartido, y los servicios de red compartidos fallan de formas corrientes y predecibles: imponen límites, agotan tiempos de espera y de vez en cuando devuelven un error que no significa más que "vuelve a intentarlo en un momento". La diferencia entre una integración frágil y una fiable no es la astucia. Es una pequeña cantidad de disciplina de reintentos aplicada de forma coherente. Esta guía cubre qué sale mal y cómo manejar cada caso con calma.
Por qué fallan tus llamadas (y qué fallos son normales)
Empieza por clasificar los fallos en dos montones, porque exigen respuestas opuestas.
Los fallos transitorios son temporales y no son tu culpa en ningún sentido duradero. El servicio estuvo brevemente sobrecargado, alcanzaste un límite de tasa, una petición agotó su tiempo de espera, una conexión se cortó. El rasgo definitorio es que la misma petición podría tener éxito si simplemente la envías de nuevo. Estos son los fallos para los que existen los reintentos.
Los fallos permanentes no mejorarán con la repetición. Una petición mal formada, una clave inválida, un prompt que viola la política, una entrada demasiado grande para el modelo: enviarla de nuevo solo desperdicia tiempo y cuota y, peor aún, puede hundirte más en un límite de tasa. El rasgo definitorio es que la petición está mal, no que tuvo mala suerte.
El hábito más importante en código de LLM resiliente es distinguir estos dos y responder de forma distinta. Reintentar un fallo permanente es un error. Fallar en seco ante uno transitorio también es un error. La mayoría de las integraciones frágiles cometen uno de estos dos errores en todas partes.
Entender los límites de tasa
Los límites de tasa son el fallo transitorio más común, y no son un castigo. Son la forma en que un servicio compartido se protege a sí mismo y a sus otros usuarios de que un solo cliente lo desborde. Los proveedores normalmente limitan el uso por un par de ejes a la vez: cuántas peticiones haces en una ventana, y cuánto trabajo total (a menudo medido en tokens) empujas a través de esa ventana. Puedes quedarte por debajo de un límite y aun así alcanzar el otro.
La consecuencia práctica es que no puedes razonar sobre tu rendimiento contando solo peticiones. Un puñado de peticiones muy grandes puede agotar un presupuesto de tokens mientras estás muy lejos del límite de peticiones. Cuando se dispara un límite de tasa, el servicio te lo dice explícitamente, a menudo con una pista sobre cuánto esperar. La respuesta correcta no es golpear más fuerte. Es reducir la marcha y volver.
Dos hábitos previenen la mayor parte del dolor de los límites de tasa antes de que empiece. Primero, lee las cabeceras de respuesta y los cuerpos de error: los proveedores exponen ahí tu uso y límites actuales, y esa información es la entrada para retroceder de forma inteligente. Segundo, suaviza tu propio tráfico: si tienes una ráfaga de trabajo, distribúyela en lugar de dispararla toda de golpe, para que te acerques al límite de forma gradual en vez de estrellarte contra él.
Reintentar de la forma correcta
Cuando reintentas, cómo reintentas importa enormemente. El enfoque ingenuo —reintentar de inmediato, una y otra vez— es activamente dañino. Si el servicio está sobrecargado, una avalancha de reintentos instantáneos lo empeora, y te conviertes en parte del problema que intentas sobrevivir. El enfoque disciplinado tiene tres ingredientes.
Retroceso exponencial. Espera un poco antes del primer reintento, luego aproximadamente duplica la espera antes de cada siguiente. El primer tropiezo recibe una rápida segunda oportunidad; un problema persistente recibe progresivamente más margen para respirar. Este único patrón resuelve la gran mayoría de los fallos transitorios.
Fluctuación (jitter). Añade una pequeña cantidad aleatoria a cada espera. Sin ella, muchos clientes que fallaron en el mismo instante reintentarán todos en el mismo instante, produciendo una estampida sincronizada que vuelve a sobrecargar el servicio. La fluctuación dispersa los reintentos. Es un cambio diminuto con un efecto desmesurado a escala, y omitirlo es un error clásico.
Un techo de reintentos. Limita el número de intentos y el tiempo total que vas a gastar. Reintentar para siempre convierte una interrupción breve en una petición colgada que inmoviliza recursos y frustra a quien esté esperando. Después del techo, ríndete limpiamente y expón un fallo real.
En conjunto: ante un error transitorio, espera con retroceso exponencial más fluctuación, reintenta hasta un límite fijo, y si se proporcionó una pista sobre cuánto esperar, hónrala por encima de tu propio retraso calculado.
Tiempos de espera y los límites de reintentar
Cada llamada necesita un tiempo de espera, y elegirlo es una contrapartida genuina. Demasiado corto y abandonas peticiones que habrían tenido éxito, convirtiendo respuestas lentas-pero-correctas en fallos. Demasiado largo y una petición atascada cuelga tu sistema e inmoviliza a un usuario. Elige un tiempo de espera basado en la longitud de respuesta que realmente esperas, y recuerda que las generaciones largas legítimamente tardan más: un tiempo de espera afinado para una respuesta de una línea matará por error una petición de una respuesta larga.
Los tiempos de espera interactúan con los reintentos de un modo que muerde a la gente. Una petición puede agotar su tiempo de espera de tu lado mientras el servidor sigue trabajando en ella. Reintenta a ciegas y puede que ejecutes el mismo trabajo costoso dos veces. Para generación de solo lectura eso es meramente un desperdicio. Para cualquier llamada que cause un efecto —enviar un mensaje, escribir un registro, disparar una herramienta— la ejecución duplicada es un error real. La defensa es la idempotencia: diseña las operaciones con efectos de modo que hacerlas dos veces sea seguro, a menudo adjuntando una clave única que el servidor pueda usar para reconocer y deduplicar una repetición.
Fallar con gracia cuando se acaban los reintentos
La resiliencia no trata solo de recuperarse. Trata también de fallar bien cuando la recuperación es imposible, porque a veces el servicio realmente está caído y ninguna cantidad de retroceso ayuda. Un fallo con gracia tiene unas pocas propiedades.
- Está acotado. El usuario o el sistema que llama recibe una respuesta clara en un tiempo razonable, no un cuelgue indefinido.
- Se degrada en lugar de colapsar. Donde el producto lo permita, recurre a algo útil —un resultado en caché, un camino más simple sin modelo, un honesto "esto no está disponible temporalmente"— en vez de un error en blanco.
- Es visible. Registra el fallo con suficiente contexto para entenderlo más tarde, y expón una señal que puedas monitorizar, para que una tasa de fallos en aumento te llegue antes de que tus usuarios la escalen.
La marca de una integración madura no es que nunca falle. Es que cuando falla, nada aguas abajo se sorprende.
Verlo antes de que duela
No puedes afinar lo que no puedes ver. Rastrea, como mínimo, tu tasa de fallos por tipo, con qué frecuencia se disparan los reintentos y con qué frecuencia finalmente tienen éxito, tu latencia incluido el tiempo gastado en retroceso, y cuán cerca estás corriendo de tus límites de tasa. Ese último es el sistema de alerta temprana: el uso acercándose al techo es tu señal para distribuir el tráfico, optimizar prompts o solicitar límites más altos antes del muro, no después. La mayoría de los incidentes de límites de tasa son visibles como una tendencia con horas de antelación, para quien esté mirando.
En resumen
Las llamadas a LLM resilientes se reducen a una disciplina corta y aburrida. Separa los fallos transitorios de los permanentes y responde a cada uno correctamente. Reintenta los fallos transitorios con retroceso exponencial, fluctuación y un techo firme; nunca en un bucle inmediato y apretado. Respeta los límites de tasa suavizando tu tráfico y leyendo lo que te dice el servicio. Fija los tiempos de espera deliberadamente, y haz idempotentes las llamadas con efectos para que un reintento nunca se ejecute dos veces. Falla de forma acotada, visible y que se degrade cuando se agoten los reintentos, y vigila tus límites para actuar antes del muro. Nada de esto es glamoroso, y todo ello es lo que separa una integración que sobrevive a una tarde ajetreada de una que no.
