Быстрая модель в моей колонке: от Jev до локального маршрутизатора
Я хотел спрашивать колонку о погоде обычными словами и сразу получать ответ. Облачный Jev помог найти подход, а затем я обучил локальную модель для домашнего сервера.
Колонка без заученных команд
Когда я увидел Jev, меня заинтересовал простой вопрос: для чего нужна модель, которая сама не отвечает пользователю длинным текстом? У меня как раз была задача. Из старого Android-телефона я собирал собственную умную колонку: микрофон, динамик, экран и Wi-Fi уже есть, оставалось сделать поведение, которое меня устраивает. Основную логику мы разрабатывали вместе с Claude Code на Claude Opus 5.5 — под «мы» дальше я имею в виду себя и ИИ-помощника.
Я хотел спросить «Какая погода?», «Что там на улице?» или «Дождь будет?» и получить один прогноз. Без списка обязательных команд и без ожидания, пока большой агент разберётся с очевидной просьбой. Большой агент нужен для сложного разговора, а запрос прогноза должен проходить коротким путём. Jev оказался полезен именно между распознанной фразой и выбором следующего действия.
Сначала идея сработала, но облачная скорость быстро подвела. За пару часов использования задержки стали непредсказуемыми. В итоге быстрый путь пришлось перестраивать под модель, которую можно запускать дома. Это история не о рекордных миллисекундах, а о том, как найти для модели достаточно узкую роль и проверить её в настоящем устройстве.
Что именно выбирает модель
Jev получает фразу и заранее перечисленные варианты: погода, музыка, обычный разговор. Он выбирает один из них. Прогноз он не ищет, плеер не запускает и голосом не отвечает. После выбора обычный код вызывает погодный сервис или передаёт вопрос большому агенту, а отдельный компонент озвучивает результат. Разработчик TypeSafe называет такой класс System One и описывает его как модель для структурированных решений, в том числе выбора, оценки и ответа «да/нет».
Почему не написать три условия в коде? Если команда строго одна — это было бы проще и надёжнее. Но живую речь быстро не уместить в список ключевых слов. После прогноза я могу спросить «А завтра?»: слово «погода» исчезло, смысл остался в контексте. Фраза «Почему у меня не включается музыка?» содержит слово «музыка», но просить запустить плеер я не собирался. Добавлять новое исключение на каждую фразу можно долго, а потом чинить их пересечения.
Здесь у модели маленький смысловой шаг: принять свободную фразу и выбрать один из разрешённых маршрутов. Выполнение, доступ к сервисам и подтверждения важных действий остаются в программе и агенте. Когда вход строго определён, я бы выбрал код. Когда нужно придумать новый ответ, нужна большая модель. Между ними есть повторяющиеся развилки, где такой маршрутизатор пригодится.
Первый быстрый путь — и его предел
Сначала разговор в колонке шёл через GPT Live и моего агента OpenClaw. Возможность перебить голосовой ответ и уточнить вопрос мне нравилась. Но запрос о погоде проходил слишком длинный маршрут. Я поставил Jev перед агентом: он узнавал погодную фразу, код сразу получал прогноз и отправлял его на озвучку. Первая версия умела только этот маршрут; если вопрос был другим, приходилось возвращаться в голосовой разговор и повторять его. Позже мы сделали прямой путь к агенту и добавили команды.
Начальный выбор Jev занимал меньше секунды. Уже в тот вечер ответы растягивались на две-три секунды и дольше. В одном ночном прогоне из 414 последовательных запросов 179 заняли более трёх секунд. Причину я не установил: возможная перегрузка сервиса осталась моей догадкой. Но компонент, добавленный ради скорости, сам стал источником ожидания. За каждый его вызов я также платил через API. Сумма была небольшой, однако колонка работает ежедневно, и зависеть от облачной задержки ради такого выбора не хотелось.
Laya на домашнем сервере: сначала точность, затем скорость
Я попробовал Laya — модель со схожей задачей, чьи веса можно скачать. Домашний сервер у меня скромный: Intel N100, 16 гигабайт памяти, без видеокарты. На нём уже работают другие сервисы. Первые сто подготовленных для колонки фраз исходная Laya поняла плохо: правильный маршрут был только в 24 случаях. Часто она отправляла всё в «другое». Для русского разговора с моей колонкой это не годилось.
Мы собрали размеченные примеры погоды, музыки и обычного разговора, часть написали вручную, часть получили из шаблонов. Отдельные фразы оставили для проверки. Первый раунд дообучения на моей RTX 5070 Ti занял около сорока минут; результат на проверочном наборе вырос до 87 из 100. После следующей настройки вышло 89 из 100. Jev на тех же ста фразах дал 90. Это наш небольшой подготовленный набор, а не доказательство равной надёжности на любой живой речи. Более поздние исправления после просмотра ошибок тоже уже нельзя считать чистой независимой проверкой.
На видеокарте модель выбирала маршрут за десятки миллисекунд. На N100 основной выбор занял почти секунду, а серия уточняющих вопросов — больше шести. Простое сжатие большого варианта сократило требования, но снизило число верных ответов на нашем наборе с 89 до 80. Для устройства, которое может принять разговор за команду, такой обмен мне не подошёл.
Как мы ускорили локальное решение
Сначала сократили описания вариантов до коротких меток и доучили модель под них. Вход стал примерно втрое короче; на N100 первый выбор без истории разговора сократился до нескольких десятых секунды. Потом перестали задавать все дополнительные вопросы сразу. Если выбран прогноз, незачем параллельно выяснять, какую песню включить. Эти изменения не требуют «волшебной» новой модели — мы просто перестали тратить её время на лишнюю работу.
Живой разговор вернул сложность. Для вопроса «А завтра?» нужна часть предыдущих реплик, а длинный контекст снова замедлял большую Laya на CPU. Тогда мы взяли опубликованную mmBERT-small и обучили её на решениях большой модели: давали им одинаковые фразы, маленькую учили повторять выбор и оценки большой. Затем отдельно доучивали на ошибках и перевели в восьмибитный формат. Это не механическое уменьшение Laya; у компактной версии другая основа, которую мы обучили под свою задачу.
Получился файл около 147 мегабайт вместо примерно 1,3 гигабайта у большой версии. На моём N100 выбор маршрута без истории занимал меньше десятой доли секунды, с несколькими прежними репликами — около четырёх десятых. Я проверял и побочный эффект: во время запросов к колонке Jellyfin на том же сервере продолжал работать как обычно. Скорость самого выбора не равна времени от конца моей фразы до первого звука ответа — в цепочке есть распознавание речи, сервисы и озвучка.
Живая гонка и границы эксперимента
Для сравнения колонка отправляла одну фразу одновременно Jev и локальной модели и брала маршрут от той, что ответила первой. В ранней живой беседе Jev выиграл девять раз из десяти. После оптимизации локальная модель стала приходить первой чаще: в журнале за время эксперимента она выиграла 52 из 75 запросов. Выборка небольшая и зависит от конкретной домашней сети и сервера, зато это уже не отдельный замер на мощной видеокарте.
Проверять пришлось не только задержку. Ранняя версия могла принять просьбу выключить музыку за команду включить её. Слово «запусти» иногда делало командой даже «запусти посудомойку». Мы добавляли такие фразы в проверки. Подключить новое действие — значит пересмотреть и старые границы: модель не должна решать, что любой похожий текст даёт ей право что-то выполнить.
Во время подготовки сценария локальная модель уже работала у меня дома, а гонку с Jev я сохранял ради дальнейшего сравнения. Поэтому плата за его вызовы тогда оставалась. После отключения гонки маршрутизация сможет выполняться локально без этой платы и без ожидания Jev. Если делать похожую систему для поддержки или домашней автоматизации, я бы начал со списка разрешённых действий, реальных пользовательских фраз и безопасного пути для непонятного запроса. Затем измерил бы всю цепочку, а не только один вызов модели.