Все записи
6 мин

Нейросеть-провайдер затупила — и платные расклады молча превратились в мусор

claude codeнейросетиBuilding in Public

Если у вас приложение генерит контент через стороннюю нейросеть и человек за этот контент платит токенами, то однажды вы упрётесь в простую и максимально подлую штуку. Провайдер затупил, ответ пришёл обрезанный или вообще не пришёл, а токены вы уже списали, и пользователь вместо своего платного разбора видит кривой мусор, и больше к вам не возвращается. У меня в Картаре ровно это и случилось 24 июня, прямо во время слива трафика, и день дал 37 раскладов при нуле регистраций именно потому, что разборы людей не впечатлили. Чинил я это так: авто-ретрай генерации на бэке при таймауте провайдера, детектор деградаций модели, починка уже битых записей с возвратом токенов пострадавшим и вывод этих 503-ошибок в админку, чтобы ловить тормоза провайдера заранее. Ниже расскажу, как до этого дошло и почему оно вообще ловится так поздно.

Тихая катастрофа, которую не видно в логах деплоя

Самое мерзкое в таких сбоях — что они тихие. Контейнер up, healthcheck зелёный, деплой прошёл без единой красной строчки, всё как надо. А внутри у двух людей в базе вместо натального разбора и вместо нумерологии лежит текст ошибки, и токены за это списаны. То есть формально система работает, по метрикам ничего не горит, а по факту человек заплатил и получил мусор. И узнаёшь ты об этом не из мониторинга, а из того, что льёшь трафик и видишь 37 раскладов и ноль заявок, и начинаешь руками копать, почему так.

А копать пришлось потому, что причина была не одна. Часть свежих сбоев — это OpenRouter тормозит или режет ответ на полуслове прямо в момент генерации. А часть — это вообще старая болячка: натальные разборы били из-за того, что ответ упирался в лимит на 6000 токенов и обрезался, и на выходе получался невалидный JSON, который приложение не могло разобрать. То есть один и тот же симптом, кривой разбор, приходил с двух разных сторон, и пока эти два случая не разделишь, чинить бесполезно.

Почему первопричину чинить нельзя, а жить с ней надо

Тут важный момент, который я для себя проговорил прямо в процессе: первопричина в том, что OpenRouter тормозит, а это не в моей власти. И это правда. Я не могу прийти в OpenRouter и сказать «перестаньте лагать», когда у вас всплеск нагрузки. Когда ты сидишь на чужой нейросети как на провайдере, а я туда переехал не от хорошей жизни, про это была отдельная история, как два проекта встали, потому что кончились деньги на OpenAI, то ты принимаешь, что провайдер иногда будет деградировать, и это вне твоего контроля. Вопрос не в том, как сделать, чтобы он не тормозил, а в том, как сделать, чтобы его тормоза не превращались в катастрофу для пользователя.

И вот в этой формулировке всё и кроется. Я перестал воевать с провайдером и начал строить буфер между ним и человеком, чтобы было так, как я себе это в итоге и сформулировал: их деградация теперь равна вежливому сообщению плюс деньги целы, а не равна катастрофе. Разница на словах вроде небольшая, а на деле это разница между «юзер ушёл навсегда» и «юзер пожал плечами и попробовал ещё раз».

Что мы с нейросетью в итоге собрали

Дальше я сел и расписал Claude Code, что делаем, а он это оркестрировал в код, потому что сам я код не пишу, я тут режиссёр, а не машинистка. Получилось четыре куска, и каждый закрывает свою дырку.

Первое и главное — авто-ретрай на бэке. Если при генерации словили transient-таймаут от OpenRouter, бэк не падает сразу, а тихо пробует ещё раз, один-два раза. И вот это самое жирное по эффекту, потому что большинство сбоев после этого просто становятся невидимыми. Провайдер моргнул на первой попытке, а вторая прошла, и человек даже не узнал, что что-то было. Раньше любой такой моргнул означал убитый разбор, а теперь это просто лишняя секунда ожидания.

Второе — детектор деградаций модели. Это чтобы отличать «провайдер тупит прямо сейчас» от нормальной работы и не списывать токены за заведомо битый ответ. Когда ответ приходит обрезанный или явно невалидный, система это видит и обрабатывает как сбой, а не пихает мусор в базу как готовый разбор.

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

Четвёртое — вывел эти 503-ошибки в админ-раздел «Ошибки», чтобы в следующий раз я не узнавал про тормоза провайдера по косвенным признакам типа просевших регистраций, а видел их сразу, на ранней стадии. Это та самая видимость, которой не хватало: симптом теперь не прячется внутри тихих записей, а вылезает мне в лицо в админке.

Бэкап перед тем как лезть в прод

Отдельно скажу про то, что обычно опускают в таких рассказах. Перед тем как трогать боевую базу, а перегенерация, обнуление текстов и возврат токенов — всё это пишет в прод, я сделал бэкап БД. Снимок до всех изменений, чтобы если я или нейросеть что-то напортачим при починке битых записей, было откуда откатиться. Дамп вышел небольшой, мегабайт на двенадцать, минута дела, но это та минута, без которой нельзя. Когда лезешь руками возвращать людям деньги и переписывать их записи, цена ошибки — это уже не баг в коде, а ещё больше испорченных людей. Так что бэкап тут не «на всякий случай», а часть процедуры.

Что я из этого вынес

Главный вывод даже не технический. Когда строишь продукт на нейросетях для бизнеса и часть продукта генерится в рантайме чужой моделью, ты обязан закладывать, что эта модель будет тупить, обрезать ответ и отдавать невалидный результат — не «может быть когда-нибудь», а регулярно, как погода. И вся разработка с помощью нейросети тут не отменяет инженерную дисциплину, а наоборот её требует: ретраи, детекторы битых ответов, видимость сбоев в админке, возврат денег за непредоставленную услугу. Это не фичи на потом, это базовая гигиена, без которой автоматизация на ИИ превращается в автоматическую раздачу мусора платящим людям.

И второе, чисто по-человечески. Цифра 37 раскладов и ноль регистраций — она же не про то, что трафик плохой. Она про то, что люди пришли, попробовали, получили кривой разбор и ушли, и ты этого даже не видел, пока не начал копать. Один мой комментарий по ходу разбора был коротким и злым: юзеры видят ерунду, конечно не вернутся. Вот и весь маркетинг. Можно сколько угодно лить трафик и крутить воронку, но если в момент истины человек получает обрезанный мусор вместо того, за что заплатил, дальше можно не считать. Так что если у вас в проекте есть платный шаг, который держится на стороннем LLM, идите и проверьте прямо сейчас: что видит пользователь, когда провайдер моргнул? Если ответ «обрезанный JSON и списанные токены», то у вас уже сейчас тихо утекают люди, вы просто пока про это не знаете.