Skip to main content
Glama

LLM-Routing: ein gemessenes Benchmark und der Router, für den es argumentiert

CI Python 3.10–3.13 License: MIT

Ein kostenbewusster LLM-Routing-Dienst (LangGraph + MCP) und das Benchmark mit 417 Aufgaben, das seine Richtlinie bestimmt.

Antworte mit dem günstigsten Modell, das nachweislich richtig geantwortet hat, und eskaliere nur, wenn die Verifikation fehlschlägt. Ob das besser ist, als einfach für das beste Modell zu zahlen, ist keine Frage der Meinung – es hängt von den Modellen ab, zwischen denen du wählst, und dieses Repository misst es an drei realen Leitern.

Das Ergebnis in einem Satz: Kaskadiere, wenn die oberste Sprosse wirklich besser ist und die Verifikation günstig ist – kein Preisverhältnis-Schwellenwert trifft für alle drei Leitern zu. Der ausgelieferte Router berechnet dieses Urteil pro Leiter aus den festgehaltenen Messungen und verweigert die Antwort für eine Leiter, zu der er keine Daten hat.

Was läuft

Eine LangGraph-Zustandsmaschine – classify → answer → verify → escalate ⟲ – mit Human-in-the-Loop-Genehmigung innerhalb der Eskalationsschleife und checkpointbasiertem Fortsetzen, bereitgestellt über MCP mit fünf Tools und vier Ressourcen.

Was seine Richtlinie bestimmt

417 Aufgaben (MBPP+ Code, MATH-500 Stufe 5), 9 Richtlinien, 3 Preisleitern, alle an echten Modellen gemessen: Kosten-Qualitäts-Grenzen, exakter McNemar, gepaartes Bootstrap.

Gebaut mit

Python 3.10–3.13 · LangGraph · MCP · Anthropic + DeepSeek APIs · pytest (268 Tests) · GitHub Actions. Der Forschungskern ist reine Standardbibliothek – keine Abhängigkeit kann eine Benchmark-Zahl verändern.

Belege

5.075 echte Modellantworten, festgehalten. $8,51 ausgegeben. Jede Abbildung und Tabelle wird offline neu generiert, ohne API-Schlüssel, für $0,00.


Schnellstart

pip install -e ".[agent]"
python -m llm_routing.build_taskset
python -m router_agent.cli --demo      # real model output, no API key, $0.00

Kein Konto, kein Schlüssel, kein Geld: Die Antworten wurden einmal gekauft und festgehalten, sodass der Router echte Modellausgaben abspielt, statt sie zu simulieren.

python scripts/demo.py gibt die drei kanonischen Abläufe aus – die Kaskade, die auf der günstigen Sprosse gewinnt, die Kaskade, die doppelt zahlt, und den Code-Fall, in dem die Verifikation exakt und kostenlos ist. Der erste davon:

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

Diese vier Zeilen sind ein Durchgang durch den unten stehenden Graphen, der aus router_agent/graph.py geparst wird, statt gezeichnet zu werden – die Eskalationskante führt zurück zu answer, und genau diese Schleife macht das zu einer Kaskade statt zu einem Router.

Die LangGraph-Zustandsmaschine: classify, answer, verify, escalate, finalize

Drei unabhängige Ziehungen von DeepSeek ergaben alle die richtige Antwort, also akzeptierte die Kaskade und rief Opus 5 nie auf – etwa 27x günstiger als direkt zur obersten Sprosse zu routen. Wenn die Verifikation fehlschlägt, eskaliert die Kaskade und zahlt für beide Sprossen. Ob sich dieser Tausch lohnt, misst der Rest dieses Repositorys.

Das Ergebnis

Vorab registrierter Vergleich gegen das einfache Immer-Zahlen für das beste Modell. Exakter McNemar über gepaarte Ergebnisse, n=209 zurückgehaltene Aufgaben pro Leiter.

Leiter

Sprossen

Kaskade

Immer-teuer

Δ Genauigkeit

p

Δ Kosten/Aufgabe

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

Kaskade gegen Immer-teuer, in Genauigkeit und in Geld, auf allen drei Leitern

Auf wide ist die Kaskade genauer und viermal günstiger. Auf claude erkauft sie sich die Genauigkeit mit einem Aufpreis – Verifikation ist nicht kostenlos, wenn die günstige Sprosse Haiku ist und die Mathe-Hälfte fünf Stichproben daraus zieht. Die Leiter entscheidet das Vorzeichen, weshalb der unten stehende Router sie liest, statt sie anzunehmen.

Drei weitere Ergebnisse, jeweils mit Zahlen und Einschränkungen in docs/RESULTS.md:

  • Prädiktives Routing schlägt keinen Münzwurf – sechs von sechs Vergleichen. Weder ein LLM-als-Router noch RouteLLMs vortrainiertes BERT schlägt eine kostenangepasste Zufalls-Nullhypothese auf irgendeiner Leiter, während die Kaskade beide auf jeder schlägt. Der Unterschied liegt im Zeitpunkt der Entscheidung: Ein prädiktiver Router legt sich fest, bevor er einen Versuch sieht, eine Kaskade entscheidet nach der Verifikation eines Versuchs. → die sechs Vergleiche und die Grenz-AUC dahinter

  • Genauigkeit versteckt, was ein Router tatsächlich getan hat. Zwei Richtlinien können dieselbe Genauigkeit erreichen, indem sie die richtigen zehn Aufgaben eskalieren oder indem sie alles eskalieren. always_expensive eskaliert 201 Aufgaben, um 27 Rettungen zu kaufen, und verbrennt $0,71 für Eskalationen, die die Antwort nicht verbessern konnten; cascade bekommt 24 dieser Rettungen und verschwendet $0,084. → die Ergebnisliste pro Richtlinie

  • Jede Richtlinie ist eine Kurve, kein Punkt. Jeder Router hier hat einen Regler, der Genauigkeit gegen Geld tauscht. Wenn man also zwei bei jeweils einer Einstellung vergleicht, kann der, der die Regler gesetzt hat, den Sieger bestimmen. frontier.py durchläuft jeden Regler über seinen gesamten Bereich und vergleicht die resultierenden Kurven. → die Grenzen und warum das Preisverhältnis nicht darüber entscheidet

Jedes davon hat eine Abbildung in figures/, die auflistet, was jedes Diagramm behauptet und aus welchem Artefakt in runs/ es gezeichnet wurde.

Das Benchmark liefert seine eigene Schlussfolgerung

Ein Benchmark, das in einer Tabelle endet, überlässt es dem Leser, es anzuwenden. Dieses endet in einer Funktion. findings.ratio_verdict(ladder) liest die festgehaltene Grenze dieser Leiter und gibt das Urteil dafür zurück. Dieselbe Abfrage, zwei Leitern, gegensätzliche Antworten:

$ 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%

Dieser Umschwung ist das Ergebnis, und der Router liest es, statt es anzunehmen – und lehnt für eine Leiter ab, zu der er keine Daten hat. Die CLI, das MCP-Tool explain_routing und die RouterConfig-Standardwerte rufen alle dieselbe Funktion auf, sodass eine Änderung dessen, was das Benchmark gemessen hat, auch ändert, was der Router empfiehlt. Es gibt keine Konstante, die veralten könnte – es gab eine, und zwei ihrer drei Urteile waren falsch.

Layout

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/

Die beiden Hälften teilen sich einen Modell-Client, eine Preistabelle und einen Antwort-Cache, was dafür sorgt, dass ein Dollarwert vom Router dasselbe bedeutet wie ein Dollarwert in den Tabellen. Der Pfeil verläuft nur in eine Richtung – router_agent importiert llm_routing, nie umgekehrt – und CI hat einen Job, dessen einziger Zweck es ist, das so zu halten. Modul für Modul: docs/ARCHITECTURE.md.

Aus einem MCP-Client verwenden

Der Router ist ein MCP-Server: fünf Tools (route_query, resume_routing, estimate_cost, compare_policies, explain_routing), vier schreibgeschützte Ressourcen unter routing:// und ein Prompt, der einen Client durch die Auswahl einer Richtlinie führt. Eine .mcp.json ist festgehalten, sodass Claude Code den Server bei pip install -e ".[agent,mcp]" automatisch aufnimmt und sonst nichts. Für Claude Desktop oder einen anderen Client registriert derselbe Block ihn von Hand:

{
  "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 ist die sichere Registrierung: Der Server antwortet aus den festgehaltenen Antworten und kann kein Geld ausgeben, auf Kosten dessen, dass er nur Prompts bedient, die tatsächlich bezahlt wurden – alles andere kommt als strukturiertes no_cached_response zurück, statt als erfundene Antwort. ROUTER_K=3 ist festgelegt, um den Parametern zu entsprechen, unter denen diese Antworten gekauft wurden; der Standardwert von 5 würde den Cache nach Stichproben fragen, die niemand gekauft hat. ROUTER_MODE=real mit einem Schlüssel bedient beliebige Abfragen und stellt sie in Rechnung.

Eine Eskalation genehmigen

Standardmäßig nicht gesetzt. Füge ROUTER_APPROVAL_USD hinzu, und eine Eskalation, die teurer als dieser Betrag projiziert wird, unterbricht den Graphen, statt Geld auszugeben: route_query gibt stop_reason: awaiting_approval zurück, mit einer thread_id und einem interrupted-Payload, der Modell und Preis nennt, und resume_routing trägt die Antwort des Menschen wieder ein.

"env": {"ROUTER_LADDER": "wide", "ROUTER_MODE": "replay",
        "ROUTER_K": "3", "ROUTER_AGREEMENT": "1.0",
        "ROUTER_APPROVAL_USD": "0.001"}

Die Genehmigung gilt pro Eskalation – der escalate-Knoten löscht sie beim Durchlaufen –, sodass eine Leiter mit drei Sprossen zweimal fragt und ein Client fortsetzen muss, bis stop_reason etwas anderes ist. Der Checkpoint ist ein InMemorySaver, der im Serverprozess lebt, also müssen beide Aufrufe denselben laufenden Server erreichen: Ein Client, der pro Aufruf einen neuen startet, einschließlich scripts/mcp_call.py, kann nie fortsetzen, was der vorherige pausiert hat. Eine thread_id, die nicht mehr existiert, kommt als no_suspended_run zurück, statt als KeyError aus LangGraph.

Die gesamte Oberfläche auf einmal sehen

python scripts/demo_mcp.py

Ein skriptbasierter Durchgang durch den Server über eine echte stdio-Client-Sitzung – was er bewirbt und welche seiner eigenen Aufrufe Geld ausgeben, eine Ressource, der Leiter-Umschwung, eine kostenlose Projektion, eine geroutete Antwort und die Genehmigungsschleife, die auf beide Arten beantwortet wird. Kein Schlüssel, keine Ausgaben; am Ende wird ausgegeben, was die Abfragen in Produktion gekostet hätten, gegen das, was tatsächlich vom Konto abging.

Es startet zwei Server, und der Grund ist der Sinn von ROUTER_K: Selbstkonsistenz-Stichproben werden pro Stichprobenindex gecacht, also wird k beim Start auf das festgelegt, unter dem die Antworten gekauft wurden – k=3 für die Abfrage, die auf der günstigen Sprosse verifiziert, k=4 für die, deren vierte Ziehung abweicht und die Eskalation auslöst. demo.py zeigt, was der Router tut; das hier zeigt, was der Server tut.

Vom Terminal aus steuern

scripts/mcp_call.py ist ein Einmal-MCP-Client – er startet den Server, macht den Handshake, ruft ein Tool auf und gibt das Ergebnis aus:

python scripts/mcp_call.py --list
python scripts/mcp_call.py explain_routing ladder=wide
python scripts/mcp_call.py --resource routing://findings/probe

JSON-RPC von Hand hineinzupipen funktioniert nicht, und der Fehler ist still: Der Server interpretiert stdin-EOF als Herunterfahren und beendet sich, ohne seine Warteschlange zu leeren, also gibt echo '...' | python -m router_agent.mcp_server die initialize-Antwort aus, verwirft den Tool-Aufruf und beendet sich mit 0. Ein Client hält die Pipe offen.

Um echtes Geld auszugeben, benenne den Modus – das ist ein echter DeepSeek-Aufruf, geroutet und über MCP bepreist:

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

Drei HTTP-Aufrufe – eine gierige Antwort und zwei weitere, um sie gegen sich selbst zu prüfen – einstimmig auf der günstigen Sprosse akzeptiert, also wurde v4-pro nie berührt. Führe es ein zweites Mal aus, und backend_cost_usd ist $0,00, während cost_usd unverändert ist: Die Antworten wurden auf dem Weg nach draußen gecacht, was derselbe Mechanismus ist, der es dem Benchmark erlaubt, 5.075 davon kostenlos abzuspielen. Die beiden Zahlen sind absichtlich getrennt – eine ist, was das Bedienen in Produktion kostet, die andere, was vom Konto abging.

Was eine bediente Abfrage kauft, landet in cache/serving.<ladder>.jsonl, nicht im cache/raw_calls.<ladder>.jsonl des Benchmarks. Beide enthalten echte bezahlte Antworten, aber nur eine ist Beleg: Die Datei des Benchmarks ist die geschlossene Menge, aus der jede veröffentlichte Tabelle berechnet wird, und wenn eine beliebige Abfrage daran anhängen würde, würde das die Antwortanzahl und die unten genannten Gesamtausgaben verschieben. Das Bedienen liest weiterhin den Benchmark-Cache, was --demo kostenlos macht.

Verifizieren

python scripts/check_mcp_server.py

Zwei Phasen, und die zweite ist die, die zählt. Sie listet und ruft die Tools prozessintern auf, startet dann den Server als Unterprozess und spricht per Hand JSON-RPC mit ihm — denn auf stdio ist stdout das Protokoll, und ein einzelnes verirrtes print unter einem Tool korrumpiert den Frame, während jeder In-Process-Test weiterhin besteht. Das ist nicht hypothetisch: response_cache warnte auf stdout vor veralteten Schlüsseln, auf einem Codepfad, den nur route_query erreicht, sodass der Server seine Tools perfekt auflistete und dann auf den ersten echten Aufruf eine verstümmelte Antwort zurückgab.

Alles reproduzieren

Der Replay-Modus führt die veröffentlichte Analyse erneut gegen die committeten Antworten aus — kein Schlüssel, kein Netzwerk, 0,00 $:

ROUTER_MODE=replay python scripts/run_all_ladders.py --ladders wide   # ~30 min

Lassen Sie --ladders wide für alle drei weg, etwa 75 Minuten. Die veröffentlichten Zahlen wurden genau so erzeugt, nachdem jedes abgeleitete Artefakt gelöscht wurde: 0 Aufrufe erreichten ein Backend, 0 Zeilen sind simuliert, und jede neu erzeugte Datei kam byte-identisch zur committeten zurück.

Replay benötigt überhaupt nichts installiertes — reine Standardbibliothek, offline, byte-deterministisch bis auf die Zahlen — und es ist der Standard, daher nennt keiner der obigen Befehle einen Modus. Der echte Modus benötigt einen Schlüssel und kostet Geld. Es gibt einen dritten Modus, mock, der Antworten für die Testsuite erfindet und in dem jedes Analysemodul sich weigert zu laufen. Alle drei, plus jeden Analyse-Einstiegspunkt und die Reihenfolge, in der Daten gekauft werden, finden Sie in docs/METHOD.md.

Dokumentation

Datei

lesen Sie es, wenn

docs/EXPLAINED.md

Sie die Version in einfacher Sprache möchten, keine Vertrautheit mit Routing vorausgesetzt — hier beginnen

docs/RESULTS.md

Sie alle Ergebnisse möchten, mit den Zahlen und was sie kosten

docs/METHOD.md

Sie die Methode möchten: Aufgabensatz, warum diese Datensätze, Ladders, Richtlinien, Verifier, das Degradationsexperiment, wie man es wirklich ausführt, und die Fehler, die dieses Projekt in sich selbst gefunden hat

docs/ARCHITECTURE.md

Sie wissen möchten, wie Benchmark und Serving-Schicht Modul für Modul zusammenpassen

docs/LIMITATIONS.md

Sie möchten, was die Behauptungen begrenzt

Was die Behauptungen begrenzt, und was hier neu ist

Auf der Titelseite statt vergraben angegeben: Der Verifier, der das Signal erzeugt, ist nicht der Verifier, der ausgeliefert wird. Die Code-Hälfte wird bewertet, indem die Tests ausgeführt werden, die MBPP+ liefert, und ein bereitgestellter Router hat sie nicht.

Diese Lücke wird bepreist statt nur angemerkt, und das Bepreisen ist es, was dieses Repository zur Literatur beiträgt. FrugalGPT (2305.05176) ist die Kaskaden-Baseline, und es und AutoMix nehmen ihren Verifier als gegeben an; Dekoninck et al. (2410.10347) identifizieren die Genauigkeit des Qualitätsschätzers als den Faktor, der entscheidet, ob irgendetwas davon funktioniert, testen dies jedoch durch Injizieren von synthetischem Rauschen. Hier degradiert sweep_degraded.py stattdessen einen echten Verifier um einen kontrollierten Betrag bei objektiv bewerteten Aufgaben, wobei Domäne, Modelle, Prompts und Bewerter festgehalten werden — sodass das Ausliefern eines Proxy-Verifiers eine Bewegung entlang einer gemessenen Kurve ist, kein Schritt ins Unbekannte.

Jede andere Begrenzung wird einmal in docs/LIMITATIONS.md mit dem angegeben, was sie klären würde, und die vollständige Bibliographie befindet sich in docs/METHOD.md.

Lizenz

MIT — siehe 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