Игорь Градов
Игорь Градов
7 мин
ai

LLM-конвейер молча теряет записи: три бага, которые не видны в сводке

Конвейеры на базе больших языковых моделей (LLM, от англ. Large Language Model) часто теряют записи так, что итоговые проверки показывают успех, а пользователь не получает результат: разработчик обнаружил это в собственном боте для отбора вакансий и описал три конкретных места, где данные пропадали незаметно.

LLM-конвейер молча теряет записи: три бага, которые не видны в сводке
Почему это важно

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

Материал основан на разборе, опубликованном разработчиком после аудита собственного продакшен-бота. Бот собирал посты с вакансиями, проверял их по профилю кандидата и показывал подходящие. В какой-то момент вместо десятков вакансий в неделю стало приходить две. Все автоматические проверки при этом проходили без ошибок.

Что выяснилось при разборе?

В одном из прогонов бот обработал 543 поста. Из них 530 получили статус «не показывать». 196 отказов пришлись на один фильтр по роли. Первая реакция: ослабить фильтр. Но разработчик сначала проследил путь записи от загрузки до показа и нашёл три разных бага.

Все три бага объединяет одно: LLM-конвейер на каждом шаге может потерять запись, а стандартная сводка этого не покажет, потому что считает только то, что до неё дошло.

Баг первый: пачка считалась готовой, хотя часть ответов не вернулась

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

Вот упрощённый фрагмент, где прятался баг:

missing = expected_ids - seen_ids
if missing:
    errors.append(f"нет результата для: {sorted(missing)}")
if errors:
    print(f"[chunk {idx}] validation errors: {errors}")
    ok = True  # даже если есть ошибки
    break

Код замечал недостающие записи, печатал их в лог и всё равно выставлял ok = True. Пачка считалась обработанной, повтор не запускался. Потерянные записи не попадали ни в показ, ни в отказы, ни в очередь на ручную проверку.

Сводка по маршрутам тоже не спасала. Она считала только дошедшие результаты, а общее число брала из той же выборки. Сумма всегда сходилась.

Баг второй: валидатор выбрасывал вакансию из-за вспомогательного поля

Модель иногда возвращала значения не из заданного списка. Например, поле с вариантами yes, no и unknown получало значение pass. В других случаях модель присылала строку там, где ожидался объект.

В промпте (текстовая инструкция для модели) было прямо написано:

remote_from_russia и relocation принимают ТОЛЬКО yes/no/unknown — не pass/fail

Модель всё равно возвращала pass. Повторное указание запрета в промпте не помогало.

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

Ошибка воспроизводилась стабильно. В одном прогоне из 115 записей без результата остались 4. В другом, из 764, неверное значение встретилось в 11 записях: 5 спас повторный запрос, 6 остались без результата.

Баг третий: подходящие вакансии оставались за пределами показа

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

Что понадобится

  • Доступ к логам вашего LLM-конвейера (любой бот, агент или пакетный обработчик на базе языковой модели)
  • Список идентификаторов входных записей до обработки
  • Возможность сравнить входы и выходы поштучно, а не только по количеству
  • 30 минут на первый аудит, дальше проверки автоматизируются

Пошаговая инструкция: как найти скрытые потери в LLM-конвейере

  1. Зафиксируйте число входов ДО обработки. Сохраните список идентификаторов записей до отправки в модель. Никогда не берите «общее число» из итоговой сводки: она считает только дошедшие ответы.

  2. Введите контракт прогона. Проверяйте равенство после каждого прогона:

входы = обработанные + отклонённые + технические ошибки + оговорённые исключения

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

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

  2. Ограничьте допустимое число технических ошибок. Даже если все записи учтены, но модель на каждой упала по таймауту (ограничению времени ответа), задача не выполнена. Для критичных типов ошибок установите порог в ноль.

  3. Разделите обязательные и вспомогательные поля в валидаторе. Неверное значение во вспомогательном поле (нужном только для отчёта) не должно выбрасывать всю запись. Замените отбраковку на значение по умолчанию. Неверное значение в yes/no/unknown становится unknown, а не no: отсутствие уверенности не равно отказу.

  4. Не додумывайте за модель. Значение pass не переводится автоматически в yes. Сохраняйте исходный ответ модели рядом с заменой, чтобы видеть частоту ошибок.

  5. Проверьте очередь вывода. Убедитесь, что записи, прошедшие все фильтры, действительно доходят до пользователя, а не остаются за пределами окна показа.

Как это выглядит на практике

Разработчик расширял профиль отбора: поменял правила в коде, затем промпт. Потребовалось заново разметить 188 записей. Разметку получили 168. Ещё 20 не дошли до маршрута. Новая проверка остановила прогон с сообщением:

FAIL coverage: прогон не выполнил агрегатный контракт
reason 'missing_record' on 20 case(s), contract allows at most 0

Раньше у этих двадцати записей уже были маршруты. После переразметки результатов не стало. Без контракта прогона это выглядело бы как успешное обновление. После исправления промпта повторная обработка дала результат для всех 20.

Причём диагностика уточнила картину: модель не вернула только 2 записи. Остальные 18 она вернула, но их отклонил валидатор из-за вспомогательных полей.

Частые ошибки
  • Сводка считает сама себя. Если общее число записей берётся из итоговой выборки, пропуски невидимы. Берите число входов до обработки.
  • Лог пишет ошибку, а статус остаётся «успех». Классический баг: ok = True после вывода ошибки в консоль. Проверьте, что ошибка действительно влияет на статус прогона.
  • Любое неверное значение приводит к полной отбраковке. Отделите поля, от которых зависит маршрут, от полей для отчёта. Иначе галлюцинация (когда модель уверенно выдумывает ответ, которого не было в инструкции) в одном вспомогательном поле убивает корректную запись.
  • Повтор запрета в промпте вместо обработки в коде. Модель может игнорировать ограничения формата, сколько бы раз вы их ни написали. Обрабатывайте невалидные значения на стороне кода.

Что делать с этим прямо сейчас?

Автору Дзена или копирайтеру, если вы используете LLM для пакетной обработки текстов (рерайт, классификация, генерация описаний), проверьте: совпадает ли число входных файлов с числом готовых результатов? Сравнивайте поштучно, а не итогами.

Маркетологу, если бот или агент на базе языковой модели обрабатывает обращения, лиды или анкеты, внедрите контракт прогона. Без него вы не узнаете, что часть заявок молча выпала из обработки.

Предпринимателю в РФ и СНГ, описанные баги не зависят от конкретной модели. Они воспроизводятся с YandexGPT, GigaChat и любой другой языковой моделью, которую вы подключаете к своему LLM-конвейеру. Чек-лист контрактов из этой инструкции работает для любого провайдера.

Мнение редакции dzen.guru

Этот разбор ценен не технической сложностью, а тем, как буднично выглядит потеря данных в LLM-конвейере. Код логирует ошибку, ставит «ок», идёт дальше. Сводка зелёная. Пользователь молчит, потому что не знает, что должен был получить больше. По моему опыту, большинство ботов и агентов на базе языковых моделей сегодня работают без контракта прогона. Это значит, что потери есть, но их никто не измеряет. Честная оговорка: описанный подход требует, чтобы у каждой записи был уникальный идентификатор и вы контролировали код обработки. Если вы используете готовый no-code-сервис без доступа к логам, начните хотя бы с ручного сравнения: сколько записей отправили и сколько получили на выходе.

Генератор промптов dzen.guru

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

Попробовать генератор

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

Поделиться:TelegramVK
Игорь Градов
Игорь Градов

Основатель dzen.guru. Эксперт по монетизации и продвижению на Дзен. Автор курса «Старт на Дзен 2026».

Комментарии

Читайте также

Graph RAG позволил моделям с 2 млрд параметров решить задачу, которую без графа не решила ни одна
ai

Graph RAG позволил моделям с 2 млрд параметров решить задачу, которую без графа не решила ни одна

Graph RAG (граф знаний для поиска по документам, дополняющий генерацию) позволяет даже компактным моделям с 2-8 миллиардами параметров решать сложные…

8 мин
ai

Профи.ру встроил LLM поверх Elasticsearch: 150 000 запросов в час без замены движка

Дмитрий, лид отдела семантики в Профи.ру, рассказал, как команда встроила языковую модель в поиск услуг поверх Elasticsearch, не выбрасывая классический…

6 мин
Cursor отменяет подписки в России: как оплатить Cursor AI в России уже неважно, есть Codex за те же $20
ai

Cursor отменяет подписки в России: как оплатить Cursor AI в России уже неважно, есть Codex за те же $20

Cursor начал отменять платные подписки пользователей из России: 5 сентября появились сообщения о письмах, в которых сервис ссылается на работу из запрещённого…

5 мин