llm-routing
LLM-Routing: ein gemessenes Benchmark und der Router, für den es argumentiert
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 – |
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.00Kein 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.000000Diese 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.
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 |
| 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 |
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_expensiveeskaliert 201 Aufgaben, um 27 Rettungen zu kaufen, und verbrennt $0,71 für Eskalationen, die die Antwort nicht verbessern konnten;cascadebekommt 24 dieser Rettungen und verschwendet $0,084. → die Ergebnisliste pro RichtlinieJede 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.pydurchlä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.pyEin 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 --listpython scripts/mcp_call.py explain_routing ladder=widepython scripts/mcp_call.py --resource routing://findings/probeJSON-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.00Drei 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.pyZwei 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 minLassen 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 |
Sie die Version in einfacher Sprache möchten, keine Vertrautheit mit Routing vorausgesetzt — hier beginnen | |
Sie alle Ergebnisse möchten, mit den Zahlen und was sie kosten | |
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 | |
Sie wissen möchten, wie Benchmark und Serving-Schicht Modul für Modul zusammenpassen | |
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.
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