# Routing

> Distribuye solicitudes entre deployments según capacidad, límites y estado.

URL: https://boelabs.com/docs/ai-gateway/routing
Language: Español

El router selecciona un deployment elegible para cada intento. Los deployments con el mismo `publicModel` forman un grupo; la estrategia seleccionada determina cómo se distribuye el tráfico dentro de ella. Las reglas de reintento y las políticas de fallback controlan lo que sucede cuando falla un intento.

## Estrategias [#strategies]

Configurado con `PUT /admin/router-settings` (`routingStrategy`), efectivo para **cada** gateway de grupo; actualmente no hay anulación por deployment o por public model.

| Estrategia | Elige el deployment con... | Bueno para |
|---|---|---|
| `simple-shuffle` (predeterminado) | Un sorteo aleatorio ponderado sobre `weight` | Carga uniforme en deployments idénticas |
| `least-busy` | La menor cantidad de solicitudes a bordo en este momento | Tráfico intenso, duración de solicitud desigual |
| `usage-based-rpm` | La menor cantidad de solicitudes en el minuto actual | Hacer cumplir la paridad suave de RPM en un grupo |
| `usage-based-tpm` | La menor cantidad de tokens en el minuto actual | Aplicar la paridad suave de TPM en un grupo |
| `latency-based` | La latencia de finalización de EWMA más baja | Minimizar el tiempo de respuesta entre proveedores de velocidad mixta |
| `throughput-based` | Los tokens de salida EWMA más altos por segundo | Maximizar el rendimiento del streaming |
| `price-based` | La tasa de token comparable más baja o `searchUnitCents` | Optimización de costos en modelos equivalentes con una única base de facturación |
| `health-aware` | El más alto `successRate × weight` | Alejar el tráfico de un deployment inestable sin cortarlo por completo |

Un grupo de exactamente un deployment omite la selección por completo: la estrategia nunca se ejecuta y las métricas aún se acumulan para la observabilidad. Todas las estrategias que se clasifican según una métrica activa (`latency-based`, `throughput-based`, `price-based`, `health-aware`) vuelven a ser aleatorias ponderadas cuando ningún candidato tiene un valor utilizable todavía (por ejemplo, una nuevo deployment sin solicitudes completadas).

Para la reranking, los precios unitarios de búsqueda son exactos sólo cuando cada candidato declara esa misma base. Si un grupo combina facturación de token y unidad de búsqueda, combina ambas bases o contiene un precio desconocido, `price-based` recurre a la selección ponderada en lugar de presentar una comparación numérica falsa.

### Cómo se mide cada métrica [#how-each-metric-is-measured]

Las métricas por deployment se encuentran en Redis, codificadas por ID de deployment y se leen actualizadas en cada decisión de routing (sin caching del lado del cliente):

- **En vuelo**: se incrementa cuando comienza un intento, se reduce cuando finaliza, falla o el cliente lo cancela. TTL para que un proceso bloqueado no pueda filtrar un deployment permanentemente "ocupada".
- **RPM / TPM** — contadores por minuto. Las RPM aumentan en cada intento de inicio; el TPM aumenta en `totalTokens` en caso de éxito. Ambos expiran por sí solos cada minuto.
- **Latencia**: un EWMA (α = 0,2) de tiempo transcurrido desde justo antes de la llamada upstream hasta el momento en que realmente terminó de responder. Para las solicitudes de streaming, este es el tiempo hasta el **último fragmento upstream**, no cuando el cliente terminó de recibir la respuesta retransmitida, por lo que el routing basado en latencia refleja la velocidad upstream, no el tiempo que tomó su propia red hasta el cliente.
- **Rendimiento**: un EWMA (α = 0,2) de `completionTokens / durationSeconds`, que utiliza el mismo tiempo de finalización upstream como latencia.
- **Puntuación de salud**: `successes / (successes + failures)` durante un período continuo de 10 minutos. Un deployment sin **ningún intento registrado** en esa ventana tiene como valor predeterminado un **0.5** neutral, no un 1.0 perfecto; un valor predeterminado perfecto permitiría a `health-aware` reenviar inmediatamente todo el tráfico a un deployment que estaba fallando hace minutos, en el instante en que sus contadores expiran después de ser enrutados. Neutral lo mantiene clasificado detrás de cualquier cosa con un historial real, sin incluirlo permanentemente en la lista negra.

Ninguna de estas métricas influye en la **elegibilidad** del deployment: todavía se intenta un deployment lenta o de bajo estado, solo que con menos frecuencia. La elegibilidad se decide por separado según el estado del circuito y los **límites** de RPM/TPM por deployment (consulta a continuación).

## Grupos, pesos y límites por deployment [#pools-weights-and-per-deployment-limits]

Los deployments creados con el mismo `publicModel` forman un grupo automáticamente. Dos opciones controlan el pool por deployment (configuradas en el deployment, no en la configuración del router):

- **`weight`**: participación relativa del tráfico en `simple-shuffle` y el multiplicador aplicado a la puntuación de estado en `health-aware`. Un deployment con `weight: 0` nunca se elige por ponderación aleatoria, pero las otras estrategias aún lo consideran (clasificación según su propia métrica, ignorando el peso, excepto `health-aware`, que multiplica por peso y, por lo tanto, también omite un deployment de peso `0`).
- **`rpmLimit` / `tpmLimit`**: límites estrictos por deployment. Un deployment que se encuentre actualmente en cualquiera de los límites o por encima de ellos queda excluido del grupo de candidatos para este intento (no enfriado, simplemente omitido esta ronda). Si **todos** los deployments en el grupo superan su límite, la solicitud falla con `rate_limit_exceeded` (`429`) antes de realizar cualquier llamada upstream.
- **`failureDomain`**: identidad de cuota/cuenta de proveedor compartida opcional. Un proveedor `429` abre un circuito de capacidad para cada deployment con el mismo valor, por lo que duplicar filas respaldadas por una cuenta no multiplica los reintentos. Omitirlo cuando los deployments tengan cuotas realmente independientes.

## Disyuntores y reintentos acotados [#circuit-breakers-and-bounded-retries]

Configurado globalmente a través de `PUT /admin/router-settings`:

Los controles de los circuitos siguen siendo globales. Los plazos de ejecución y los presupuestos de reintento se definen mediante la matriz exhaustiva `executionPolicies` en lugar de un tiempo de espera compartido por operaciones no relacionadas.

| Campo | Predeterminado | Significado |
|---|---|---|
| `allowedFails` | `3` | Fallos transitorios de solicitudes lógicas: se permite un deployment antes de que la **siguiente** abre el cooldown. Un éxito reinicia la cuenta; `0` se abre ante el primer error. |
| `cooldownSeconds` | `5` | Tiempo que un deployment permanece en cooldown después de exceder `allowedFails`. `0` desactiva los circuitos de enfriamiento automático. |
| `retryAfterSeconds` | `0` | Espera mínima antes de reintentar el mismo upstream (ver [Reintentos](#retries)). |

Los campos restantes son controles de seguridad avanzados:

| Campo | Predeterminado | Significado |
|---|---|---|
| `failureWindowSeconds` | `60` | Ventana fija (no deslizante) de recuento de fallos |
| `maxCooldownSeconds` | `300` | Enfriamiento exponencial máximo después de sondas de recuperación fallidas |
| `halfOpenProbeSeconds` | `30` | Propiedad TTL para la sonda única de recuperación semiabierta |
| `configurationCooldownSeconds` | `300` | Cuarentena inmediata para credenciales/acceso a modelos no válidos |
| `throttleCooldownSeconds` | `5` | Tiempo de reutilización mínimo de capacidad compartida para las respuestas del proveedor `429` |

### Cuánto espera el gateway [#how-long-the-gateway-waits]

Cada entrada `executionPolicies[operation].json|stream` define `responseMs`, `idleMs` anulable, `totalMs` y `maxAttempts`. Se aplican sobre la **conexión**, como los timeouts de un cliente HTTP, y nunca sobre la salida del modelo:

- **`responseMs`**: JSON, hasta que llega la respuesta upstream completa. Stream, hasta que el upstream responde con los headers.
- **`idleMs`**: solo stream; el silencio máximo entre dos **bytes** upstream. Cualquier byte cuenta, incluidos los keepalives del proveedor, así que un modelo que razona en silencio durante minutos no se confunde con uno bloqueado. `null` lo desactiva.
- **`totalMs`**: la solicitud completa, con todos sus intentos, reintentos y fallbacks.

Un plazo vencido aborta la propia solicitud upstream. Los valores predeterminados coinciden con los SDK oficiales de OpenAI y Anthropic (600 s de lectura), así que llamar a un proveedor a través del gateway nunca es menos paciente que llamarlo directamente:

| Operación | Respuesta | Inactividad | Total | Intentos |
|---|---:|---:|---:|---:|
| Texto stream/WebSocket | 600 s | 600 s | 3600 s | 3 |
| Texto JSON | 600 s | — | 1800 s | 3 |
| Imágenes | 600 s | 300 s en stream | 1800 s | 2 |
| Audio | 900 s JSON, 600 s stream | 300 s en stream | 1800 s | 2 |
| Embeddings / reranking | 120 s | — | 300 s | 3 |
| Enviar/actualizar/eliminar vídeo | 120 s | — | 300 s | 2 |
| Descarga de vídeo | 120 s | 120 s | 900 s | 2 |

Los `executionPolicyOverrides` de un deployment solo pueden ajustarlos a la baja. Las políticas guardadas por una versión que aún usaba plazos de primera salida, pre-commit o solo-reasoning se sustituyen por estos valores.

Detrás de Cloudflare, una solicitud JSON que dura más de unos 100 s recibe un `524` de Cloudflare, permita lo que permita el gateway: usa `stream: true` para generaciones largas.

### Reintentos [#retries]

El router hace una primera pasada por los deployments elegibles antes de reutilizar uno. Un deployment que nadie ha probado se usa de inmediato; volver a preguntar al mismo upstream espera 0,5 s, duplicándose hasta 8 s, nunca menos que `retryAfterSeconds` ni que el `Retry-After` del proveedor (respetado hasta 60 s), más hasta un 25 % de jitter.

Un stream solo se reintenta o pasa a un fallback antes de haber mostrado algo al cliente: hasta el primer frame de reasoning, contenido o herramienta no hay nada comprometido. Después, un fallo cierra el stream con un evento de error en banda, como haría el stream del propio proveedor.

Las solicitudes en streaming retienen sus headers hasta 10 s mientras se resuelve el routing, de modo que un error en esa ventana (un modelo desconocido, una solicitud rechazada, todos los upstreams fallando rápido) se responde con su estado HTTP real, igual que el proveedor. Pasados 10 s el stream se abre y los keepalives mantienen la conexión.

### Qué cuenta como completo [#what-counts-as-complete]

El gateway no es más estricto que el SDK del propio proveedor. Un stream que terminó todas sus choices está completo aunque el upstream omita `[DONE]`; uno que llegó al marcador de fin del protocolo está completo aunque falte el finish reason (se deduce de la salida: `tool_calls` si se llamaron herramientas, si no `stop`); una respuesta vacía es una respuesta. Todo esto se registra en el intento como `anomalies` y nunca se reintenta. Solo falla un stream que se corta sin ninguna de las dos cosas (una conexión caída), como `upstream_stream_truncated`.

Las fallas se clasifican por efecto en lugar de por un grupo amplio de "errores de proveedor":

- **Solicitud** (fallas de entrada/contexto/contenido de clase `400`): un upstream que rechaza la propia solicitud la termina de inmediato con el estado y el mensaje saneado del proveedor; otro deployment del mismo modelo la rechazaría igual. Los rechazos por ventana de contexto o política de contenido pueden pasar a su cadena de [fallbacks](https://boelabs.com/docs/ai-gateway/fallbacks) dedicada. Nunca reduce la salud.
- **Acelerador** (`429`): no reduce el estado del deployment. Abre el circuito de capacidad compartido `failureDomain` inmediatamente y respete el valor `Retry-After` del proveedor cuando esté presente. Estas señales de capacidad no incrementan `allowedFails`.
- **Transitorio** (tiempos de espera, fallas de red, proveedor `5xx`, protocolo mal formado, stream truncado): se reintenta como se describe arriba y cuenta como máximo una falla por solicitud lógica y deployment hacia el circuito.
- **Configuración** (autenticación/acceso/modelo de proveedor no válido): ponga en cuarentena ese deployment inmediatamente en lugar de gastar repetidamente el presupuesto de la solicitud en ella.
- Errores de **gateway/ámbito de solicitud**: no penalizan ninguna deployment.

Las incompatibilidades de candidatos descubiertas por el preprocesamiento del gateway (por ejemplo, un origen de imagen o un tipo de archivo que un transport no puede consumir) son **neutrales** en cuanto a la salud. Pueden mover el routing a otro candidato o fallback, pero no incrementan los contadores de fallas del deployment, ni reducen su puntaje de salud ni activan el cooldown. Los errores de validación relacionados con la solicitud se detienen inmediatamente y también son neutrales.

Las transiciones de circuitos son atómicas en Redis. Una vez que el umbral de falla fijo abre un circuito, las fallas tardías en vuelo no pueden extenderlo. Después del cooldown, se vuelve **medio abierto**: exactamente una solicitud posee la sonda de recuperación, mientras que las solicitudes simultáneas utilizan otros candidatos. Una investigación exitosa lo cierra; una sonda fallida lo vuelve a abrir con un enfriamiento y una fluctuación exponenciales, limitados por `maxCooldownSeconds`. Mientras está cerrado, cada llamada upstream exitosa borra el recuento acumulado de fallas transitorias de ese deployment.

Cuando el grupo falla, quien llama recibe lo que habría dicho el proveedor: el estado y el mensaje público de la última respuesta upstream. El detalle del routing (intentos, motivos, causas del cooldown) queda en el registro de la operación. Si todos los circuitos de deployment están abiertos, el gateway devuelve `503 model_overloaded` con un header `Retry-After` preciso (`503 model_unavailable`, sin él, cuando todos los circuitos los abrió una mala configuración). Si la capacidad compartida es el bloqueador, devuelve `429 rate_limit_exceeded`. La vista autenticada `GET /v1/models/{model}/deployments` informa cada deployment como `available`, `cooldown`, `half_open` o `rate_limited`, además `retry_after_ms`.

## Reservas [#fallbacks]

Cuando falla cada deployment en el grupo primario (o está en enfriamiento o supera su límite de RPM/TPM), el router busca una **cadena alternativa** para ese public model y motivo de falla (`general`, `context_window` o `content_policy`) y vuelve a intentar todo el proceso de selección. cada public model alternativo en orden. Consulta [Fallbacks](https://boelabs.com/docs/ai-gateway/fallbacks) para conocer la configuración de la cadena, la semántica de reintento por salto de reserva y las reglas del ciclo de vida.

## Observando una decisión de routing [#observing-a-routing-decision]

Envía `x-unified-routing-metadata: true` en cualquier solicitud de chat (`/v1/chat/completions`, `/v1/messages`, `/v1/responses`) para obtener un objeto `unified_routing` en el cuerpo de la respuesta: la estrategia vigente, qué deployment realmente atendió la solicitud, si se utilizó un fallback y un registro por intento (latencia, clase de error, estado HTTP). Consulta [Headers](https://boelabs.com/docs/ai-gateway/headers) para conocer la forma exacta.

## Configurándolo [#configuring-it]

```bash
curl -X PUT "$GATEWAY/admin/router-settings" \
  -H "Authorization: Bearer $MASTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "routingStrategy": "latency-based",
    "allowedFails": 3,
    "cooldownSeconds": 10,
    "failureWindowSeconds": 60,
    "maxCooldownSeconds": 300,
    "halfOpenProbeSeconds": 30,
    "configurationCooldownSeconds": 300,
    "throttleCooldownSeconds": 5,
    "retryAfterSeconds": 1
  }'
```

`GET /admin/router-settings` devuelve la configuración efectiva (valor predeterminado si nunca se establece). Todos los campos son opcionales en el cuerpo `PUT`; los campos omitidos mantienen su valor actual. Al cambiar `executionPolicies` enviar la matriz completa devuelta por `GET`; Es intencionalmente exhaustivo, por lo que no se puede realizar una nueva operación sin una política explícita.

## Próximos pasos [#next-steps]

- [Política de parámetros](https://boelabs.com/docs/ai-gateway/parameter-policy): el otro comportamiento global del router, ortogonal a la estrategia.
- [Fallbacks](https://boelabs.com/docs/ai-gateway/fallbacks): qué sucede cuando falla todo un grupo.
- [Headers](https://boelabs.com/docs/ai-gateway/headers): la forma exacta de `x-unified-routing-metadata`.
- [Crear deployments](https://boelabs.com/docs/ai-gateway/creating-deployments) — `weight`, límites y `failureDomain`.

