Бенч 7 LLM на своих данных за вечер: как выбрать модель для прод-парсера
Чтобы выбрать LLM под прод-задачу, прогони 5-7 моделей через OpenRouter на 10 своих реальных примерах тем же промптом, что стоит в проде, и сверь руками. У меня за вечер победила gpt-5.6-luna (контакты 10/10, 4.6 с/вызов), а в fallback встала открытая gpt-oss-120b - страховка на случай, если провайдер выпилит модель.
Как за вечер выбрать LLM под прод-задачу: бенч 7 моделей через OpenRouter на 10 своих вакансиях, ручная сверка, грабли structured output и fallback с открытой моделью.

Чтобы выбрать LLM под конкретную прод-задачу, не смотри чужие лидерборды - прогони 5-7 моделей через OpenRouter на 10 своих реальных примерах тем же промптом, что стоит в проде, и сверь результат руками. У меня за один вечер так выбралась модель для парсера вакансий, и заодно вскрылось, почему без fallback выкатывать её нельзя.
История вот про что. У меня есть парсер вакансий (бот joba_search_bot), он сосёт посты из телеграм-каналов и вытаскивает из них структуру: контакты HR, теги по стеку и главное - вакансия это вообще или нет. Всё это делает LLM по одному промпту. И в какой-то момент захотелось понять: а на какой модели этот промпт живёт лучше всего? Не "какая модель круче вообще", а какая круче на моём мусоре.
Почему бенчмарк LLM надо гонять на своих данных, а не по чужим лидербордам
Бенчмарк LLM на своих данных - это прогон моделей-кандидатов на реальных примерах твоей задачи, а не на публичных тестах. Списки "лучшие нейросети для кода" и "нейросети для программирования" ранжируют модели по усреднённым задачам, которых у тебя нет. На своих 10 записях за вечер видно то, что лидерборд прячет: как модель ведёт себя именно на твоих кривых входных данных.
А данные у меня кривые по определению. Пост в канале - это не аккуратное резюме, это простыня с эмодзи, где контакт HR может быть спрятан в t.me-ссылке, в конце текста или вообще в подписи. Половина постов - вообще не вакансии, а призывы в инди-команду "за долю в проекте" или ссылки на careers-страницы. Лидерборд про такое ничего не скажет. Про то, как парсер видит этот шум, я уже писал отдельно - больше половины постов оказались мусором.
Как выбрать модель в OpenRouter: методика бенча за вечер
Методика простая до неприличия. Берём 7 моделей, 10 свежих реальных вакансий из прода, прогоняем каждую модель тем же промптом, что стоит в бою (не причёсанным под тест - именно боевым), а результат сверяем с бейзлайном llama-3.3-70b плюс руками проверяем все спорные места по исходным текстам постов.
Почему через OpenRouter - потому что это единый API к 400+ моделям от 70+ провайдеров через один ключ, OpenRouter сам роутит и фолбечит. Мне не надо заводить семь разных SDK и семь ключей, чтобы сравнить семь моделей. Поменял строку с названием модели - и гоняй.
Десять записей - это мало? Для статистической строгости мало. Для того чтобы за вечер увидеть, какая модель тупит на твоих кейсах, - за глаза. Разница между хорошей и плохой моделью на живых данных вылезает уже на третьей-четвёртой записи, дальше только подтверждается.
Какая нейросеть победила и зачем всё равно нужен fallback
Победила gpt-5.6-luna: контакты 10/10, единственная из всех вытащила LinkedIn-контакт, честно отсеяла не-вакансию и уложилась в 4.6 секунды на вызов. В резерв встала gpt-oss-120b - тоже 10/10 по контактам, но 20 секунд на вызов, потому что это reasoning-модель и она долго думает.
Казалось бы, зачем вообще резерв, если основная модель работает идеально? А затем, что провайдер может молча выпилить модель, и ты об этом узнаешь по тому, что прод перестал сохранять данные. У меня ровно так уже было с бесплатной моделью на Groq - парсер 6 дней не сохранял вакансии. После того случая правило простое: под основной моделью всегда стоит запасная, и желательно от другого поставщика.
Тут-то и играет open-source. gpt-oss-120b - это открытая MoE-модель на 117 миллиардов параметров от OpenAI под Apache 2.0, первые открытые веса у них со времён GPT-2. Открытую модель нельзя "выпилить" в принципе: в худшем случае поднимешь её сам или найдёшь другого хостера. Медленная, зато не исчезнет. Это и есть страховка. Про падение основной модели прямо посреди работы я тоже разбирал отдельно - ловим отказ и падаем на резерв.
Почему бесплатные и лёгкие модели сливают на проде
Короткий ответ: они экономят там, где экономить нельзя. gemini-flash-lite упустила контакт HR, спрятанный в t.me-ссылке, - для парсера вакансий это провал, контакт же и есть весь смысл. Следующая её версия наоборот пропустила в выдачу не-вакансию, то есть засрала результат мусором. deepseek дал 7/10: reasoning съедал весь лимит токенов и возвращал content=None, пустоту вместо ответа. А бесплатная модель просто флакала ошибками 400 - работает, работает, а потом на ровном месте 400, и делай что хочешь.
Вот это и есть цена бесплатного тира: не деньги, а нестабильность. Одна модель теряет данные, другая добавляет лишнее, третья роняется в случайный момент. Копейки за нормальную модель против вот этого зоопарка рисков - выбор очевидный.
Почему нельзя слепо доверять бейзлайну
Тут я сам чуть не обжёгся. Идея была красивая: сравниваем всех кандидатов с бейзлайном llama-3.3-70b, у кого меньше расхождений - тот и молодец. А потом полез руками проверять расхождения и понял, что бейзлайн сам грешит.
Он пропустил careers-URL в тексте, записал призыв в инди-команду как полноценную вакансию и на ровном месте генерил мусорные теги. То есть если бы я тупо мерил "насколько модель похожа на бейзлайн", я бы наградил тех, кто ошибается так же, как llama. Сверка с бейзлайном - это не сверка с истиной. Бейзлайн нужен, чтобы быстро отсеять явно плохих, но финальное слово - за ручной проверкой по исходнику. Без этого шага весь бенч превращается в конкурс "кто лучше повторит чужие ошибки".
Грабли structured output: max_tokens, JSON-массив и стоимость
Пока гонял модели, собрал горсть граблей, на которые наступит каждый, кто дёргает LLM за structured output.
Первое: у reasoning-моделей max_tokens должен быть не меньше 2048, иначе content приходит пустой. Механика известная - рассуждения съедают весь бюджет токенов, модель упирается в лимит с finish_reason=length ещё на этапе размышлений, и на сам ответ токенов уже не остаётся. Именно так deepseek и отдавал мне content=None. Лечится не хитрым параметром, а тупо поднятием лимита.
Второе: модели иногда возвращают JSON-массив вместо объекта. По-хорошему верхний уровень structured output должен быть объектом, но на практике модель нет-нет да и обернёт всё в массив. Парсер должен уметь оба варианта, иначе ловишь исключение на пустом месте. Я захардкодил проверку: пришёл массив - беру первый элемент, пришёл объект - беру как есть.
Третье: в ответе OpenRouter в поле usage лежит cost. Логируй каждый вызов вместе с этой цифрой. Без такого лога ты не увидишь, как одна медленная reasoning-модель тихо съедает бюджет, пока прод крутится. Мониторинг расходов - это не про жадность, это про то, чтобы утром не словить сюрприз.
Как выкатить это в прод: лестница фолбеков и алерты
В бою всё это собралось в лестницу: luna, если она легла - gpt-oss, если и та упала - regex по любой ошибке. Плюс потолок на ретраи, чтобы не долбить провайдера в бесконечном цикле, и TG-алерт админам, если легли обе модели разом. Объём небольшой, около 90 сообщений в день, так что даже медленный резерв на 20 секунд погоды не делает.
И на закуску - грабля, которая не про модели вообще. В первый же день на проде я запустил догон истории по водяному знаку (добрать старые сообщения, которые пропустил), и это вскрыло баг в Telethon: get_chat без username молча скипал все сообщения. То есть модель ни при чём, LLM отработала бы идеально - просто на вход ей ничего не приходило. Мораль знакомая: сначала убедись, что данные вообще доезжают до модели, а потом уже меряй модели.
Коротко, что вынести
Выбор LLM под прод - это не "погуглить лучшую модель", а вечер на своих 10 записях: боевой промпт, 5-7 кандидатов через OpenRouter, ручная сверка (бейзлайну не верь), запас по max_tokens, парсер на оба формата JSON, лог стоимости и обязательный fallback с открытой моделью в резерве. Скучно? Зато на проде не разваливается.
Частые вопросы
Сколько моделей нужно для бенчмарка LLM на своих данных?
Хватает 5-7 кандидатов на 10 своих реальных примерах. Для статистики это мало, но за один вечер такой прогон уже ловит, какая модель тупит именно на твоих кривых входных данных.
Почему reasoning-модель возвращает пустой content?
Рассуждения съедают весь бюджет токенов: при finish_reason=length модель упирается в лимит ещё на этапе размышлений, и на ответ токенов не остаётся. Ставь max_tokens не меньше 2048, тогда JSON допишется.
Зачем в проде fallback между LLM, если основная модель работает?
Провайдер может молча выпилить или уронить модель, и прод перестанет сохранять данные. Лестница luna → gpt-oss → regex плюс потолок ретраев и алерт держат парсер живым, когда основная модель легла.
Как готовился материал: черновик собран моим AI-конвейером по темам и заметкам из практики, факты сверены с первоисточниками, финальный текст я прочитал, поправил и утвердил перед публикацией. Обложку к статье тоже рисует нейросеть. Про сам конвейер — в разделе обо мне.
