Skip to main content
Glama

Enrutamiento de LLM: un benchmark medido y el enrutador que defiende

CI Python 3.10–3.13 License: MIT

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 — classify → answer → verify → escalate ⟲ — con aprobación humana en el bucle dentro del bucle de escalada y reanudación con checkpoint, servida a través de MCP con cinco herramientas y cuatro recursos.

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.00

Sin 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.000000

Esas 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.

La máquina de estados de LangGraph: classify, answer, verify, escalate, finalize

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

wide

v4-flash → Opus 5

95.7%

92.3%

+3.3%

0.039

−$0.00307

claude

Haiku 4.5 → Sonnet 5 → Opus 5

96.7%

92.3%

+4.3%

0.012

+$0.00097

deepseek

v4-flash → v4-pro

86.6%

83.7%

+2.9%

0.070

−$0.00000

Cascada contra siempre-caros, en precisión y en dinero, en las tres escaleras

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_expensive escala 201 tareas para comprar 27 rescates, quemando $0.71 en escaladas que no podían mejorar la respuesta; cascade consigue 24 de esos rescates y desperdicia $0.084. → la tarjeta de puntuación por política

  • Cada 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.py barre 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.py

Un 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 --list
python scripts/mcp_call.py explain_routing ladder=wide
python scripts/mcp_call.py --resource routing://findings/probe

Enviar 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.00

Tres 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.py

Dos 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 min

Omita --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

docs/EXPLAINED.md

quieras la versión en lenguaje sencillo, sin asumir familiaridad con el enrutamiento — empieza aquí

docs/RESULTS.md

quieras todos los hallazgos, con los números y lo que costaron

docs/METHOD.md

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

docs/ARCHITECTURE.md

quieras saber cómo encajan el benchmark y la capa de servicio, módulo por módulo

docs/LIMITATIONS.md

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.

-
license - not tested
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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.

View all MCP Connectors

Latest Blog Posts

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