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

Демо у кальянщика прошло, а у друга упало в 500: я недоделал миграцию на OpenRouter

Building in Publicmvpнейросети

Если совсем коротко, то вышло так: я показал свой продукт живому кальянщику, ему зашло, он обещал показать руководству, я выдохнул и тут же дал тот же самый QR-код другу пощупать, а у друга экран завис на лоадере и упал в 500 прямо на первой загрузке. И самое обидное, что у меня-то всё открывалось нормально, я ведь проверял по сто раз. А причина оказалась даже не в новом баге, а в моей же недоделанной работе из этой же сессии: я перевёл проект с OpenAI на OpenRouter, но переключил не всё, и каждая генерация микса сначала тратила секунды три-пять на провальный запрос в старый OpenAI, ловила фейл, и только потом шла в OpenRouter. У меня на быстром интернете эти секунды просто растворялись, я их физически не замечал, а у друга в кальянной на медленном LTE они сложились в таймаут на пятнадцать секунд и краш. Дальше честно по шагам, что было, как искали и что в итоге пришлось чинить.

Демо, которое наконец-то зашло

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

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

А у друга — долгий лоадер и 500

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

Знаете это ощущение, когда продукт только что похвалили, а через пять минут он у живого тестера разваливается на ровном месте? Вот это оно и было. И хорошо ещё, что это был друг, а не тот самый кальянщик с его руководством, потому что если бы 500-ю словили они, то второго шанса показать могло и не быть.

Лезем в логи прода

Гадать на кофейной гуще я не стал, полез в логи прода вместе с Claude Code, он у меня и копает, и формулирует гипотезы, а я уже разруливаю, что делать дальше. И там вылезла такая картина, что я сам себе высказал примерно всё, что думаю, в духе «мы же в этой же сессии перешли на OpenRouter, какой ещё OpenAI, ты что, не всё перевёл?». Перевёл, как выяснилось, и правда не всё.

А логика провала оказалась тупой до обидного. Я переключал проект со старого LLM-провайдера на OpenRouter, в основном переключил, но один путь генерации остался смотреть на старый адрес, и из-за этого каждый запрос микса сначала честно шёл в OpenAI, ждал, ловил отказ за те самые три-пять секунд, и только после этого уходил в OpenRouter, где уже нормально отрабатывал. Полезной работы ноль, а время горит на каждом запросе.

И вот тут вступает в игру разница в железе под пальцами. У меня дома и в машине быстрый интернет, и эти лишние секунды ожидания на фоне общей генерации просто не читались, я их вообще не замечал, а у друга был медленный LTE в помещении кальянной, где сигнал и так не блещет. Сама генерация микса заявлена примерно на пятнадцать секунд, и когда сверху накидываются ещё несколько секунд на бессмысленный провальный запрос плюс общая медлительность канала, всё это перелезает за таймаут и отдаёт 500. То есть баг сидел не в коде самой логики, а в том, что я оставил недоделанную миграцию и проверял её только в тепличных условиях.

Второй баг, который вылез заодно

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

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

Что в итоге чинил

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

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

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

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

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

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

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

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