llm-routing
Enrutamiento de LLM: un benchmark medido y el enrutador que defiende
Un servicio de enrutamiento de LLM consciente de costes (LangGraph + MCP) y el benchmark de 417 tareas que decide su política.
Responde con el modelo más barato que pueda verificarse que ha acertado, y escala solo cuando la verificación falla. Si eso supera a simplemente pagar por el mejor modelo no es una cuestión de opinión: depende de los modelos entre los que eliges, y este repositorio lo mide en tres escaleras reales.
El hallazgo, en una frase: cascada cuando el peldaño superior es genuinamente mejor y la verificación es barata — ningún umbral de relación de precios acierta con las tres escaleras. El enrutador incluido calcula ese veredicto por escalera a partir de las mediciones comprometidas, y se niega a responder para una escalera de la que no tiene datos.
Lo que se ejecuta | Una máquina de estados de LangGraph — |
Lo que decide su política | 417 tareas (código MBPP+, nivel 5 de MATH-500), 9 políticas, 3 escaleras de precios, todas medidas en modelos reales: fronteras coste-precisión, McNemar exacto, bootstrap pareado. |
Construido con | Python 3.10–3.13 · LangGraph · MCP · APIs de Anthropic + DeepSeek · pytest (268 tests) · GitHub Actions. El núcleo de investigación es biblioteca estándar pura — ninguna dependencia puede cambiar un número del benchmark. |
Evidencia | 5.075 respuestas reales de modelos, comprometidas. $8.51 gastados. Cada figura y tabla se regenera sin conexión, sin clave de API, por $0.00. |
Inicio rápido
pip install -e ".[agent]"
python -m llm_routing.build_taskset
python -m router_agent.cli --demo # real model output, no API key, $0.00Sin cuenta, sin clave, sin dinero: las respuestas se compraron una vez y se comprometieron, por lo que el enrutador reproduce la salida genuina del modelo en lugar de simularla.
python scripts/demo.py imprime las tres trazas canónicas: la cascada que gana
en el peldaño barato, la cascada que paga dos veces, y el caso de código donde
la verificación es exacta y gratuita. La primera de ellas:
1. The cascade's win - verified at the cheap rung
--------------------------------------------------------------------------
query: Let f(x) = x^3 - 3x + 1. Find the sum of the squares of all real roots. [...]
classify domain=math, start=cheap, verifier=self_consistency
answer cheap (deepseek-v4-flash) answered
verify self_consistency -> ACCEPT, confidence=1.00
finalize done: verified
answered by deepseek-v4-flash
verified True (self_consistency)
cost $0.000315 backend $0.000000Esas cuatro líneas son un recorrido por el grafo siguiente, que se extrae de
router_agent/graph.py en lugar de dibujarse: el borde de escalada vuelve a
answer, y ese bucle es lo que convierte esto en una cascada en lugar de un
enrutador.
Tres muestras independientes de DeepSeek dieron todas la respuesta correcta, por lo que la cascada aceptó y nunca llamó a Opus 5 — aproximadamente 27x más barato que enrutar directamente al peldaño superior. Cuando la verificación falla, la cascada escala y paga por ambos peldaños. Si ese intercambio merece la pena es lo que mide el resto de este repositorio.
El hallazgo
Comparación pre-registrada contra simplemente pagar siempre por el mejor modelo. McNemar exacto sobre resultados pareados, n=209 tareas reservadas por escalera.
escalera | peldaños | cascada | siempre-caros | Δ precisión | p | Δ coste/tarea |
| v4-flash → Opus 5 | 95.7% | 92.3% | +3.3% | 0.039 | −$0.00307 |
| Haiku 4.5 → Sonnet 5 → Opus 5 | 96.7% | 92.3% | +4.3% | 0.012 | +$0.00097 |
| v4-flash → v4-pro | 86.6% | 83.7% | +2.9% | 0.070 | −$0.00000 |
En wide la cascada es más precisa y cuatro veces más barata. En claude
compra la precisión a un precio superior — la verificación no es gratuita cuando el
peldaño barato es Haiku y la mitad de matemáticas extrae cinco muestras de él. La escalera
decide el signo, por eso el enrutador de abajo la lee en lugar de asumirla.
Tres resultados más, cada uno con sus números y sus advertencias en docs/RESULTS.md:
El enrutamiento predictivo no supera a una moneda al aire — seis comparaciones de seis. Ni un LLM como enrutador ni el BERT preentrenado de RouteLLM superan a un nulo aleatorio ajustado por coste en ninguna escalera, mientras que la cascada supera a ambos en todas. La distinción es cuándo se toma la decisión: un enrutador predictivo se compromete antes de ver un intento, una cascada decide después de verificar uno. → las seis comparaciones, y el AUC de frontera que las respalda
La precisión oculta lo que realmente hizo un enrutador. Dos políticas pueden alcanzar la misma precisión escalando las diez tareas correctas o escalando todo.
always_expensiveescala 201 tareas para comprar 27 rescates, quemando $0.71 en escaladas que no podían mejorar la respuesta;cascadeconsigue 24 de esos rescates y desperdicia $0.084. → la tarjeta de puntuación por políticaCada política es una curva, no un punto. Cada enrutador aquí tiene un mando que intercambia precisión por dinero, así que comparar dos en un ajuste cada uno deja que quien ajustó los mandos elija al ganador.
frontier.pybarre cada mando a lo largo de todo su rango y compara las curvas resultantes. → las fronteras, y por qué la relación de precios no lo decide
Cada uno de esos tiene una figura en figures/, que enumera lo que cada gráfico
afirma y de qué artefacto en runs/ se dibujó.
El benchmark incluye su propia conclusión
Un benchmark que termina en una tabla deja que el lector lo aplique. Este termina en
una función. findings.ratio_verdict(ladder) lee la frontera comprometida de esa
escalera y devuelve el veredicto para ella. Misma consulta, dos escaleras, respuestas
opuestas:
$ llm-router --estimate "prove that sqrt(2) is irrational" --ladder wide
recommended policy cascade (measured on the wide ladder)
cascade vs always-best, at matched accuracy -83.1%
$ llm-router --estimate "prove that sqrt(2) is irrational" --ladder claude
recommended policy route (measured on the claude ladder)
cascade vs always-best, at matched accuracy +11.7%Ese cambio es el hallazgo, y el enrutador lo lee en lugar de asumirlo — y
declina para una escalera de la que no tiene datos. La CLI, la herramienta MCP
explain_routing y los valores predeterminados de RouterConfig llaman todos a la misma
función, por lo que cambiar lo que midió el benchmark cambia lo que recomienda el enrutador.
No hay constante que quede desactualizada — solía haber una, y dos de sus tres veredictos
estaban al revés.
Diseño
llm_routing/ the experiment — 16 modules, standard library only
router_agent/ the product — LangGraph cascade + MCP server
cache/ 5,075 real model responses — what makes replay free
runs/ every derived artefact: results, frontiers, scorecards
data/ docs/ figures/ scripts/ tests/ archive/Las dos mitades comparten un cliente de modelo, una tabla de precios y una caché de respuestas,
que es lo que hace que una cifra en dólares del enrutador signifique lo mismo que una
cifra en dólares en las tablas. La flecha va en una dirección — router_agent importa
llm_routing, nunca al revés — y CI tiene un trabajo cuyo único propósito es
mantenerlo así. Módulo por módulo:
docs/ARCHITECTURE.md.
Úsalo desde un cliente MCP
El enrutador es un servidor MCP: cinco herramientas (route_query, resume_routing,
estimate_cost, compare_policies, explain_routing), cuatro recursos de solo
lectura bajo routing://, y un prompt que guía a un cliente a través de
la elección de una política.
Se incluye un .mcp.json, por lo que Claude Code recoge el servidor con pip install -e ".[agent,mcp]" y nada más. Para Claude Desktop o cualquier otro cliente,
el mismo bloque lo registra manualmente:
{
"mcpServers": {
"llm-routing": {
"command": "python",
"args": ["-m", "router_agent.mcp_server"],
"env": {"ROUTER_LADDER": "wide", "ROUTER_MODE": "replay",
"ROUTER_K": "3", "ROUTER_AGREEMENT": "1.0"}
}
}
}ROUTER_MODE=replay es el registro seguro: el servidor responde desde las
respuestas comprometidas y no puede gastar dinero, a costa de solo servir
prompts que realmente se pagaron — cualquier otra cosa vuelve como un
no_cached_response estructurado en lugar de una respuesta fabricada. ROUTER_K=3 está fijado para
coincidir con los parámetros bajo los que se compraron esas respuestas; el valor predeterminado de 5
pediría a la caché muestras que nadie compró. ROUTER_MODE=real con una clave
sirve consultas arbitrarias y las factura.
Aprobando una escalada
Sin establecer por defecto. Añade ROUTER_APPROVAL_USD y una escalada proyectada más cara
suspende el grafo en lugar de gastar: route_query devuelve
stop_reason: awaiting_approval con un thread_id y una carga útil interrupted
que nombra el modelo y el precio, y resume_routing lleva la
respuesta del humano de vuelta.
"env": {"ROUTER_LADDER": "wide", "ROUTER_MODE": "replay",
"ROUTER_K": "3", "ROUTER_AGREEMENT": "1.0",
"ROUTER_APPROVAL_USD": "0.001"}La aprobación es por escalada — el nodo escalate la limpia al pasar
— así que una escalera de tres peldaños pregunta dos veces, y un cliente tiene que reanudar hasta
que stop_reason sea otra cosa. El checkpoint es un InMemorySaver que vive en
el proceso del servidor, por lo que ambas llamadas deben llegar al mismo servidor en ejecución: un cliente
que genera uno por llamada, scripts/mcp_call.py incluido, nunca puede reanudar lo que
el anterior pausó. Un thread_id que ya no existe vuelve como
no_suspended_run en lugar de un KeyError desde dentro de LangGraph.
Viendo toda la superficie a la vez
python scripts/demo_mcp.pyUn recorrido con guion del servidor a través de una sesión real de cliente stdio — lo que anuncia y cuáles de sus propias llamadas gastan, un recurso, el cambio de escalera, una proyección gratuita, una respuesta enrutada, y el bucle de aprobación respondido de ambas maneras. Sin clave, sin gasto; termina imprimiendo lo que las consultas habrían costado en producción contra lo que realmente salió de la cuenta.
Inicia dos servidores, y la razón es el punto de ROUTER_K:
las muestras de auto-consistencia se almacenan en caché por índice de muestra, por lo que k está fijado
al inicio a lo que se compraron las respuestas — k=3 para la consulta que
verifica en el peldaño barato, k=4 para la que su cuarta muestra discrepa y
desencadena la escalada. demo.py muestra lo que hace el enrutador; esto muestra lo que
hace el servidor.
Conduciéndolo desde un terminal
scripts/mcp_call.py es un cliente MCP de un solo uso — inicia el servidor, hace el
apretón de manos, llama a una herramienta e imprime el resultado:
python scripts/mcp_call.py --listpython scripts/mcp_call.py explain_routing ladder=widepython scripts/mcp_call.py --resource routing://findings/probeEnviar JSON-RPC a mano no funciona, y el fallo es silencioso: el servidor
toma EOF de stdin como apagado y sale sin vaciar su cola, por lo que
echo '...' | python -m router_agent.mcp_server imprime la respuesta de inicialización,
suelta la llamada a la herramienta y sale con 0. Un cliente mantiene la tubería abierta.
Para gastar dinero real, nombra el modo — esta es una llamada genuina a DeepSeek, enrutada y con precio a través de MCP:
ROUTER_MODE=real ROUTER_LADDER=deepseek ROUTER_K=3 ROUTER_AGREEMENT=1.0 python scripts/mcp_call.py route_query query="What is 17 * 23? Give the final answer in \boxed{}." domain=math answered by deepseek-v4-flash (cheap)
verified True via self_consistency
cost $0.000068 backend $0.000068
classify domain=math, start=cheap, verifier=self_consistency
answer cheap (deepseek-v4-flash) answered
verify self_consistency -> ACCEPT, confidence=1.00Tres llamadas HTTP — una respuesta codiciosa y dos más para comprobarla contra sí misma —
aceptadas por unanimidad en el peldaño barato, por lo que v4-pro nunca se tocó. Ejecútalo
una segunda vez y backend_cost_usd es $0.00 mientras que cost_usd no cambia:
las respuestas se almacenaron en caché al salir, que es el mismo mecanismo que permite
al benchmark reproducir 5.075 de ellas gratis. Las dos cifras están separadas a propósito
— una es lo que cuesta servir en producción, la otra es lo que salió de la cuenta.
Lo que compra una consulta servida aterriza en cache/serving.<ladder>.jsonl, no en
el cache/raw_calls.<ladder>.jsonl del benchmark. Ambos contienen respuestas reales pagadas, pero
solo uno es evidencia: el archivo del benchmark es el conjunto cerrado del que se calcula
cada tabla publicada, y permitir que una consulta arbitraria se añada a él movería
el recuento de respuestas y el gasto total citado abajo. Servir aún lee la
caché del benchmark, que es lo que hace que --demo sea gratuito.
Verificándolo
python scripts/check_mcp_server.pyDos fases, y la segunda es la que importa. Enumera y llama a las herramientas en el proceso, luego lanza el servidor como subproceso y le habla JSON-RPC manualmente — porque en stdio, stdout es el protocolo, y un solo print perdido bajo una herramienta corrompe la trama mientras todas las pruebas en proceso siguen pasando. Eso no es hipotético: response_cache avisó de claves obsoletas en stdout, en una ruta de código a la que solo llega route_query, así que el servidor enumeró sus herramientas perfectamente y luego devolvió una respuesta mutilada a la primera llamada real.
Reproducirlo todo
El modo de reproducción vuelve a ejecutar el análisis publicado contra las respuestas confirmadas — sin clave, sin red, 0,00 $:
ROUTER_MODE=replay python scripts/run_all_ladders.py --ladders wide # ~30 minOmita --ladders wide para los tres, unos 75 minutos. Las cifras publicadas se produjeron exactamente así después de eliminar todo artefacto derivado: 0 llamadas llegaron a un backend, 0 filas se simulan, y cada archivo regenerado volvió byte-idéntico al confirmado.
La reproducción no necesita nada instalado — biblioteca estándar pura, sin conexión, byte-determinista hasta las cifras — y es la opción predeterminada, así que ninguno de los comandos anteriores nombra un modo. El modo real necesita una clave y gasta dinero. Hay un tercer modo, mock, que fabrica respuestas para la suite de pruebas y en el que todos los módulos de análisis se niegan a ejecutarse. Los tres, junto con cada punto de entrada de análisis y el orden de compra de datos, están en docs/METHOD.md.
Documentación
archivo | léelo cuando |
quieras la versión en lenguaje sencillo, sin asumir familiaridad con el enrutamiento — empieza aquí | |
quieras todos los hallazgos, con los números y lo que costaron | |
quieras el método: conjunto de tareas, por qué estos conjuntos de datos, escaleras, políticas, verificadores, el experimento de degradación, cómo ejecutarlo de verdad y los errores que este proyecto encontró en sí mismo | |
quieras saber cómo encajan el benchmark y la capa de servicio, módulo por módulo | |
quieras lo que limita las afirmaciones |
Qué limita las afirmaciones, y qué hay de nuevo aquí
Declarado en la portada en lugar de enterrado: el verificador que produce la señal no es el verificador que se distribuye. La mitad del código se califica ejecutando las pruebas que suministra MBPP+, y un router desplegado no las tiene.
Esa brecha se valora en lugar de solo señalarse, y valorarla es lo que este repositorio añade a la literatura. FrugalGPT (2305.05176) es la línea base de cascada, y tanto él como AutoMix dan su verificador por sentado; Dekoninck et al. (2410.10347) identifican la precisión del estimador de calidad como el factor que decide si todo esto funciona, pero lo prueban inyectando ruido sintético. Aquí sweep_degraded.py en cambio degrada un verificador real en una cantidad controlada en tareas calificadas objetivamente, manteniendo fijos dominio, modelos, prompts y calificador — así que enviar un verificador proxy es un movimiento a lo largo de una curva medida, no un paso hacia lo desconocido.
Cualquier otro límite se declara una vez en docs/LIMITATIONS.md con lo que lo resolvería, y la bibliografía completa está en docs/METHOD.md.
Licencia
MIT — ver LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Hosted MCP server for LLM cost estimation, model comparison, and budget-aware routing.
Agent Cost Allocator MCP — multi-tenant LLM cost attribution for chargeback billing. Companion to
AI Reasoning Cache & Consensus Layer with 11 MCP tools via Streamable HTTP.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/APantov/llm-routing-comparison'
If you have feedback or need assistance with the MCP directory API, please join our Discord server