6 месяцев боли с AI-агентами в продакшне: что я понял про архитектуру
Тесты — 94%. Production — 4 инцидента за полгода. Тихие сбои, галлюцинации в числах, бесконечные циклы. Проблема не в модели — в отсутствии execution layer.
Январь. AI-агент выходит в production. Классификация клиентских заявок, извлечение данных, проверка в базе, генерация ответа. Один промпт, шесть инструментов, линейная цепочка. Тесты — 94% точности. Demo для руководства — без единой ошибки.
К июлю: четыре инцидента, 340 часов инженерного времени на тушение пожаров и полная перестройка архитектуры. Не потому что модель плохая — Claude Sonnet работал отлично. Потому что всё вокруг модели было построено неправильно.
Хроника шести месяцев. Каждый инцидент — урок, за который я заплатил временем и нервами.
Месяц 1-2: медовый месяц и первый удар
Первые недели — эйфория. 50 заявок в день, стабильность 89%. Ниже тестовых 94%, но реальные данные грязнее — ожидаемо. Поправили промпт, подняли до 91%.
Потом обнаружили случайно: агент перестал вызывать check_compliance. Не на всех заявках — на 40%. Без ошибки. Без предупреждения. Просто пропускал шаг и шёл дальше.
127 заявок за 8 дней — без compliance-проверки.
Что случилось: Anthropic выкатили минорный апдейт Claude. Без анонса, без changelog для нашей версии API. Поведение модели чуть сдвинулось — она стала «оптимизировать» цепочку вызовов. Промпт говорил «проверь compliance». Для обновлённой версии это оказалось рекомендацией, а не требованием. В некоторых случаях модель решила, что проверка «очевидно не нужна».
Урок: промпт — не контракт. Это рекомендация, которую модель выполняет вероятностно. Минорный апдейт провайдера меняет вероятности без вашего ведома. Всё, что критично для бизнеса, должно быть гарантировано кодом — не текстом промпта.
Месяц 3: уверенная ложь в цифрах
Клиент написал: «Переведите 15 000 рублей.» Агент извлёк сумму: 150000. Не баг парсинга — галлюцинация. Модель прочитала «15 000» и вернула другое число.
Промпт содержал инструкцию: «Проверь корректность извлечённых данных.» Модель «проверила» — и подтвердила собственную ошибку. Она была уверена в числе. Проверка не нашла того, чего для неё не существовало — ошибки.
Три заявки с неправильными суммами за неделю. Одну поймали. Две — нет.
Урок: LLM не может валидировать свой output. Просить модель проверить собственный результат — как просить человека увидеть собственную слепую зону. Валидация должна быть внешней: Pydantic, JSON Schema, regex — в коде, не в промпте.
class ExtractedAmount(BaseModel):
amount: float
@field_validator("amount")
@classmethod
def reasonable_range(cls, v):
if v <= 0 or v > 5_000_000:
raise ValueError(f"Amount {v} outside valid range")
return round(v, 2)После внедрения Pydantic: ноль ошибок в суммах за четыре месяца. Модель по-прежнему иногда галлюцинирует — но валидатор ловит и отправляет на retry с конкретной ошибкой. Модель получает обратную связь от кода, не от себя — и исправляет.
Месяц 4: $12 за одну заявку
Пятница, 17:40. Алерт: аномальный spike расходов на API. Один запрос сгенерировал 38 вызовов инструмента за 4 минуты.
Агент вошёл в цикл. Инструмент возвращал ошибку, агент повторял. Промпт: «Если ошибка — попробуй с другими параметрами.» Модель пробовала — формально с другими: переставила пробел, изменила регистр. Суть та же. Ошибка та же. 38 раз. Стоимость одной заявки: $12. При средней $0.15. В 80 раз дороже.
Урок: retry без ограничений — бомба замедленного действия. Промпт не задал максимум попыток. Не определил, что значит «другие параметры». LLM интерпретировал буквально и минимально.
for attempt in range(3):
result = await tool.call(params)
if result.success:
break
params = mutate_params_meaningfully(params, result.error)
await asyncio.sleep(2 ** attempt)
else:
await escalate_to_human(state)Десять строк. Три попытки, конкретная мутация параметров, backoff, fallback на человека. Бесконечных циклов больше нет.
Месяц 5: маршрут в никуда
VIP-клиент. Заявка — одновременно жалоба (ошибка в счёте), срочная (дедлайн завтра) и содержит вопрос (как исправить). Три категории, три возможных маршрута.
Промпт: «Жалобу — в отдел качества. Срочное — эскалируй. Вопрос — в FAQ.» Без приоритетов. Без инструкции, что делать, когда всё сразу. Агент отправил в отдел качества. Без пометки «срочная». Без ответа на вопрос. VIP-клиент ждал сутки вместо эскалации за 2 часа.
Урок: бизнес-правила с приоритетами нельзя описать в промпте однозначно. Когда условия конфликтуют — модель выбирает одно и игнорирует остальные.
def route(state):
pri = state["classification"]["priority"]
flags = state["classification"]["flags"]
if pri >= 4: return "escalate" # Первый приоритет
if "complaint" in flags: return "quality_team" # Второй
if "question" in flags: return "faq" # Третий
return "standard" Явный приоритет. Тестируемо: 15 unit-тестов покрывают все комбинации. Промпт нельзя протестировать как функцию.
Месяц 6: перестройка
Четыре инцидента — четыре разных симптома, одна причина. Весь execution logic жил в промпте: порядок шагов, валидация, retry, маршрутизация, состояние. 2 800 токенов инструкций, которые LLM выполнял вероятностно.
Перестроил за две недели. Два слоя:
Execution engine (Python + LangGraph): граф шагов — порядок гарантирован; Pydantic — валидация каждого output; retry с backoff — три попытки, таймаут, fallback; conditional edges — маршрутизация по правилам; TypedDict — управление состоянием.
LLM (только мышление): классифицировать заявку, извлечь данные из текста, сгенерировать ответ, оценить неоднозначную ситуацию.
Промпт: с 2 800 до 300-400 токенов на шаг. Одна задача — один промпт. LLM не управляет выполнением.
Результат
Стабильность: месяцы 1-5 — 72-84%, месяц 6+ — 97%.
Инциденты: 4 за 5 месяцев → 0 за 6 месяцев.
Обнаружение сбоя: 1-8 дней → <5 минут.
Отладка edge case: 2-4 часа → 15-30 минут.
Апдейт модели: риск инцидента → без влияния.
Ноль инцидентов за полгода. Модель по-прежнему иногда возвращает невалидный JSON, галлюцинирует число. Но Pydantic ловит, retry исправляет, граф гарантирует порядок. Ошибки модели компенсируются кодом.
Пять правил, выученных болью
1. Модель — вероятностный исполнитель. Промпт — рекомендация. Апдейт провайдера меняет поведение без предупреждения. Критичное — в коде.
2. LLM не валидирует свой output. Pydantic, JSON Schema, regex — в коде. Каждый output проверяется до использования.
3. Retry без лимита — $12 за запрос. Три попытки. Backoff. Таймаут. Fallback. Десять строк кода.
4. Маршрутизация с приоритетами — if/elif/else. Однозначно, тестируемо, предсказуемо.
5. Мониторинг каждого шага. Не только результата. Какие инструменты вызваны? Что вернули? Прошла ли валидация? Без этого тихий сбой обнаруживается через неделю.
Что сказал бы себе в январе
Не выводи агента в production без execution layer. Не промпт, который описывает процесс — а код, который его контролирует.
Тесты покрывают happy path. Production подкидывает комбинации, которые ты не предусмотрел. Модель, оставленная без контроля, обработает их творчески. Иногда — гениально. Иногда — на $12 за запрос. Иногда — молча пропустив compliance-проверку на 127 заявках.
Промпт — для мышления. Код — для выполнения. Шесть месяцев и четыре инцидента, чтобы усвоить вещь, которая звучит очевидно постфактум.