Модели OpenRouter: умный роутинг к Claude Opus для кодинга
Я перепробовал десятки конфигураций, прежде чем понял: проблема не в том, какую модель выбрать, а в том, что выбор вообще приходится делать вручную. Когда работаешь с Claude, OpenAI, Gemini и кучей дешёвых альтернатив одновременно, задача "отправить сложный кодинг-запрос к самой умной модели, а не к первой попавшейся" превращается в рутину, которая съедает время. Именно здесь модели OpenRouter меняют подход - вместо ручного переключения между провайдерами получаешь единую точку входа, где маршрутизация происходит автоматически.
Именно это мы и решали в проекте llm-router. Расскажу про три больших куска работы: перенос настроек агентов в OMP, добавление кастомного эндпоинта с умным роутингом к Opus, и выстраивание системы онбординга аккаунтов с парсером кредов прямо в UI.
Что такое llm-router и зачем он нужен
В основе проекта лежит 9Router — локальный AI-шлюз, который сидит между клиентами (Claude Code, OMP, Codex, Cursor) и множеством LLM-провайдеров. Схема простая:
Claude Code / OMP / Codex / Cursor
↓
http://localhost:20128/v1
↓
9Router
↓
Claude / OpenAI / Gemini / дешёвые моделиКлиент думает, что разговаривает с одним OpenAI-совместимым API. На самом деле 9Router смотрит на задачу, применяет правила роутинга и отправляет запрос туда, куда нужно. Это даёт гибкость: дорогие модели — для сложных задач, дешёвые — для рутины.
Проект живёт в .worktrees/9router-fork — это git worktree от основного репозитория, что позволяет работать с форком параллельно, не засоряя основную ветку.
Перенос агентов и настроек в OMP
Первая большая задача — инициализировать в OMP все настройки: skills, агентов, плагины и MCP-серверы, с которыми работали в Claude Code.
На первый взгляд звучит просто: скопировать конфиги из одного места в другое. На практике оказалось несколько неочевидных моментов.
Проблема 1: несовместимый frontmatter агентов. У нас было 8 активных кастомных агентов в Claude Code. OMP не подхватил их автоматически — у каждого агента был frontmatter в формате, который OMP не понимает. Пришлось конвертировать вручную, приводя к OMP-совместимому формату.
Проблема 2: Telegram skills. Выключенный Telegram всё равно обнаруживался OMP при сканировании. Пришлось явно отключить skills access и configure для Telegram, чтобы они не всплывали в интерфейсе.
Проблема 3: открытый API-ключ в агенте. В одном из агентов лежал открытый ключ — его нельзя было просто скопировать. Ключ вынесли в переменную окружения, агент переписали на чтение из env.
Проблема 4: MCP-серверы. Изначально планировали перенести и MCP-серверы, но в процессе выяснилось, что ими никто не пользуется. Приняли решение полностью исключить MCP из OMP-конфига — убрали ~/.omp/agent/mcp.json и все связанные .env файлы.
В итоге структура получилась такая:
~/.omp/
├── RULES.md # sticky-правила проекта
├── AGENTS.md # карта агентов и путей
├── skills/ # конвертированные skills из Claude Code
├── plugins/ # плагины
└── agents/ # 8 активных агентов (без MCP)
Важный момент — RULES.md сделали sticky-правилом: оно перецепляется к каждому ходу агента, а не тонет в начале контекста. Туда же записали, что прод крутится не из корня репозитория, а из .worktrees/9router-fork — это критично для правильной работы агентов.
Approval mode выставили в write, что даёт агентам право писать файлы без подтверждения каждого шага.
Кастомный эндпоинт с умным роутингом к Opus
Вторая задача — добавить специальный эндпоинт для задач кодинга, где основная модель Claude Opus, а не что попало.
Логика роутинга
Рассматривали три подхода:
-
Обычный Combo — Opus первый, остальные fallback. Уже поддерживается, но это не умный роутинг: пока Opus жив, всё идёт в Opus без разбора задачи.
-
LLM-классификатор перед каждым запросом — отдельная модель анализирует задачу и решает, куда её отправить. Понимает смысл лучше, но добавляет задержку и дополнительный запрос.
-
Гибридный rule-based + весовой роутинг — правила на основе паттернов задачи, без дополнительных запросов к LLM.
Выбрали третий вариант с жёсткими правилами: архитектура, оркестрация, сложный debugging, неоднозначные задачи — только Opus. Если задача сомнительная — тоже Opus. Gemini и более дешёвые модели получают только явно безопасные уровни сложности.
Codex из роутинга исключили полностью.
Реализация эндпоинта
Эндпоинт добавили как отдельный маршрут в 9Router:
// routes/coding.ts
import { Router } from 'express';
import { classifyTask, OPUS_PATTERNS } from '../lib/task-classifier';
const router = Router();
router.post('/v1/coding', async (req, res) => {
const { messages, ...rest } = req.body;
const lastMessage = messages[messages.length - 1]?.content || '';
const taskLevel = classifyTask(lastMessage);
const targetModel = selectModel(taskLevel);
// Проксируем к выбранной модели
return proxyRequest(targetModel, { messages, ...rest }, res);
});
function selectModel(level: 'complex' | 'medium' | 'simple'): string {
switch (level) {
case 'complex':
return 'claude-opus-4-5'; // Всегда Opus для сложного
case 'medium':
return 'claude-sonnet-4-5'; // Sonnet для среднего
case 'simple':
return process.env.CHEAP_MODEL || 'gemini-flash'; // Дешёвая для простого
}
}// lib/task-classifier.ts
export const OPUS_PATTERNS = [
/архитектур/i,
/рефактор/i,
/debug/i,
/оркестрац/i,
/race condition/i,
/concurren/i,
/deadlock/i,
/performance/i,
/оптимизац/i,
];
export function classifyTask(text: string): 'complex' | 'medium' | 'simple' {
// Если хоть один паттерн совпал — это сложная задача
if (OPUS_PATTERNS.some(p => p.test(text))) {
return 'complex';
}
// Длинные запросы тоже считаем сложными
if (text.length > 2000) {
return 'complex';
}
// Короткие вопросы — простые
if (text.length < 200) {
return 'simple';
}
return 'medium';
}Интеграция с OMP
Чтобы OMP использовал новый эндпоинт для задач кодинга, добавили конфиг в ~/.omp/AGENTS.md:
## Coding Agent
- Endpoint: http://localhost:20128/v1/coding
- Модель: авто (Opus для сложного, Sonnet для среднего)
- Использовать для: написание кода, дебаггинг, архитектураТеперь когда OMP запускает coding-агента, запросы идут через /v1/coding, а не через стандартный /v1/chat/completions. Роутер сам решает, какую модель использовать.
Система онбординга аккаунтов
Третья задача выросла из реальной боли: в системе видео-пула Google-аккаунты теряли свои креды. Конкретно — у шестого аккаунта не было сохранённых данных, потому что их скидывали в чат, но не записывали в реестр.
Правило про креды
Первым делом добавили sticky-правило в .omp/RULES.md:
## Правило: учётные данные аккаунтов
При добавлении любого Google-аккаунта в систему — в ту же сессию
записать в реестр:
- email
- password
- TOTP-seed (2FA секрет)
- recoveryEmail
- recoveryEmailPassword
- backupCodes (резервные коды)
Если кредов нет на руках — спросить у пользователя.
Никогда не оставлять поля пустыми молча.Мастер онбординга
Дальше спроектировали UI-мастер для добавления новых аккаунтов. Ключевая идея: пользователь кидает в окно чата дамп с кредами от продавца, сервер парсит его и заполняет карточку аккаунта.
Это решает главную проблему — креды больше не теряются в истории чата.
Архитектура мастера:
Шаг 1: Базовые данные
└── Пользователь вставляет дамп от продавца
└── Сервер парсит email, пароль, TOTP, резервные коды
└── Показывает распознанное для подтверждения
Шаг 2: Автоматические действия (делает агент)
└── Создание профиля браузера
└── Первый вход через noVNC
└── Подключение к Flow/Gemini/Dola/Antigravity
Шаг 3: Ручные действия (делает пользователь)
└── Первый вход в Google (требует живого клика)
└── Подтверждение consent-экрана
Шаг 4: Проверка владения
└── Смена пароля
└── Обновление 2FA
└── Выход со всех устройств
└── Статус: «только у нас» / «не только у нас»
Парсер кредов на сервере:
// lib/creds-parser.ts
interface ParsedCreds {
email?: string;
password?: string;
totpSeed?: string;
recoveryEmail?: string;
recoveryEmailPassword?: string;
backupCodes?: string[];
}
export function parseCreds(raw: string): ParsedCreds {
const result: ParsedCreds = {};
// Email
const emailMatch = raw.match(/([\w.+-]+@[\w-]+\.[\w.]+)/);
if (emailMatch) result.email = emailMatch[1];
// Пароль — ищем после типичных подписей
const passMatch = raw.match(
/(?:пароль|password|pass)[:\s]+([^\s\n]+)/i
);
if (passMatch) result.password = passMatch[1];
// TOTP seed — обычно 32 символа base32
const totpMatch = raw.match(/([A-Z2-7]{32,})/);
if (totpMatch) result.totpSeed = totpMatch[1];
// Резервные коды — обычно 8-значные числа
const backupMatches = raw.match(/\b\d{8}\b/g);
if (backupMatches?.length >= 3) {
result.backupCodes = backupMatches;
}
return result;
}Статус владения аккаунтом
Отдельно реализовали чек-лист владения — в UI видно, «только у нас» аккаунт или нет:
interface OwnershipStatus {
passwordChanged: boolean; // Сменили пароль
twoFaUpdated: boolean; // Обновили 2FA
loggedOutAll: boolean; // Вышли со всех устройств
recoveryEmailOwned: boolean; // Резервная почта наша
}
function isFullyOwned(status: OwnershipStatus): boolean {
return Object.values(status).every(Boolean);
}В карточке аккаунта это отображается как зелёный/красный индикатор с расшифровкой, каких шагов не хватает.
Работа с concurrency и блокерами
В процессе разработки наткнулись на несколько технических блокеров, которые проверяли запуском тестов.
Один из них — проблема конкурентной записи в реестр аккаунтов. Когда два процесса одновременно пишут в один файл реестра, данные могут перезаписать друг друга. Проверяли это на /tmp-копии с двумя параллельными writer-процессами:
# Тест конкурентной записи
for i in 1 2; do
node scripts/write-registry.js --account acc$i &
done
wait
# Проверяем целостность
node scripts/verify-registry.jsРешение — файловая блокировка через proper-lockfile с retry-логикой:
import { lock, unlock } from 'proper-lockfile';
async function writeToRegistry(accountId: string, data: object) {
const registryPath = '/path/to/registry.json';
await lock(registryPath, { retries: 5 });
try {
const registry = JSON.parse(await fs.readFile(registryPath, 'utf8'));
registry[accountId] = { ...registry[accountId], ...data };
await fs.writeFile(registryPath, JSON.stringify(registry, null, 2));
} finally {
await unlock(registryPath);
}
}Файл реестра с кредами получил права 0600 — читает только владелец процесса.
Результаты
После всей этой работы получили:
- 8 агентов перенесены в OMP с правильным frontmatter
- Кастомный эндпоинт
/v1/codingс умным роутингом: сложные задачи → Opus, средние → Sonnet, простые → дешёвая модель - Sticky-правила в
.omp/RULES.md— агент всегда знает контекст проекта - UI-мастер онбординга аккаунтов с парсером кредов
- Чек-лист владения — видно, «только у нас» аккаунт или нет
- Конкурентная запись в реестр защищена файловой блокировкой
- Реестр с кредами имеет права
0600
Выводы и уроки
Главный урок этого проекта — простые задачи не нужно усложнять. Перенос агентов из Claude Code в OMP — это по сути конвертация конфигов. Не нужны подзадачи, ревью-циклы и worktree-изоляция для каждого шага. Иногда лучший процесс — это его отсутствие.
Второй урок — данные должны жить в системе, а не в чате. История переписки — плохое место для хранения паролей и TOTP-секретов. Как только мы выстроили процесс «вставил дамп → сервер распарсил → записал в реестр», проблема потери кредов исчезла сама собой. Sticky-правила в .omp/RULES.md помогают агенту не забывать об этом требовании.
Третий урок — умный роутинг не обязательно означает LLM-классификатор. Добавлять отдельный запрос к модели ради классификации задачи — это overhead. Rule-based подход с паттернами работает достаточно хорошо для большинства случаев и не добавляет задержку. Если задача содержит слова «архитектура», «рефактор», «race condition» — она идёт в Opus. Просто и надёжно.
Четвёртый урок — MCP не нужен, если им не пользуются. Мы потратили время на планирование переноса MCP-серверов, а потом выяснилось, что ими никто не пользуется. Лучше сначала спросить «а это вообще нужно?», чем реализовывать ради галочки.
Пятый урок — безопасность кредов — это не опция. Открытый API-ключ в агенте, права 0777 на реестр, секреты в истории чата — всё это реальные риски. Права 0600 на файл с кредами, вынос секретов в env, парсинг без вывода значений в лог — базовые вещи, которые нужно делать сразу, а не потом.
Проект продолжает развиваться: следующий шаг — добавить метрики роутинга, чтобы видеть, сколько запросов уходит в каждую модель и какова средняя стоимость запроса через /v1/coding по сравнению со стандартным эндпоинтом.
Полезные ссылки
- Claude API документация — официальная документация Anthropic
- OpenAI API Reference — совместимый API, который эмулирует 9Router
- proper-lockfile — файловые блокировки для Node.js
- Git Worktrees — работа с несколькими рабочими деревьями
- Gemini API — документация Google Gemini, один из провайдеров в роутере

AI-инженер, предприниматель, маркетолог. Основатель feberra.com и x10seo.ru. 13 лет в перфоманс-маркетинге, 3 года в системной интеграции AI в бизнес.