# LLM-пайплайн два месяца в проде: 6 мест, где всё сломалось — и ни одно не в промпте
Каноническая страница: https://plainews.ru/posts/llm-pipeline-prod-failures-procurement
Опубликовано: 2026-09-09T07:01:27.046Z

Два месяца в проде, 4 452 закупки, почти 300 инцидентов в журнале — где реально ломается LLM-пайплайн и как это чинить без переписывания промптов.
За два месяца система автоматического поиска тендеров обработала 4 452 закупки и накопила почти 300 записей в журнале инцидентов. Большинство поломок не имели никакого отношения к качеству промптов. Вот что реально идёт не так, когда LLM-конвейер выходит в прод.

## Почему это важно, прежде чем вы напишете первый промпт

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

Система, о которой пойдёт речь, читает ленты 16 закупочных площадок (14 через платный агрегатор, 2 напрямую), прогоняет каждое извещение через детерминированный фильтр по названию, затем через трёхуровневый стек моделей: Claude Haiku 4.5 (предскоринг), Claude Sonnet 5 (оценка по 18 критериям), Claude Opus 5 (коммерческое предложение). Промпт предскоринга достиг шестой версии за первые две недели и с конца июля не менялся. Дальше начались настоящие проблемы.

## Чужой поисковый индекс врёт — и вы об этом не узнаете

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

Закупка аутстаффа называлась «Ставки специалистов» — словарь её не видел. «ИТ-аутсорсинг» через дефис поиск не находил, потому что матчил слово целиком. По «аутсорсинг» без «ИТ» приходило около 100 нерелевантных записей про детсады и грузчиков.

Критичнее оказалось другое. Запись с «чат-бот» в названии не находилась по слову «чат-бот» из словаря, но находилась по «чатбот», которого в названии не было. Индекс, судя по всему, строился по телу извещения, а не по названию. Расширение словаря здесь не помогло бы: слово уже было в словаре, просто индекс работал не так, как ожидалось.

**Решение:** перестать искать по чужому индексу и читать ленты площадок напрямую, страница за страницей. Фильтрацию «наше или нет» перенести на свою сторону.

## Чтение лент без отметки позиции сжигает лимит за трое суток

Переход на чтение лент поставил новую задачу: не читать одно и то же дважды. Первый расчёт показал цену ошибки.

| Режим | Запросов к агрегатору | Что это означало |
| --- | --- | --- |
| Чтение без отметки позиции | ~500 в час | Лимит тарифа (30 000/мес) кончался за 3 суток |
| С отметкой по каждой площадке | 14 в час | Установившийся режим |
| Глубокий суточный обход (до оптимизации) | 430+ | Перечитывал всё окно записей |
| Глубокий обход после разбивки на трети | 131 | Площадки разбиты на три группы по дням |

Отметка «докуда дочитали» хранится отдельно по каждой тендерной площадке. Глубокий обход, который перечитывает окно ниже отметки, запускается раз в сутки и разбит на трети площадок по дням.

## Фиксированный порядок обхода скрывает пропуски за зелёными счётчиками

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

Счётчики при этом показывали найденные профильные записи — по ним ничего не было заметно. Система «успешно» работала, пока часть источников систематически не читалась.

> **Важно.** Зелёные метрики не гарантируют полноту покрытия. Счётчик «найдено записей» не покажет, что две площадки из шестнадцати не читались неделю.

**Решение:** ротация площадок по времени последнего чтения вместо фиксированного порядка.

## Потолок запросов, который проверяется только на старте

После поднятия суточного потолка до 1 000 запросов два дня подряд расход составил 1 008 и 1 023. Причина: проверка потолка стояла только на старте прохода, а не внутри цикла. Прогон стартовал в рамках лимита, затем превышал его.

Результат — 11 и 10 пропущенных часовых проходов из 24: остаток суток система отменяла запуски из-за исчерпанного лимита.

```python
# Было: проверка только на старте
if daily_requests_used < DAILY_LIMIT:
    run_full_pass()

# Стало: проверка внутри цикла
for platform in platforms:
    if daily_requests_used >= DAILY_LIMIT:
        break
    fetch_feed(platform)
    daily_requests_used += 1
```

Проверку перенесли внутрь цикла — перерасход прекратился.

## Слабая ступень предскоринга — это архитектурное решение, а не баг

75% отказов человека на гейте имеют код «не наш профиль». Это выглядит как слабость дешёвой модели, но это сознательный выбор.

Предскоринг намеренно держится с низким порогом отсечения: лучше пропустить нерелевантный тендер на следующую ступень, чем потерять реального кандидата на входе. Цена ложноотрицательного результата (пропущенный тендер) выше, чем цена ложноположительного (лишняя работа для человека на гейте).

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

## Где ломается: сводка

- **Чужой индекс** — не индексирует то, что вы думаете. Проверяйте поведение на реальных примерах, не доверяйте документации.
- **Отсутствие отметки позиции** — чтение лент без курсора сжигает лимиты экспоненциально.
- **Фиксированный порядок источников** — при потолке запросов часть источников систематически выпадает, метрики этого не покажут.
- **Проверка лимитов только на старте** — цикл превышает потолок, следующие N проходов отменяются.
- **Метрики без контекста воронки** — зелёный счётчик не означает полноту; точность предскоринга без учёта стоимости ошибки вводит в заблуждение.
- **Ожидание, что модель — слабое звено** — в реальном проде инфраструктура вокруг модели ломается чаще самой модели.

## Что попробовать дальше

Если вы строите похожий пайплайн, три вещи стоит сделать до первого прода:

1. Проверить поведение чужого поискового API на 10-15 реальных примерах с известным результатом — особенно с дефисами, аббревиатурами и составными словами.
2. Реализовать курсор позиции для каждого источника до запуска, а не после первого инцидента с лимитами.
3. Добавить метрику покрытия источников (когда последний раз читалась каждая площадка) отдельно от метрики найденных записей.

---

## FAQ

### Почему промпты не были главной проблемой?

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

### Как выбрать порог отсечения на ступени предскоринга?

Считайте стоимость каждого типа ошибки в деньгах или времени. Если пропущенный тендер стоит дороже, чем лишний тендер на гейте, держите низкий порог и принимайте высокий процент ложноположительных. В описанной системе 75% отказов на гейте с кодом «не наш профиль» — это осознанная цена за минимизацию пропусков.

### Какие модели использовались в стеке?

Три уровня: Claude Haiku 4.5 для предскоринга (дёшево, быстро), Claude Sonnet 5 для оценки по 18 критериям (баланс качества и стоимости), Claude Opus 5 для сборки коммерческого предложения (дорого, используется редко).

### Как отслеживать покрытие источников, а не только найденные записи?

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

### Что делать, если агрегатор не даёт прямой доступ к лентам?

Проверьте, есть ли у агрегатора RSS или Atom-лента, webhook или возможность получить полный список новых записей за период. Если единственный интерфейс — поисковый API, протестируйте его поведение на граничных случаях (дефисы, аббревиатуры, составные слова) и заложите в архитектуру резервный канал — например, прямое чтение двух-трёх ключевых площадок напрямую, как в описанной системе.

## Источники

- Habr ML: Шесть мест, где за два месяца в проде ломался LLM-конвейер над лентами закупок, и почти все вне промптов
