Вашему LLM-классификатору нужна таксономия, а не «промпт получше»
Классификация на LLM ломается не из-за слабого промпта, а из-за неопределённых меток. Мой рабочий процесс: сначала таксономия, потом извлечение фактов вместо вердиктов, а решение принимает код.
Pavel Duglas
AI Automation & MVP Architect
Раз в пару недель мне показывают одно и то же: промпт, который заканчивается строкой «Верни одно из: urgent, normal, spam», и жалобу «модель нестабильна». Я прошу двух человек из их команды вручную разметить одни и те же двадцать сообщений - и они расходятся в шести. Модель не нестабильна. Метки не определены. Невозможно выбить из промпта соблюдение правила, которого ещё не существует.
Классификация - самый частый реальный сценарий применения LLM в бизнес-автоматизации: триаж тикетов, скоринг лидов, модерация, категоризация спарсенных объявлений, роутинг входящих сообщений в Telegram-боте. И это же место, где люди пропускают всю скучную инженерную часть, а потом винят модель. Ниже - процесс, которым я реально пользуюсь.
Шаг 1: метка должна соответствовать разному действию
До того как написать первую строчку промпта, выпишите, что происходит дальше для каждой метки. Если две метки ведут к одному и тому же действию - это одна метка. Если одна метка ведёт к трём разным действиям в зависимости от контекста - это три метки, либо вам не хватает отдельного поля.
Плохая таксономия для тикетов поддержки:
billing,technical,question,complaint,other
Они перекрываются. Жалоба на неудавшийся платёж - это одновременно billing, complaint и technical. Модель вынуждена угадывать ваш приоритет, и во вторник она угадает иначе, чем в понедельник.
Нормальная таксономия, определённая через действие:
refund_request-> в очередь финансов, SLA 4 часаaccess_blocked-> в on-call, SLA 30 минутhow_to-> автоответ со ссылкой на документацию, без человекаsales_inquiry-> в CRMunclear-> в очередь ручного разбора
Правила, от которых я не отступаю:
- Взаимоисключаемость. Если сообщение честно тянет на две метки, добавьте явный tie-breaker: «если пользователь не может войти И просит возврат - ставим
access_blocked». - Полнота плюс аварийный выход. Всегда держите
unclearилиother. Без него модель начинает изобретать уверенность, которой у неё нет. - Максимум 7-9 меток на уровень. Больше - делайте иерархию: сначала грубая метка, потом второй проход внутри ветки. Плоские таксономии на 40 категорий - это то место, где точность умирает.
- Пишите рубрику, а не слово. На каждую метку: строчка определения, два положительных примера, один пограничный отрицательный пример и правила разрешения конфликтов. Эта рубрика - ядро промпта и одновременно инструкция для людей-разметчиков.
Если два человека из вашей команды не могут договориться, имея рубрику на руках, - правьте рубрику. Неоднозначность это баг таксономии, и никакой апгрейд модели его не закроет.
Шаг 2: просите у модели факты, решение принимает код
Это единственное изменение, которое улучшило больше моих пайплайнов, чем любая смена модели. Вместо вердикта просите наблюдаемые факты, а бизнес-правила применяйте в коде.
Сравните. Стиль «вердикт»:
Определи приоритет тикета: urgent / normal / low.
Стиль «факты»:
{
"mentions_payment": true,
"mentions_cannot_access": false,
"has_order_id": true,
"asks_for_money_back": true,
"customer_tone": "frustrated",
"language": "ru",
"quoted_error_code": null
}
А дальше:
def route(facts, account):
if facts["mentions_cannot_access"] and account.plan == "paid":
return "access_blocked"
if facts["asks_for_money_back"]:
return "refund_request"
if facts["customer_tone"] == "frustrated" and account.mrr > 500:
return "escalate_human"
return "how_to"
Почему это выигрывает в продакшене:
- Изменение политики не трогает промпт. Финансы решили, что возвраты свыше 200 долларов требуют согласования менеджера? Это
ifна Python, а не переписывание промпта плюс переоценка всей таксономии. - Отлаживается. Когда тикет ушёл не туда, вы видите, модель ли неверно прочитала текст или правило было кривое. Из односложного вердикта вы получаете одно слово и объяснение, которому нельзя доверять.
- Дешевле и стабильнее. Извлечение фактов - узкая задача. Маленькая модель делает это надёжно. Токены вы тратите там, где реально нужны суждения.
- Можно подмешать данные, которых модель не видит. MRR аккаунта, дата регистрации, число предыдущих обращений. Модели они не нужны, вашим правилам - нужны.
Именно это имеют в виду, когда говорят, что классификация - это feature engineering. LLM здесь экстрактор признаков из грязного текста. Решение остаётся в коде, где его можно протестировать.
Всегда форсируйте структурированный вывод: JSON-схема с enum и boolean, без свободного текстового поля под метку. Если провайдер умеет strict schema mode - включайте. Если нет - валидируйте, делайте один retry, а потом падайте в unclear, но не принимайте мусор.
Шаг 3: golden set до любой оптимизации
150-300 реальных примеров, размеченных руками того человека, который владеет действием ниже по потоку. Не синтетика. Не двенадцать кейсов, которые вы помните по памяти.
Как я его собираю:
- Стратифицированно по меткам, включая редкие. Случайная выборка из продакшена даст вам 90%
how_toи ничего не расскажет про метки, которые действительно важны. - 20% специально тяжёлых кейсов: неоднозначные, многосоставные, те, из-за которых команда спорила.
- 5% мусора: пустая строка, только эмодзи, не тот язык, вставленный стектрейс, HTML из сломавшегося парсера.
- Хранить файлом в репозитории:
id, сырой вход, ожидаемая метка, ожидаемые факты. Под версионированием.
Дальше - гоняем в CI. Любое изменение промпта, модели, температуры или схемы запускает golden set и печатает precision и recall по каждой метке. Не accuracy. Accuracy на несбалансированной выборке - метрика для самоуспокоения: если 85% элементов это how_to, классификатор, который всегда отвечает how_to, наберёт 85% и при этом не будет работать.
На что я смотрю:
- Recall по меткам, пропуск которых дорог. Пропущенный
access_blocked- это ушедший клиент. Пропущенныйhow_toне стоит ничего. - Precision по меткам, запускающим автоматические действия. Если
refund_requestавтоматически создаёт заявку на возврат, false positive стоит денег. - Матрица ошибок. Обычно одна пара меток даёт большую часть ошибок. И в 80% случаев эта пара - проблема таксономии, а не модели.
Задайте в CI явные пороги: recall по access_blocked не ниже 0.95, precision по refund_request не ниже 0.90. Ниже - билд красный. Теперь ваш промпт это протестированный артефакт, а не удачно подобранная строка.
Шаг 4: дешёвая модель, потом эскалация
Трёхуровневый роутинг, который я переиспользую почти везде:
- Детерминированный пре-фильтр. Регулярки и ключевые слова снимают очевидные 20-40%. Точное совпадение с «отписаться» не требует ни одного токена. Пустой вход - тоже.
- Маленькая модель на извлечение фактов для всего остального. Она тянет основной объём.
- Большая модель только для того, что маленькая пометила как
unclear, где факты противоречат друг другу, или где элемент переходит порог ценности (enterprise-аккаунт, заказ выше X).
Про уверенность: не верьте самоотчётному полю "confidence": 0.93. Модель с радостью выдаст эту цифру для полной галлюцинации. Работают две вещи:
- Self-consistency. Прогнать маленькую модель три раза при температуре 0.7. Три совпавших ответа - сильный сигнал, расхождение - триггер эскалации. Стоит 3x на дешёвом уровне, всё равно дешевле, чем всегда звать большую модель.
- Logprobs токена метки, если провайдер их отдаёт. Дешевле self-consistency и вполне калиброванно после того, как вы разложите их по бакетам на своём golden set.
И всегда оставляйте человеческую очередь. Классификатор, который отправляет 5% человеку и по остальным 95% прав, - это рабочая система. Тот, который делает вид, что закрывает 100%, - это будущий инцидент.
Шаг 5: логируйте как под аудит, следите за распределением
На каждую классификацию сохраняйте: хеш входа, сырой вход (или указатель на него), извлечённые факты, финальную метку, версию промпта, имя и версию модели, latency, стоимость и уровень каскада, который дал ответ. Потом доджойните результат ниже по потоку: одобрили ли возврат, конвертировался ли лид, переписал ли человек метку.
Этот процент переопределений (overturn rate) - лучшая продакшен-метрика, которая у вас есть. Это бесплатная разметка. Каждая правка человека - кандидат в golden set. Раз в неделю я выгружаю все переопределения плюс 1% случайной выборки и пятнадцать минут просматриваю в таблице.
Отдельно мониторьте распределение меток по дням, а не только ошибки. Если how_to три месяца держался на 60% объёма и за ночь упал до 30% - либо сломался продукт, либо изменился источник трафика, либо ваш парсер стал отдавать другой HTML. В парсинг-пайплайнах это почти всегда третье: сайт переехал на новую вёрстку, извлечённый текст превратился в навигационное меню, и классификатор старательно классифицирует меню. Алерт на сдвиг распределения ловит это за несколько дней до того, как кто-то заметит кривой роутинг.
И последнее: пиньте версию модели. «Latest» - это не версия. Тихое обновление на стороне провайдера может сдвинуть пограничную пару меток на несколько пунктов, и вы потратите день на поиск бага в собственном коде.
Коротко
Пишите таксономию через действия. Заставьте модель извлекать факты, а не выносить вердикты. Решения держите в коде. Соберите golden set на 200 строк и гоняйте метрики по меткам в CI. Эскалируйте вместо угадывания. Логируйте переопределения и смотрите на распределение.
Ничего из этого не выглядит захватывающе и ничего не зависит от того, какая модель побеждает в этом месяце. Именно поэтому оно продолжает работать через полгода после релиза.
Вопросы и ответы
У меня уже есть промпт с 30 категориями и он работает так себе. Переделывать всё с нуля?
Не с нуля, но таксономию - да. Возьмите лог за последний месяц, посмотрите фактическое распределение и выкиньте метки с долей меньше 1% или те, что ведут к тому же действию, что и соседние. Обычно из 30 категорий остаётся 6-8 осмысленных плюс второй уровень внутри одной-двух ветвей. Это займёт день и даст больше, чем неделя подбора формулировок в промпте.
Стоит ли файнтюнить модель вместо всего этого?
Только после того, как у вас есть стабильная таксономия и golden set, и вы упёрлись в потолок по качеству или по стоимости. Файнтюн на неопределённых метках просто зафиксирует вашу неразбериху в весах, и переобучать придётся при каждом изменении политики. Практический порядок: таксономия -> факты плюс правила -> каскад моделей -> и лишь потом файнтюн, если экономика на объёме сходится.
Как быстро собрать golden set, если у меня ещё нет продакшен-трафика?
Возьмите то, что есть: письма, чаты, тикеты, комментарии, выгрузку из парсера - 150 строк найдётся почти всегда. Разметьте вручную сами, вместе с человеком, который потом будет обрабатывать эти кейсы. Синтетику используйте только для мусорной части набора (пустые строки, битый HTML, не тот язык), потому что её легко генерировать и она реально встречается. Для содержательных меток синтетические примеры слишком чистые и дают ложное ощущение качества.
Похожие статьи
Сделаю под ключ
Соберу ИИ-агента под реальную задачу
С инструментами, памятью и логами, чтобы он работал в проде, а не только в демо.
от 1 500 $ · 1-2 недели