וואלהסין: שוקלים לקיים פסגה עם דרום קוריאה בנובמברBollywood HungamaEsha Deol turns Sunny Deol’s “Dhaai Kilo Ka Haath” into a Raksha Bandhan gag in Instamart campaign, watchRTP DesportoAvançado senegalês Junior Mendes assina pelo Desportivo de Chaves até 2028ESPN DeportesMourinho defiende a Vinícius ante PrestianniESPNSource: Saints' Kamara (MCL) out at least 1 monthPunchPolice arrest railway vandal, recover slippers in BauchiInquirer EntertainmentPH’s Miss World bet walks with broken shoe at ‘Top Model’ eventThe Jerusalem PostIDF targets Hamas commanders in Gaza, Palestinian sources say nine killed in strikesDaily MaverickGROUNDUP: Tulbagh factory closure threatens thousands of jobs, way of life20 MinutenZu günstige SBB-Billette? Dahinter können Betrüger steckenNOSOekraïense verdachte opnieuw opgepakt om opblazen Nord Stream, nu in KroatiëCBS NewsWatch Live: Lindsay Clancy trial resumes with cross-examination of psychologist
The Daily Newsstand · Free, Always
Wednesday, August 19, 2026

7 ошибок в оценке качества LLM‑систем в продакшене

Translate

Всем привет, меня зовут Сергей Прощаев и в этой статье расскажу про то, как команды теряют контроль над качеством своих LLM‑фич и что с этим делают инженеры, у которых это работает.

Я Tech Lead и руководитель направления Java | Kotlin разработки в FinTech & E‑commerce и преподаю на курсах разработки и архитектуры в ОТУС.

Фича с LLM в проде третий месяц. Метрики на дашборде ровные: latency в норме, ошибок 5xx нет, средняя оценка ответа от «судьи» — 4,3 из 5. А support каждую неделю приносит скриншоты, где ассистент отвечает уверенно и неправильно. Вы открываете логи и понимаете, что предъявить нечего: есть текст запроса, есть текст ответа, и всё. Ни того, что нашёл ретривер, ни того, что вернул внешний инструмент, ни того, почему модель ответила именно так.

Разберём 7 ошибок, которые прячутся ровно до того момента, когда LLM‑фичу начинают спрашивать реальные пользователи. Все они из одной области: как устроены оценка качества (LLM evaluation) и наблюдаемость LLM‑систем в продакшене. Автоматические проверки качества ответов я дальше называю эвалами (evals) — так их называют почти все, кто этим занимается.

Рис. 1. Разрыв между зелёным дашбордом и реальным поведением LLM-системы

Рис. 1. Разрыв между зелёным дашбордом и реальным поведением LLM‑системы

Ошибка 1. Качество проверяется «на глаз»

  1. Симптом. Изменение промпта уезжает в прод после того, как разработчик прогнал три‑четыре своих любимых запроса и сказал: «Ну вот, стало лучше». В PR идёт спор двух senior‑инженеров, у каждого своё «стало лучше», и побеждает тот, кто громче.

  2. Почему возникает. Промпт не воспринимается как код. Он в конфиге, его правит кто угодно, ревью проходит по диагонали. Плюс психология: изменение, которое ты сам придумал, всегда кажется улучшением.

  3. Что ломает. Регрессии, которые никто не ловит. Вы чините один сценарий и молча ломаете два соседних. Через несколько итераций система работает хуже, чем месяц назад, но доказать это нечем — точки отсчёта нет.

Как исправить. Зафиксировать датасет из реальных запросов и гонять его в CI на каждый PR, который трогает промпт, модель, параметры или логику сборки контекста. Начните с 30–50 кейсов, но честно понимайте, что это дымовой тест: такой набор ловит грубые поломки и регрессии в известных сценариях, а на статистически значимые выводы про качество не тянет — для них счёт идёт на сотни кейсов. Это нормальный первый шаг, ненормально — не иметь никакого.

# (Python, pytest) минимальный офлайн-эвал в CI
import pytest

@pytest.mark.parametrize("case", golden_cases(), ids=lambda c: c.id)
def test_answer_passes_binary_checks(case, assistant, judge):
    trace = assistant.run(case.input)

    # 1. Детерминированные ассерты — дёшево, быстро, без судьи
    for call in trace.tool_calls:
        assert call.status == "OK", f"вызов {call.name} упал"
        assert tool_result_is_valid(call), f"{call.name}: ответ без полезных данных"

    assert trace.answer_tokens <= MAX_ANSWER_TOKENS   # доменный лимит на размер

    if case.must_cite:
        assert trace.citations, "нет ссылки на источник"

    # 2. Судья — только там, где формально проверить нельзя
    verdict = judge.evaluate(case.input, trace.answer, case.context)
    assert verdict.passed, f"судья: {verdict.reason}"

Обратите внимание на две вещи.

  • Первая — порядок: сначала обычные ассерты, потом судья. На практике я стараюсь закрывать детерминированными правилами около 70% проверок и только остальное отдавать модели: дешевле, стабильнее и объяснимее.

  • Вторая — рядом со статусом стоит проверка самого результата. Статус OK ничего не гарантирует, а что считать валидным ответом инструмента, знает только ваш домен: где‑то это непустой список, где‑то конкретное поле, где‑то допустим и 204.

Дальше в статье будет ровно тот случай, ради которого эта строчка написана.

Примеры на Python, хотя основной сервис у нас на Kotlin. Это осознанно: обвязка вокруг LLM почти везде живёт на Python, там же eval‑раннеры и инструментация. Я бы предпочёл, чтобы эвалы правились без меня — теми, кто ближе к данным.

Ошибка 2. В логах только вход и выход

  1. Симптом. Пришла жалоба на конкретный диалог. Вы находите в логах пару «запрос — ответ» и дальше упираетесь. Что вернул поиск? Какая версия промпта была в тот момент? Сколько токенов ушло в контекст? Неизвестно.

  2. Почему возникает. LLM‑вызов инструментируют как обычный HTTP‑запрос: URL, код, длительность. Для микросервиса этого хватает, для LLM‑пайплайна — нет.

  3. Что ломает. Невоспроизводимые инциденты. Разбор одного случая занимает часы, а вывод в постмортеме звучит как «модель иногда галлюцинирует». Это не вывод, это капитуляция.

Как исправить. Трассировать всю цепочку, а не финальный вызов. Хорошая новость: изобретать схему атрибутов для LLM observability больше не нужно. OpenTelemetry получил статус graduated в CNCF 21 мая 2026 года, а конвенции GenAI описывают выполнение агента как дерево спанов: invoke_agent, внутри — chat на каждый вызов модели и execute_tool на каждый вызов инструмента. Конвенции MCP переехали в тот же репозиторий, так что вызовы инструментов через MCP попадают в общий словарь атрибутов. Формально большая часть спецификации всё ещё в экспериментальном статусе, и атрибуты могут переименовать — но это лучше, чем свой формат полей, который не прочитает ни один сторонний бэкенд.

# (Python, OpenTelemetry SDK) спан вокруг вызова модели
from opentelemetry import trace

tracer = trace.get_tracer("assistant")

with tracer.start_as_current_span("chat") as span:
    span.set_attribute("gen_ai.operation.name", "chat")
    span.set_attribute("gen_ai.provider.name", provider)
    span.set_attribute("gen_ai.request.model", model)
    span.set_attribute("gen_ai.request.temperature", temperature)
    span.set_attribute("app.prompt.version", prompt_version)      # своя ось для A/B
    span.set_attribute("app.retrieval.top_k", top_k)              # без списка id:
    span.set_attribute("app.retrieval.result_count", len(docs))   # он длинный и
    span.set_attribute("app.index.version", index_version)        # ломает лимиты

    response = client.chat(**request)

    usage = response.usage
    span.set_attribute("gen_ai.usage.input_tokens", usage.input_tokens)
    span.set_attribute("gen_ai.usage.output_tokens", usage.output_tokens)
    span.set_attribute("app.answer.hash", sha256(response.text))  # без сырого текста

Отдельно про хранение текстов. Промпты и ответы — это персональные данные пользователя со всеми вытекающими. В финтехе я бы предпочёл не тащить сырой контент в общий коллектор, а держать его в отдельном хранилище с ограниченным доступом и коротким TTL, а в трассе оставлять идентификаторы и хеши. Спецификация это предусматривает: в ней есть отдельные режимы захвата содержимого — не писать тексты вообще, писать их в спан или отправлять отдельным потоком событий. Выбор режима — это ваше решение про баланс отладки и приватности, и принимать его лучше до первого инцидента, а не после.

Ошибка 3. Судью никто не калибровал

  1. Симптом. Метрика качества от LLM‑судьи стабильно держится в районе 0,9. Продуктовые метрики при этом не двигаются, а количество обращений в поддержку растёт.

  2. Почему возникает. LLM‑as‑a‑Judge прикручивают за час: берут ту же модель, что уже в проекте, пишут промпт «оцени ответ от 1 до 5» и вешают результат на дашборд. Дальше цифра живёт своей жизнью и попадает в отчёты, хотя её ни с чем не сверяли.

  3. Что ломает. Дашборд начинает врать, причём системно. Это не догадка: в исходной работе про LLM‑as‑a‑Judge (Zheng et al., 2023) описаны три смещения — позиционное, многословное и самопредпочтение, когда модель выше оценивает собственные выводы. Там же показано, что сильный судья сходится с людьми более чем в 80% случаев: инструмент рабочий, и именно поэтому его перекос легко не заметить.

Свежие цифры: в сравнении 21 судьи (2026) простое совпадение вердиктов у каждого оказалось намного выше согласия с поправкой на случайность — разрыв по каппе Коэна доходил до 41 процентного пункта. «Судья и человек согласны в 85% случаев» звучит отлично ровно до того момента, пока не посчитаешь, сколько из этих совпадений дало бы угадывание.

Как исправить. Относиться к судье как к производственному компоненту, а не к утилите. Практика 2026 года выглядит так: берём 100–300 продовых трасс, размечаем их двумя‑тремя людьми, считаем согласие между аннотаторами по каппе Коэна (по классической шкале Лэндиса и Коха 1977 года диапазон 0,61–0,80 считается существенным согласием, выше 0,81 — почти идеальным), затем прогоняем те же трассы судьёй и меряем согласие судьи с людьми по той же шкале. Дальше — регулярный прогон по gold‑set с алертом на падение каппы и обновление самого набора свежими трассами.

Про семейство моделей уточню, потому что тут часто перегибают, и я сам поначалу перегибал. Судья из другой линейки — разумная страховка от самопредпочтения, но это эвристика, а не закон. Решает калибровка: если согласие с людьми измерено и держится, судья того же семейства не мешает. Если не измерено, другое семейство не спасёт.

Схема ниже (Рис. 2) показывает, как эти куски собираются в один контур: прод отдаёт трассы, часть уходит людям, люди калибруют судью, судья работает на потоке, а найденные типы ошибок возвращаются в офлайн‑датасет.

Рис. 2. Схема принципиальная: контур оценки качества LLM-системы

Рис. 2. Схема принципиальная: контур оценки качества LLM‑системы

Главное, что стоит вынести из этой схемы: контур замкнут. Люди не заменяются судьёй и не размечают весь трафик — они калибруют инструмент, который работает на потоке.

Если стрелка от разметки к судье отсутствует, у вас не система оценки, а генератор красивых чисел.

Ошибка 4. Метрики берут из коробки вместо анализа реальных ошибок

  1. Симптом. На дашборде 12 метрик: faithfulness, relevancy, coherence, helpfulness и далее по списку фреймворка. Вы чините реальную проблему — ни одна из них не шевелится.

  2. Почему возникает. Готовые метрики поставляются вместе с библиотекой, их видно в документации, они включаются одной строкой. Анализ собственных логов не поставляется ни с чем.

  3. Что ломает. Вы измеряете чужие представления о качестве. Ваши пользователи ломают систему совсем не там, где предполагали авторы фреймворка.

Как исправить. Сначала руками прочитать несколько десятков реальных трасс и выписать, что именно пошло не так, потом придумывать метрики. Помню, как однажды на другом проекте два дня чтения логов дали больше, чем неделя настройки мониторинга — просто потому, что стало видно, какие формулировки пользователи используют на самом деле.

Хрестоматийный пример на эту тему — история ассистента для управляющих компаний Nurture Boss, описанная в полевом руководстве Хамеля Хусейна. Команда сделала простой просмотрщик диалогов с полем для заметок о сбоях, разметила несколько десятков разговоров — и вылезла закономерность: ассистент не справлялся с датами, ошибаясь в 66% случаев, когда человек писал что‑то вроде «запишите меня на просмотр через пару недель». Вместо того чтобы менять модель, они разобрали типы сбоев с датами, написали под них конкретные тесты и стали мерить именно это. Доля успешных обработок дат выросла с 33% до 95%.

Никакой магии: узкая метрика под реальный сбой оказалась полезнее десяти универсальных. Как и предполагал, самое дорогое в этой истории — не инструмент, а решение потратить время инженера на чтение сырых диалогов.

Ошибка 5. Оценка по шкале 1–5 и одно среднее число на всё

  1. Симптом. «Среднее качество за неделю — 4,1». Вопрос «а что именно сломалось» ответа не имеет.

  2. Почему возникает. Пятибалльная шкала кажется информативнее бинарной. На деле граница между 3 и 4 не воспроизводится ни у людей, ни у модели: сегодня 3, завтра 4, а разница внутри шума.

  3. Что ломает. Среднее прячет хвосты. Пять процентов катастрофических ответов не сдвинут среднее заметно, зато один такой ответ клиенту в финтехе стоит дороже, чем весь остальной трафик за месяц.

Как исправить. Бинарный вердикт (прошло / не прошло) плюс обязательная причина и разбивка по типам сбоев. Считать не среднее, а долю провалов в каждой категории.

# (текстовый промпт судьи) бинарный вердикт с причиной
Ты проверяешь ответ ассистента банка.
Верни строго JSON: {"pass": true|false, "reason": "...", "category": "..."}

pass = false, если выполнено хотя бы одно:
- в ответе есть утверждение, которого нет в предоставленном контексте;
- названа сумма, ставка или срок без ссылки на документ из контекста;
- вопрос требовал передачи оператору, но передача не предложена.

category — одно из: hallucination | missing_handoff | wrong_number | other

Ошибка 6. Выводы делаются по одному прогону

  1. Симптом. «Новый промпт дал +3% на датасете, катим». Через неделю на том же датасете он же даёт −2%, и никто не понимает, что произошло.

  2. Почему возникает. Привычка из мира детерминированных тестов: зелёный прогон означает «работает». В LLM‑пайплайне разброс дают сразу несколько источников — температура выше нуля, недетерминизм инфраструктуры провайдера, параллельные вызовы инструментов, порядок и состав выдачи ретривера, реранкер, а иногда и тихое обновление модели на стороне провайдера. Плюс сам судья, который тоже модель и тоже отвечает не идентично.

  3. Что ломает. Команда принимает решения по шуму. Хуже того — начинает подгонять промпт под случайные флуктуации, и это уже прямой путь к переобучению на собственный тестовый набор.

Как исправить. Несколько прогонов, интервальная оценка, зафиксированные версии генератора и судьи. Если интервалы пересекаются, разницы нет. Версию судьи фиксируйте отдельно: провайдеры обновляют модели без предупреждения, и это тихо смещает оценки. Вот как это выглядит в отчёте CI:

# (вывод eval-раннера в CI)
# 60 кейсов x 5 прогонов = 300 наблюдений на вариант
# интервал: бутстрэп по кейсам, 1000 ресэмплов, метрика — доля pass

prompt v14  | pass 71.2% | 95% CI [65.4 .. 76.7] | gen=gpt-4.1-2025-04, judge=claude-3.7
prompt v15  | pass 73.9% | 95% CI [68.3 .. 79.1] | gen=gpt-4.1-2025-04, judge=claude-3.7

ВЕРДИКТ: интервалы пересекаются, значимой разницы нет. Не катим.

Метод не так важен, как его постоянство: бутстрэп, биномиальный интервал — любой, но одинаковый от прогона к прогону. Важнее другое: ресэмплить нужно кейсы, а не отдельные наблюдения, потому что пять прогонов одного кейса — это не пять независимых замеров.

Первая же такая табличка снимает половину споров в PR. Помню, как однажды она сняла и мой собственный аргумент: я был уверен, что переписал промпт лучше, а после пяти прогонов увидел то же качество и на 20% больше токенов.

Ошибка 7. Оценка живёт только на релизе

  1. Симптом. Эвалы гоняются в CI, всё зелёное. Через месяц продуктовые метрики просели, а в CI по‑прежнему зелено.

  2. Почему возникает. Оценка воспринимается как приёмка: прошли — забыли. Между тем меняется всё: распределение запросов, содержимое базы знаний, версия модели у провайдера, поведение судьи.

  3. Что ломает. Деградацию замечает не мониторинг, а бизнес. Обычно с задержкой в несколько недель и через жалобы.

Как исправить. Онлайн‑контур: судья на сэмпле продового трафика, алерты по доле провалов в каждой категории, отдельный трекинг стоимости судьи и согласия с людьми. Могу себе представить, как это звучит для команды из трёх человек — «ещё один сервис поддерживать». Поэтому начинать стоит с малого: сэмпл 5%, две категории ошибок, один алерт.

Ниже (Рис. 3) — как выглядит один запрос в трассировке и где именно ставятся точки проверки. Это тот самый случай из первого экрана: внешний инструмент вернул HTTP 200 с пустым телом, модель не упала, а достроила правдоподобный ответ вокруг пустоты.

Рис. 3. Трассировка одного запроса и точки контроля качества

Рис. 3. Трассировка одного запроса и точки контроля качества

Главная мысль этой схемы: самые дорогие сбои LLM‑систем не выглядят как сбои. Никаких исключений, никаких 5xx — ответ формально успешный. Поймать это можно только детерминированной проверкой на уровне спана, и такая проверка стоит копейки по сравнению с судьёй.

Где этот подход не сработает

Честно про ограничения, иначе чек‑лист превращается в карго‑культ.

Если LLM‑фича внутренняя и ей пользуются пять человек из соседнего отдела, полный контур избыточен: тут хватит логов с сохранением трасс и разбора жалоб руками. Порог, после которого контур окупается, я бы провёл примерно там, где ошибка ассистента становится видимой клиенту или деньгам.

Второе ограничение — калибровка судьи требует людей. Не аннотаторов с биржи, а тех, кто понимает предметку: в финтехе разметить ответ про условия продукта может далеко не каждый. Если такого человека нет и не будет, честнее оставить детерминированные проверки и ручной разбор выборки, чем строить судью, которому вы не сможете доверять.

Третье: интервальные оценки бессмысленны на датасете в полтора десятка кейсов. Пока набор маленький, эвалы работают как дымовые тесты — ловят грубые поломки, но не измеряют качество, и подавать их как измерение нечестно по отношению к самим себе.

Сводная таблица

Ошибка

Признак в проде

Что проверить прямо сегодня

Проверка «на глаз»

Спор в PR про «стало лучше»

Есть ли датасет, который падает красным в CI

Только вход и выход в логах

Инцидент нельзя воспроизвести

Есть ли спаны retrieval и execute_tool, а не только финальный вызов

Судья без калибровки

Метрика 0,9 при росте обращений в поддержку

Когда последний раз считали согласие судьи с человеческой разметкой

Метрики из коробки

Фикс есть, а метрики не двигаются

Читал ли кто‑нибудь 50 сырых трасс подряд

Шкала 1–5 и одно среднее

«Среднее 4,1» без разбивки

Есть ли доля провалов по каждой категории сбоя

Вывод по одному прогону

Результат гуляет между запусками

Считается ли интервальная оценка, зафиксированы ли версии моделей

Оценка только на релизе

Просадка замечена бизнесом, а не мониторингом

Работает ли судья на сэмпле продового трафика

Чек‑лист перед выкаткой LLM‑фичи

  • Промпт лежит в репозитории и версионируется, его изменение запускает эвал в CI.

  • Есть офлайн‑датасет из реальных запросов, каждый кейс с бинарным критерием: 30–50 как дымовой тест, сотни — когда нужны выводы о качестве.

  • Трасса содержит retrieval, вызовы модели и вызовы инструментов, а не только финальный ответ.

  • Сырые тексты промптов и ответов хранятся отдельно, с ограниченным доступом и TTL.

  • Судья и генератор — желательно из разных линеек моделей, но обязательное здесь другое: согласие судьи с человеческой разметкой измерено, зафиксировано и перепроверяется по расписанию.

  • Метрика бинарная, с разбивкой по категориям сбоев, а не одно среднее число.

  • На продовом трафике работает сэмпл‑проверка с алертами.

  • Есть детерминированные проверки результата действия: пустое тело ответа инструмента, отсутствие цитаты, превышение лимита канала.

Что на самом деле проверяет эта группа ошибок

Все семь пунктов сводятся к одному навыку, и это не промптинг. Это умение относиться к недетерминированному компоненту как к части production‑системы: с версионированием, трассировкой, измеримым качеством и понятной процедурой отката.

Забавно, что почти всё здесь — старые инженерные практики. Тесты в CI, трассировка, разбивка метрик по категориям, интервальные оценки. Просто применённые к компоненту, который отвечает по‑разному на один и тот же вход. Тот, кто уже умеет эксплуатировать распределённые системы, осваивает эту часть быстрее, чем тот, кто год писал промпты.

LLM уже работает в продакшене, но отдельные ошибки всё ещё приходится искать вручную? Разобраться в поведении модели и точках контроля — первый шаг к системе, которой можно доверять в реальных сценариях.

Продолжить тему можно на бесплатных открытых уроках:

  • 7 сентября, 20:00. «Почему 90% ML‑проектов не доходят до продакшена? Разбираем архитектуру настоящей ML‑системы». Записаться

  • 8 сентября, 20:00. «Что надо знать про работу LLM моделей». Записаться

  • 23 сентября, 18:00. «Structured Outputs: как заставить LLM всегда возвращать то, что вам нужно». Записаться

Полный список бесплатных уроков августа смотрите в дайджесте.

View the original on Хабр

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.