llm-routing
LLM-маршрутизация: измеренный бенчмарк и маршрутизатор, который он обосновывает
Сервис LLM-маршрутизации с учётом стоимости (LangGraph + MCP) и бенчмарк из 417 задач, который определяет его политику.
Отвечайте на самой дешёвой модели, которая может быть проверена как правильно ответившая, и повышайте уровень только при неудачной проверке. Превосходит ли это простую оплату лучшей модели — не вопрос мнения: это зависит от моделей, между которыми вы выбираете, и этот репозиторий измеряет это на трёх реальных лестницах.
Вывод в одном предложении: каскадируйте, когда верхняя ступень действительно лучше, а проверка дёшева — никакой порог ценового соотношения не подходит для всех трёх лестниц. Поставляемый маршрутизатор вычисляет этот вердикт для каждой лестницы на основе зафиксированных измерений и отказывается отвечать для лестницы, по которой у него нет данных.
Что работает | Конечный автомат LangGraph — |
Что определяет его политику | 417 задач (код MBPP+, уровень 5 MATH-500), 9 политик, 3 ценовые лестницы, все измерены на реальных моделях: границы стоимость–точность, точный тест Макнемара, парный бутстрэп. |
Создано с помощью | Python 3.10–3.13 · LangGraph · MCP · API Anthropic + DeepSeek · pytest (268 тестов) · GitHub Actions. Исследовательское ядро — чистая стандартная библиотека — никакая зависимость не может изменить число в бенчмарке. |
Доказательства | 5 075 реальных ответов моделей, зафиксированы. $8.51 потрачено. Каждый график и таблица регенерируются офлайн, без API-ключа, за $0.00. |
Быстрый старт
pip install -e ".[agent]"
python -m llm_routing.build_taskset
python -m router_agent.cli --demo # real model output, no API key, $0.00Никакого аккаунта, ключа и денег: ответы были куплены один раз и зафиксированы, поэтому маршрутизатор воспроизводит реальные выходные данные моделей, а не симулирует их.
python scripts/demo.py выводит три канонических трассировки — каскад, выигрывающий на дешёвой ступени, каскад, платящий дважды, и случай с кодом, где проверка точна и бесплатна. Первая из них:
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Эти четыре строки — прогулка по графу ниже, который разбирается из router_agent/graph.py, а не рисуется — ребро эскалации зацикливается обратно в answer, и именно этот цикл делает это каскадом, а не маршрутизатором.
Три независимых выборки из DeepSeek дали правильный ответ, поэтому каскад принял его и никогда не вызывал Opus 5 — примерно в 27 раз дешевле, чем маршрутизация прямо на верхнюю ступень. Когда проверка не удаётся, каскад повышает уровень и платит за обе ступени. Стоит ли такая сделка того — это то, что измеряет остальная часть этого репозитория.
Вывод
Предварительно зарегистрированное сравнение с простой оплатой лучшей модели. Точный тест Макнемара по парным результатам, n=209 отложенных задач на лестницу.
лестница | ступени | каскад | всегда-дорогая | Δ точность | p | Δ стоимость/задача |
| 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 |
На wide каскад точнее и в четыре раза дешевле. На claude он покупает точность по премиальной цене — проверка не бесплатна, когда дешёвая ступень — Haiku, а математическая половина берёт из неё пять выборок. Лестница определяет знак, поэтому маршрутизатор ниже читает её, а не предполагает.
Три дополнительных результата, каждый со своими числами и оговорками в docs/RESULTS.md:
Прогнозирующая маршрутизация не превосходит подбрасывание монеты — шесть сравнений из шести. Ни LLM-как-маршрутизатор, ни предобученный BERT от RouteLLM не превосходят случайный нуль с сопоставимой стоимостью ни на одной лестнице, в то время как каскад превосходит оба на каждой. Различие — когда принимается решение: прогнозирующий маршрутизатор обязуется до попытки, каскад решает после проверки. → шесть сравнений и AUC границы за ними
Точность скрывает, что на самом деле сделал маршрутизатор. Две политики могут достичь одинаковой точности, повышая уровень для правильных десяти задач или повышая для всего.
always_expensiveповышает уровень для 201 задачи, чтобы купить 27 спасений, сжигая $0.71 на эскалациях, которые не могли улучшить ответ;cascadeполучает 24 из этих спасений и тратит $0.084. → таблица результатов по политикамКаждая политика — это кривая, а не точка. У каждого маршрутизатора здесь есть ручка, обменивающая точность на деньги, поэтому сравнение двух при одной настройке каждой позволяет тому, кто установил ручки, выбрать победителя.
frontier.pyпроходит по каждой ручке по всему диапазону и сравнивает полученные кривые. → границы и почему ценовое соотношение не решает
У каждого из них есть рисунок в figures/, где перечислено, что утверждает каждый график и из какого артефакта в runs/ он взят.
Бенчмарк поставляет собственный вывод
Бенчмарк, заканчивающийся таблицей, оставляет читателю применение. Этот заканчивается функцией. findings.ratio_verdict(ladder) читает зафиксированную границу этой лестницы и возвращает вердикт для неё. Тот же запрос, две лестницы, противоположные ответы:
$ 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%Этот переворот и есть вывод, и маршрутизатор читает его, а не предполагает — и отказывается для лестницы, по которой у него нет данных. CLI, инструмент MCP explain_routing и значения по умолчанию RouterConfig вызывают одну и ту же функцию, поэтому изменение того, что измерил бенчмарк, меняет то, что рекомендует маршрутизатор. Нет константы, которая могла бы устареть — раньше она была, и два из трёх её вердиктов были обратными.
Структура
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/Две половины используют один клиент моделей, одну таблицу цен и один кэш ответов, что делает долларовую цифру от маршрутизатора эквивалентной долларовой цифре в таблицах. Стрелка идёт в одну сторону — router_agent импортирует llm_routing, никогда наоборот — и в CI есть задача, единственная цель которой — сохранять это. Модуль за модулем: docs/ARCHITECTURE.md.
Использование из MCP-клиента
Маршрутизатор — это MCP-сервер: пять инструментов (route_query, resume_routing, estimate_cost, compare_policies, explain_routing), четыре ресурса только для чтения под routing:// и один промпт, который проводит клиента через выбор политики.
Файл .mcp.json зафиксирован, поэтому Claude Code подхватывает сервер после pip install -e ".[agent,mcp]" и ничего больше. Для Claude Desktop или любого другого клиента тот же блок регистрирует его вручную:
{
"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 — безопасная регистрация: сервер отвечает из зафиксированных ответов и не может тратить деньги, ценой обслуживания только тех промптов, которые действительно были оплачены — всё остальное возвращается как структурированный no_cached_response, а не сфабрикованный ответ. ROUTER_K=3 закреплён, чтобы соответствовать параметрам, под которые были куплены эти ответы; значение по умолчанию 5 запросило бы из кэша выборки, которые никто не покупал. ROUTER_MODE=real с ключом обслуживает произвольные запросы и выставляет счета.
Одобрение эскалации
По умолчанию не задано. Добавьте ROUTER_APPROVAL_USD, и эскалация, прогнозируемая дороже, приостанавливает граф вместо трат: route_query возвращает stop_reason: awaiting_approval с thread_id и прерванной полезной нагрузкой, называющей модель и цену, а resume_routing возвращает ответ человека.
"env": {"ROUTER_LADDER": "wide", "ROUTER_MODE": "replay",
"ROUTER_K": "3", "ROUTER_AGREEMENT": "1.0",
"ROUTER_APPROVAL_USD": "0.001"}Одобрение — на каждую эскалацию — узел escalate очищает его на пути — поэтому лестница из трёх ступеней спрашивает дважды, и клиенту приходится возобновлять, пока stop_reason не станет чем-то другим. Контрольная точка — это InMemorySaver, живущий в процессе сервера, поэтому оба вызова должны достичь одного и того же работающего сервера: клиент, который порождает по одному на вызов, включая scripts/mcp_call.py, никогда не сможет возобновить то, что приостановил предыдущий. thread_id, который больше не существует, возвращается как no_suspended_run, а не KeyError изнутри LangGraph.
Просмотр всей поверхности сразу
python scripts/demo_mcp.pyСкриптовое прохождение сервера через реальную stdio-сессию клиента — что он рекламирует и какие из его собственных вызовов тратят, ресурс, переворот лестницы, бесплатная проекция, маршрутизированный ответ и цикл одобрения, отвеченный обоими способами. Без ключа, без трат; в конце печатается, сколько запросы стоили бы в продакшене против того, что реально ушло с аккаунта.
Он запускает два сервера, и причина — суть ROUTER_K: выборки самосогласованности кэшируются по индексу выборки, поэтому k закрепляется при запуске под то, под что были куплены ответы — k=3 для запроса, который проверяется на дешёвой ступени, k=4 для того, чья четвёртая выборка не согласна и запускает эскалацию. demo.py показывает, что делает маршрутизатор; это показывает, что делает сервер.
Управление из терминала
scripts/mcp_call.py — одноразовый MCP-клиент: он запускает сервер, выполняет рукопожатие, вызывает один инструмент и печатает результат:
python scripts/mcp_call.py --listpython scripts/mcp_call.py explain_routing ladder=widepython scripts/mcp_call.py --resource routing://findings/probeРучной ввод JSON-RPC не работает, и сбой тихий: сервер принимает EOF на stdin как завершение и выходит, не опустошая очередь, поэтому echo '...' | python -m router_agent.mcp_server печатает ответ на initialize, теряет вызов инструмента и выходит с кодом 0. Клиент держит канал открытым.
Чтобы потратить реальные деньги, назовите режим — это настоящий вызов DeepSeek, маршрутизированный и оценённый через 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Три HTTP-вызова — один жадный ответ и два дополнительных для проверки его против самого себя — приняты единогласно на дешёвой ступени, поэтому v4-pro никогда не был затронут. Запустите его второй раз, и backend_cost_usd будет $0.00, а cost_usd не изменится: ответы были кэшированы на выходе, что является тем же механизмом, который позволяет бенчмарку бесплатно воспроизводить 5 075 из них. Две цифры разделены намеренно — одна — это стоимость обслуживания в продакшене, другая — что ушло с аккаунта.
Что покупает обслуживаемый запрос, попадает в cache/serving.<ladder>.jsonl, а не в cache/raw_calls.<ladder>.jsonl бенчмарка. Оба содержат реальные оплаченные ответы, но только один является доказательством: файл бенчмарка — это закрытый набор, из которого вычисляется каждая опубликованная таблица, и разрешение произвольному запросу добавлять в него сдвинуло бы количество ответов и общую сумму расходов, указанную ниже. Обслуживание по-прежнему читает кэш бенчмарка, что делает --demo бесплатным.
Проверка
python scripts/check_mcp_server.pyДве фазы, и вторая — та, что имеет значение. Она перечисляет и вызывает инструменты в процессе, затем запускает сервер как подпроцесс и вручную общается с ним по JSON-RPC — потому что на stdio stdout — это протокол, и один случайный print под инструментом портит кадр, при том что все внутрипроцессные тесты по-прежнему проходят. Это не гипотеза: response_cache предупреждал об устаревших ключах в stdout на пути кода, которого достигает только route_query, так что сервер безупречно перечислил свои инструменты, а затем вернул искажённый ответ на первый реальный вызов.
Воспроизведение всего
Режим воспроизведения повторно запускает опубликованный анализ на зафиксированных ответах — без ключа, без сети, $0.00:
ROUTER_MODE=replay python scripts/run_all_ladders.py --ladders wide # ~30 minУберите --ladders wide для всех трёх — около 75 минут. Опубликованные цифры были получены именно так после удаления всех производных артефактов: 0 вызовов достигли бэкенда, 0 строк смоделированы, и каждый перегенерированный файл вернулся побайтово идентичным зафиксированному.
Для воспроизведения не нужно ничего устанавливать — чистая стандартная библиотека, офлайн, побайтово детерминированно вплоть до цифр — и это режим по умолчанию, поэтому ни одна из команд выше не называет режим. Реальному режиму нужен ключ, и он тратит деньги. Есть третий режим, mock, который фабрикует ответы для тестового набора и в котором отказывается работать каждый модуль анализа. Все три, плюс каждая точка входа анализа и порядок покупки данных — в docs/METHOD.md.
Документация
файл | читайте, когда |
нужна версия простым языком, без предположения о знакомстве с маршрутизацией — начните здесь | |
нужны все выводы, с цифрами и их стоимостью | |
нужен метод: набор задач, почему эти наборы данных, лестницы, политики, верификаторы, эксперимент с деградацией, как запустить по-настоящему и баги, которые этот проект нашёл в себе | |
нужно знать, как бенчмарк и серверный слой сочетаются, модуль за модулем | |
нужно знать, что ограничивает утверждения |
Что ограничивает утверждения и что здесь нового
Заявлено на первой странице, а не спрятано: верификатор, который производит сигнал, — не тот верификатор, который поставляется. Кодовая половина оценивается выполнением тестов, которые поставляет MBPP+, а у развёрнутого маршрутизатора их нет.
Этот разрыв оценён, а не просто отмечен, и именно его оценка — то, что этот репозиторий добавляет к литературе. FrugalGPT (2305.05176) — каскадный базовый уровень, и он, и AutoMix принимают свой верификатор как данность; Dekoninck и др. (2410.10347) называют точность оценщика качества фактором, определяющим, работает ли всё это, но проверяют его, внедряя синтетический шум. Здесь sweep_degraded.py вместо этого деградирует реальный верификатор на контролируемую величину на объективно оцениваемых задачах, удерживая фиксированными домен, модели, промпты и оценщик — так что поставка прокси-верификатора — это движение по измеренной кривой, а не шаг в неизвестность.
Каждая другая граница заявлена один раз в docs/LIMITATIONS.md вместе с тем, что могло бы её снять, а полная библиография — в docs/METHOD.md.
Лицензия
MIT — см. 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