Cómo rutear requests entre LLMs para bajar costos sin sumar latencia
Aprendé a rutear requests entre modelos chicos y de frontera en milisegundos con clasificadores System One para bajar costos de inferencia.
Mandás cada request de tu SaaS directo a un modelo de frontera y tu factura no para de subir. Además, la latencia P95 se dispara. En benchmarks internos sobre cargas mixtas de SaaS B2B, entre un 65% y un 75% de las consultas no necesitan razonamiento formal ni contextos gigantes. Son formateos estructurados, transformaciones idempotentes, extracción de entidades o clasificación de intención con contexto inyectado. Implementar una estrategia de routing de LLMs te permite derivar cada tarea al modelo exacto sin pagar de más.
El desafío real está en la compuerta de entrada: cómo clasificar y estimar la complejidad en milisegundos de un solo dígito sin sumar fallas sintácticas. Durante años se usó un LLM generativo compacto como router (LLM-as-a-Router), pero ese enfoque trae fricciones críticas. Con modelos System One y clasificadores directos (vía APIs como Jev o motores open-weights locales como Laya y backends en DeBERTa-v3 o SetFit), el routing de LLMs pasa a ser un problema determinista de clasificación probabilística. En este post te muestro cómo funciona este mecanismo y cómo implementarlo en tu arquitectura para bajar costos de inferencia sin arruinar la latencia.
flowchart TD
A["Cliente HTTP / SDK"] --> B["API Gateway"]
B --> C["Router Middleware (System One / Sidecar)"]
C -->|"Timeout (>50ms) o Error"| F["Tier Frontera (Razonamiento / Gran Escala)"]
C -->|"Score < 0.65 (Baja complejidad)"| E["Tier Rápido (SLM sub-3B a 8B)"]
C -->|"Score >= 0.65 o Dominio Crítico"| F
E --> G["Stream SSE / Response"]
F --> G
G --> A
Por qué el routing autorregresivo falla en alta concurrencia
Usar decodificación token por token para decidir el destino de un mensaje genera tres problemas claros:
- Sobrecarga de TTFT (Time to First Token): Un modelo generativo con gramáticas formales (CFG o JSON Schema) vía Outlines o SGLang decodifica en forma secuencial. Sumar entre 250 y 500 ms solo para decidir a dónde mandar el payload arruina la interactividad en streaming.
- Falta de calibración probabilística: Los LLMs generativos no vienen calibrados de fábrica por la pérdida de entropía cruzada y el sobreajuste de RLHF/DPO. Pedirle a un LLM que puntúe dificultad del 1 al 10 devuelve números basados en frecuencias superficiales, no una probabilidad posterior real.
- Fragilidad en tiempo de ejecución: Cualquier error al parsear la salida del router te obliga a meter reintentos sincrónicos que multiplican el tiempo total de respuesta.
Mecanismo de System One: inferencia directa sobre logits
Un modelo autorregresivo muestrea en forma recursiva la probabilidad de cada token según los anteriores: P(w_t | w_<t). En cambio, un clasificador System One procesa la secuencia en un único forward pass bidireccional o con un head discriminativo sobre las representaciones densas del encoder, una distinción clave que ya analizamos al ver Laya vs Jev en pipelines de decisión.
[Secuencia de Entrada]
│
▼
┌───────────────────┐
│ Transformer Core │ ──> [Representación Latente Z]
└───────────────────┘
│
┌───────┴────────────────────────┐
│ │
▼ ▼
[Classification Head] [Regression Head]
(Softmax Temperature) (Platt Scaling / Isotonic)
│ │
▼ ▼
Categoría Discreta Score de Complejidad [0.0 - 1.0]
(Logits normalizados) (Confianza Calibrada)
La estimación de incertidumbre no sale de un prompt. Viene de calibrar los logits de salida mediante Temperature Scaling o regresión isotónica post-entrenamiento:
P(y = complejo | x) = sigmoid((z_complejo - z_simple) / T)
La inferencia se resuelve en una única pasada liviana y devuelve datos tipados en menos de 20 ms.
Implementación de un gateway para routing de LLMs con tolerancia a fallos
Acá tenés un ejemplo de middleware en Node.js y TypeScript. Aplica límites estrictos de timeout (50 ms) y degradación elegante: si el clasificador duda o falla, la consulta escala directo al tier más capaz.
import { Request, Response, NextFunction } from 'express';
interface SystemOneDecision {
domain_category: { value: string; confidence: number };
complexity_score: { value: number; confidence: number };
requires_strict_factuality: { value: boolean; confidence: number };
}
export type ModelTier = 'tier_frontier' | 'tier_fast_slm' | 'tier_code_specialist';
const ROUTER_TIMEOUT_MS = 50;
const MIN_CONFIDENCE_THRESHOLD = 0.85;
export async function resolveModelTier(userPrompt: string): Promise<ModelTier> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), ROUTER_TIMEOUT_MS);
try {
// Llamada al motor de clasificación (Sidecar local o endpoint de inferencia ultra-rápido)
const response = await fetch('http://127.0.0.1:8080/v1/classify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
signal: controller.signal,
body: JSON.stringify({
context: userPrompt,
schema: [
{ id: 'domain_category', type: 'choice', options: ['chat', 'data_extraction', 'reasoning', 'code'] },
{ id: 'complexity_score', type: 'score', min: 0.0, max: 1.0 },
{ id: 'requires_strict_factuality', type: 'boolean' }
]
})
});
if (!response.ok) throw new Error(`Router HTTP Error: ${response.status}`);
const { decisions } = (await response.json()) as { decisions: SystemOneDecision };
const { domain_category, complexity_score, requires_strict_factuality } = decisions;
// Regla 1: Fallback a frontera si la calibración estadística detecta alta entropía/incertidumbre
if (
domain_category.confidence < MIN_CONFIDENCE_THRESHOLD ||
complexity_score.confidence < MIN_CONFIDENCE_THRESHOLD
) {
return 'tier_frontier';
}
// Regla 2: Tareas críticas o de razonamiento determinista van a frontera
if (requires_strict_factuality.value && complexity_score.value > 0.55) {
return 'tier_frontier';
}
// Regla 3: Tareas de código complejas a tier especializado
if (domain_category.value === 'code' && complexity_score.value > 0.40) {
return 'tier_code_specialist';
}
// Regla 4: Filtro general de complejidad por score normalizado
if (complexity_score.value >= 0.65) {
return 'tier_frontier';
}
return 'tier_fast_slm';
} catch (error) {
// Si hay timeout (>50ms) o error de red, degradar silenciosamente al modelo más capaz
return 'tier_frontier';
} finally {
clearTimeout(timeoutId);
}
}
Comparativa de estrategias de ruteo
| Dimensión | Routing Autorregresivo (LLM Prompting) | Embeddings + Clasificador Lineal (kNN / SetFit) | System One / Encoder-Classifier Head |
|---|---|---|---|
| Latencia P95 | 300 – 700 ms | 4 – 10 ms | 15 – 35 ms |
| Topología Óptima | Gateway externo | Proceso local / In-memory | Sidecar local o microservicio dedicado |
| Calibración de Certeza | Nula (dispersión por RLHF) | Media (dependiente del margen métrico) | Alta (optimizada vía Platt Scaling / Temperature) |
| Comprensión Semántica | Alta | Alta en dominios cerrados; rígida en out-of-domain | Alta y generalizable a instrucciones complejas |
| Parsing & Tipado | Requiere esquemas guiados | Tipado nativo (vectores de salida) | Tipado primitivo garantizado |
Límites operativos y decisiones de infraestructura
Sumar este patrón agrega piezas a tu infraestructura. Yo prefiero evitarlo en estos tres casos puntuales:
- Volumen menor a 10.000 requests/día: El ahorro en dinero no justifica mantener observabilidad, SLOs y esquemas para el router.
- Latencia entre regiones (Cross-Region Latency): Si tu router corre en
us-east-1y tu gateway eneu-west-1, el viaje de red suma entre 70 y 100 ms y mata la ventaja. Desplegalo como sidecar en la misma red privada o in-process. - Contextos gigantes pre-hidratados: Mandar 100k tokens de documentación al router satura el ancho de banda interno. Ruteá siempre sobre el query del usuario antes de recuperar datos con RAG.
Pasos para optimizar costos de inferencia
- Medí la complejidad de tus requests: Guardá una muestra de 2.000 trazas reales y separalas entre las que resuelve un modelo sub-8B y las que exigen frontera. Si más del 50% son extracciones o transformaciones directas, estás gastando de más.
- Desplegá un clasificador calibrado: Levantá un sidecar con un modelo discriminativo (vía checkpoints abiertos o servicios dedicados) y ajustá los umbrales según tus datos históricos. Si querés profundizar en la combinación con caching y decodificación, revisá esta guía de optimización de costos de inferencia en LLMs.
- Hacé un rollout canario con feature flags: Mandá un 10% del tráfico simple al tier rápido y controlá calidad de respuesta, tasa de regeneración, errores y latencia.
Optimizar inferencia no significa bajar la calidad del sistema. Se trata de armar una arquitectura donde usar modelos de frontera sea una excepción calculada y no la opción por defecto.