Если ты когда-нибудь строил автономного ИИ-агента, то наверняка знаешь главную боль продакшена: как понять, что бот прямо сейчас не запутался в вызове сторонних API и не выдает клиенту вежливую чепуху? Стандартный подход, который советуют в большинстве модных туториалов, выглядит прямолинейно: берем логи диалога (трейсы), отправляем их в тяжелую флагманскую модель вроде GPT-4o или Claude Opus с промптом «Оцени, была ли здесь ошибка от 1 до 5», и надеемся на чудо.
Но давай посчитаем экономику. Когда агент выполняет тысячи сессий в день, использование полноразмерной LLM в качестве «судьи» (LLM-as-a-judge) на каждый чих превращается в огромную финансовую дыру. К тому же ты получаешь задержку в 3–5 секунд на каждый проверочный запрос.
Инженер и практик эвалюации Хамель Хусейн вместе с Антарипой Саха опубликовали подробный разбор альтернативного пути: использование специализированных decision-моделей. Это компактные негенеративные классификаторы, которые не рассуждают вслух о смысле жизни, а возвращают конкретную вероятность ошибки. Ранее мы уже касались негенеративной модели Typesafe Jev, а теперь появились свежие данные практического бенчмарка на реальных логах.
98 продакшен-трейсов и битва классификаторов
Авторы эксперимента взяли реальную задачу: 98 валидных трейсов сложного агента по поиску и бронированию жилья. В этой выборке было зафиксировано 61 фактическое падение (ошибки аргументов инструментов, неверные даты, сбои логики) и 37 успешных завершений задачи.
Эти трейсы скормили пяти актуальным decision-моделям:
- Perplexity Decider 1.1 (27B): показал рекордную дешевизну — всего $0.0082 за проверку всех 100 сессий при итоговом F1-score 0.79. Правда, среднее время ответа оказалось ощутимым — 7.85 секунды.
- OpenAI Decisions (на базе GPT-6 Luna): продемонстрировал наивысшую точность с F1-score 0.87, но обошелся в $0.0372 за 100 прогонов — в 4.5 раза дороже Perplexity.
- Liquid d1 и d1-ultra: сверхбыстрые модели с экстремально низкой стоимостью ($0.02 и $0.042 за 1 миллион токенов) и временем отклика всего 1.56 секунды.
- Cloudflare Clef Flash и TypeSafe Jev 1.13: крепкие легковесные классификаторы со строгой структуризацией ответов.
Ловушка стандартного порога 0.50
Главное открытие исследования заключается в том, что большинство разработчиков выбрасывают деньги на ветер из-за одной школьной привычки. Получив от классификатора вероятность ошибки от 0.0 до 1.0, инженеры по умолчанию ставят условие: «Если скор больше 0.50, поднимаем тревогу».
Это фатальная ошибка. Сырые скоры моделей смещены: одни архитектуры систематически занижают оценки, другие — завышают. На пороге 0.50 модель Liquid d1 пропускала больше половины реальных сбоев, а Perplexity Decider выдавала шквал ложных срабатываний, заставляя дежурных инженеров отключать алерты в Slack.
В реальном бизнесе у тебя всегда есть развилка между Precision (точностью: если сработал алерт, там действительно сбой) и Recall (полнотой: какой процент реальных сбоев мы поймали). Чтобы система работала предсказуемо, порог отсечения нужно калибровать математически.
Ищем оптимальный порог на Python
Вот как выглядит расчет оптимального порога по сохраненным историческим скорам с помощью библиотеки scikit-learn. Мы ставим жесткое бизнес-требование: точность и полнота должны быть не ниже 80%, после чего максимизируем F1-score:
import numpy as np
from sklearn.metrics import precision_recall_curve
def find_optimal_threshold(y_true, y_scores, min_precision=0.80, min_recall=0.80):
"""
Поиск порога отсечения, гарантирующего минимальные границы точности и полноты.
y_true: массив истинных меток (1 - ошибка, 0 - успех)
y_scores: массив вероятностей от decision-модели
"""
precisions, recalls, thresholds = precision_recall_curve(y_true, y_scores)
best_threshold = 0.50
best_f1 = 0.0
for p, r, t in zip(precisions[:-1], recalls[:-1], thresholds):
if p >= min_precision and r >= min_recall:
# Вычисляем гармоническое среднее Precision и Recall
f1 = 2 * (p * r) / (p + r) if (p + r) > 0 else 0.0
if f1 > best_f1:
best_f1 = f1
best_threshold = float(t)
return best_threshold, best_f1
Сдвиг порога с 0.50 до вычисленного оптимума (например, 0.38 для моделей со смещением) мгновенно поднимает F1-score на 10–15 пунктов без дообучения весов и без изменения промпта.
Калибровка вероятностей: метод Платта
Вторая проблема сырых моделей — искажение абсолютных значений. Если модель вернула 0.90, это не значит, что в 90 случаях из 100 там действительно ошибка. Для выравнивания вероятностей отлично подходит Platt scaling — обучение логистической регрессии поверх логарифмических шансов (log-odds):
from sklearn.linear_model import LogisticRegression
def calibrate_platt_scaling(train_scores, train_labels, test_scores):
"""
Калибровка выходов классификатора методом Платта.
Служит для приведения сырых скоров к честным статистическим вероятностям.
"""
eps = 1e-6
# Обрезаем экстремальные значения для стабильности логарифма
clipped_train = np.clip(train_scores, eps, 1.0 - eps)
log_odds_train = np.log(clipped_train / (1.0 - clipped_train)).reshape(-1, 1)
calibrator = LogisticRegression(C=1.0, solver='lbfgs')
calibrator.fit(log_odds_train, train_labels)
clipped_test = np.clip(test_scores, eps, 1.0 - eps)
log_odds_test = np.log(clipped_test / (1.0 - clipped_test)).reshape(-1, 1)
# Возвращаем калиброванную вероятность положительного класса
return calibrator.predict_proba(log_odds_test)[:, 1]
Метод Платта существенно улучшает метрику Brier score для моделей с системным сдвигом (Perplexity, Liquid d1), но на крошечных датасетах (меньше 50 примеров) есть риск переобучения. Всегда фиксируй тестовую отложенную выборку перед подгонкой весов калибратора.
В сухом остатке: перестань гонять тяжелые LLM для рутинной проверки каждого агентного шага. Связка из недорогой decision-модели за доли цента и аккуратно вычисленного порога срабатывания дает такую же надежность, экономя бюджет и нервы команды поддержки.
