
Commit message и миграция платёжного сервиса сегодня часто отправляются одной модели. Отличается только Prompt. Это удобно, пока команда не замечает, что за объяснение ошибки платит как за архитектурный анализ, а сложный рефакторинг иногда уходит в дешёвый Fallback без предупреждения.

Программа Яндекса 75/75/75 задаёт амбициозную цель: к концу 2026 года AI регулярно используют не менее 75% разработчиков, он участвует минимум в 75% изменений и генерирует не менее 75% кода внутри таких изменений. Независимо от отношения к этой метрике, масштаб меняет экономику. Когда AI coding становится повседневной инфраструктурой, модель нужно выбирать не вручную в каждом IDE, а по правилам.
Соберём небольшой Router, который различает рутинные, инженерные и архитектурные задачи, учитывает контекст, бюджет и риск, а при сбое не меняет качество молча.
Сначала определим не модели, а классы работы
Профиль | Задачи | Требования |
FAST | Commit message, объяснение ошибки, поиск символа, короткая документация | Низкая задержка, низкая цена, небольшой контекст |
BUILD | Unit-тесты, локальная функция, типизация, небольшой рефакторинг | Хороший код, Tool Calling, средний контекст |
DEEP | Архитектурный анализ, сложная миграция, межсервисный баг | Reasoning, большой контекст, устойчивость на длинной задаче |
REVIEW | Security Review, критичный Diff, поиск нарушения инвариантов | Независимый контекст, другая модель или семейство |
Названия моделей вынесем в конфигурацию. Приложение должно просить профиль BUILD, а не знать, какой релиз провайдера сегодня закреплён за ним.
Архитектура в пяти компонентах
1. Classifier определяет профиль задачи.
2. Policy учитывает риск, данные и максимальную стоимость.
3. Registry хранит модели, возможности и допустимые Fallback.
4. Gateway client отправляет OpenAI-compatible запрос.
5. Telemetry записывает выбор, Token, задержку и результат Eval.
Для первой версии не нужен отдельный ML-классификатор. Явный параметр от клиента и несколько правил прозрачнее модели, которая тратит вызов только на выбор другой модели.
Конфигурация профилей
profiles:
fast:
primary: fast_model
fallback: fast_model_backup
max_input_tokens: 16000
max_cost_usd: 0.02
build:
primary: coding_model
fallback: coding_model_backup
max_input_tokens: 64000
max_cost_usd: 0.15
deep:
primary: reasoning_model
fallback: reasoning_model_backup
max_input_tokens: 200000
max_cost_usd: 1.20
review:
primary: independent_review_model
max_cost_usd: 0.40Здесь используются алиасы. Реальные Model ID и цены меняются без изменений клиентского кода. Главное — не назначать один и тот же Fallback всем профилям.
Простой классификатор
from enum import StrEnum
class Profile(StrEnum):
FAST = "fast"
BUILD = "build"
DEEP = "deep"
REVIEW = "review"
def choose_profile(task: str, files: int, context_tokens: int,
production: bool, security_review: bool) -> Profile:
if security_review:
return Profile.REVIEW
if production or files > 12 or context_tokens > 60_000:
return Profile.DEEP
if task in {"commit_message", "explain_error", "write_docstring"}:
return Profile.FAST
return Profile.BUILDЭто не «умный» Router, зато его решение легко объяснить. Позже правила можно дополнить историческими данными: какой профиль успешнее проходил тесты, сколько требовал повторов и сколько стоил принятый Diff.
Вызов через единый OpenAI-compatible API
from openai import OpenAI
client = OpenAI(
base_url=os.environ["LLM_BASE_URL"],
api_key=os.environ["LLM_API_KEY"],
)
def run_task(profile, messages, request_id):
model = registry[profile]["primary"]
return client.chat.completions.create(
model=model,
messages=messages,
temperature=0.1,
extra_headers={
"X-Request-ID": request_id,
"X-Task-Profile": profile,
},
)OdiRouter.ai и другие OpenAI-compatible gateways позволяют поставить разные модели за таким интерфейсом. Router остаётся в вашем приложении: именно он понимает, что задача относится к production или требует независимого Review.
Fallback без неприятных сюрпризов
Переключаться стоит только при технической ошибке: Timeout, Rate Limit, временная недоступность. Плохой ответ нельзя надёжно определить по HTTP-коду.
TRANSIENT = {408, 429, 500, 502, 503, 504}
def call_with_fallback(profile, messages, request_id):
candidates = registry[profile]["models"]
errors = []
for model in candidates:
try:
return call(model, messages, request_id)
except APIStatusError as exc:
errors.append((model, exc.status_code))
if exc.status_code not in TRANSIENT:
raise
raise AllModelsUnavailable(profile, errors)Для DEEP-профиля резерв должен поддерживать нужный контекст и инструменты. Если такого резерва нет, лучше вернуть контролируемую ошибку, чем незаметно выполнить архитектурную задачу моделью для Commit Message.
Бюджет считаем до и после вызова
До запроса можно оценить верхнюю границу по входному контексту и лимиту ответа. После — записать фактический Usage. Если задача превысила бюджет, есть три честных варианта: сократить контекст, запросить подтверждение или остановиться.
Автоматически переключать задачу на слабую модель только из-за цены опасно: пользователь думает, что получил тот же уровень анализа.
Контекст дороже модели
Router не исправит ситуацию, если в каждый запрос отправляется весь репозиторий. Для профилей полезны разные стратегии:
· FAST получает сообщение об ошибке и ближайший фрагмент;
· BUILD — сигнатуры, связанные файлы, тесты и локальные правила;
· DEEP — карту компонентов, ADR, критические пути и выбранные исходники;
· REVIEW — задачу, Diff, инварианты и тесты, но не оправдания модели-автора.
Последний пункт снижает confirmation bias: Reviewer не должен сначала прочитать уверенное объяснение, почему решение прекрасно.
Маршрутизация после генерации
Для критичных изменений pipeline не заканчивается первым ответом:
BUILD → tests → static analysis → REVIEW → human approval
DEEP → plan approval → implementation → tests → REVIEW → canary
Модель Review ищет нарушение инвариантов, проблемы безопасности, лишние зависимости и отсутствующие тесты. Она не переписывает код автоматически: замечания возвращаются автору или человеку.
Какие события логировать
Поле | Зачем |
request_id, repository, task_type | Связать вызов с задачей и Diff |
selected_profile, model, fallback_reason | Объяснить решение Router |
input/output tokens, cost, latency | Считать экономику целиком |
tests_passed, review_findings | Связать цену с качеством |
accepted_lines, reverted_30d | Не путать генерацию с полезным кодом |
Не оптимизируйте только Token
Дешёвая модель, которая требует три повтора и час Review, может быть дороже сильной. Для каждой категории считайте Cost per Accepted Task:
(стоимость всех вызовов + стоимость Review + стоимость исправлений)
──────────────────────────────────────────────────────────────
число принятых задачЭто превращает выбор модели из вкусового спора в инженерный эксперимент.
Что говорит 75/75/75 — и чего не говорит
По данным Яндекса, 73% его разработчиков уже регулярно используют AI, а более половины нового кода создаётся с помощью генеративных моделей. Компания подчёркивает, что архитектура, контроль качества и ответственность остаются у инженеров.
Процент сгенерированного кода измеряет проникновение инструмента, но не качество системы. Для своей команды добавьте Accepted Task Rate, Rework after 30 days, число найденных Review-дефектов и время восстановления после AI-изменений.
Вывод
Простой LLM Router не обязан предсказывать идеальную модель. Он должен делать выбор явным, ограничивать стоимость, сохранять контракт при Fallback и связывать вызов с результатом разработки.
Commit Message, Unit-тест и архитектурная миграция — не три Prompt для одной модели. Это три класса работы. Как только команда признаёт это в коде, AI coding перестаёт быть набором личных привычек и становится управляемой инженерной системой.