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 раз дешевле, чем маршрутизация прямо на верхнюю ступень. Когда проверка не удаётся, каскад повышает уровень и платит за обе ступени. Стоит ли такая сделка того — это то, что измеряет остальная часть этого репозитория.
Related MCP server: whichmodel-mcp
Вывод
Предварительно зарегистрированное сравнение с простой оплатой лучшей модели. Точный тест Макнемара по парным результатам, 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 deployed
Maintenance
Related MCP Connectors
Hosted MCP server for LLM cost estimation, model comparison, and budget-aware routing.
Read-only AI/LLM cost tools with current MCP discovery and attributable Registry transport.
Find, vet, and run MCP tools through a secure audited gateway with prompt-injection risk scoring
Zero-secret MCP gateway for AI agents: risk-scored, audited calls with human-in-the-loop approval.
Related MCP Servers
- AlicenseNot gradedqualityFmaintenanceLLM routing proxy that cuts API costs 60-90% by auto-selecting the cheapest capable model across OpenAI, Anthropic, and Google. Provides stats, config, and model comparison tools via MCP.21 npm1MIT
- AlicenseNot gradedqualityDmaintenanceA model routing advisor for autonomous agents — get cost-optimised LLM recommendations via MCP.8 npmMIT
- AlicenseNot gradedqualityDmaintenanceMCP server that experiments with cheaper LLM policies (model, prompt, reasoning, retry, escalation) against project tasks to recommend the lowest-cost policy that meets quality constraints.MIT
- AlicenseAqualityDmaintenanceEnables policy-governed MCP interactions with deterministic authorization, tenant isolation, minimized PII exposure, and human approval gates for sensitive mutations, while producing structured audit events.3MIT