Когда смотришь на AI-сервис со стороны, всё выглядит почти обманчиво просто. Есть чат. Есть генерация изображений. Есть видео, аудио, голосовые функции, какие-то дополнительные инструменты. Иногда сверху добавляют ленту, публикации, рейтинги, подборки. На первом экране это выглядит как набор понятных сценариев: выбрал режим, написал запрос, получил результат.
Но как только продукт выходит из режима “мы показали демо и оно один раз сработало”, картина резко меняется. Почти все настоящие проблемы возникают не в точке подключения модели, а в слое между пользователем и нейросетью. Именно там начинают копиться задержки, тяжёлые файлы, фоновые задачи, неудачные генерации, пересечения лимитов, сценарии возврата, проблемы повторного просмотра, рост стоимости и вопросы монетизации.
Именно этот слой мы и строили в Ranvik.
Мы довольно быстро поняли, что не хотим делать просто каталог AI-моделей, где пользователю показывают длинный список названий, а дальше он должен сам догадываться, что из этого подходит, почему одна функция сработала быстро, а другая долго, почему в одном сценарии всё прошло нормально, а в другом расход выглядит неожиданно, и куда вообще делся результат после генерации.
Нам был нужен цельный продукт. Такой, в котором:
-
разные AI-сценарии ощущаются частью одной системы;
-
тяжёлые задачи не ломают быстрые;
-
результат не исчезает после закрытия чата;
-
стоимость поведения продукта можно контролировать;
-
технический сбой не превращается автоматически в пользовательскую проблему.
Поэтому архитектурно мы смотрели на AI-сервис Ranvik не как на “чат с моделями”, а как на систему из нескольких связанных контуров:
-
основной функциональный контур: 1) текст 2) изображения 3) видео 4) аудио 5) голос, 6) инструменты;
-
исполнительный контур: запуск, обработка, статусы, хранение промежуточных и итоговых результатов;
-
контентный контур: публикации, лента, просмотры, реакции, повторное вовлечение;
-
экономический контур: доступ, лимиты, токены, подписки, доппакеты, офферы, компенсации.
Снаружи это выглядит как единый продукт. Внутри это означает довольно много инженерной дисциплины. Ниже расскажу, на каких решениях мы в итоге сошлись и почему они для нас стали важнее, чем просто “подключить побольше AI-функций”.
Не каталог моделей, а единый слой исполнения
Важно сразу честно зафиксировать одну вещь. У нас нет “магической системы”, которая полностью всё решает за пользователя, скрывает выбор и сама подбирает идеальную модель под любую задачу. В ряде сценариев пользователь всё равно выбирает нужный режим, тип генерации и в некоторых случаях модель. И это нормальная продуктовая логика.
Инженерная сложность была не в том, чтобы “отменить выбор”. Она была в том, чтобы выбор пользователя не превращал систему в набор разрозненных интеграций.
То есть мы строили не волшебный слой автоматического выбора, а единый слой исполнения. Пользователь запускает понятный продуктовый сценарий, а дальше сервис берёт на себя всё то, что в реальной эксплуатации обычно и ломает AI-продукт:
-
проверку доступа и ограничений;
-
подготовку входных данных;
-
разбор того, что именно нужно передать в модель;
-
запуск в подходящем режиме исполнения;
-
контроль состояния;
-
сохранение результата;
-
корректную выдачу в UI;
-
учёт расхода;
-
обработку неудачных сценариев.
Эта разница кажется словесной, но на практике она очень важна. Потому что “автоматический выбор модели” звучит эффектно, но не описывает реальную основную работу системы. Реальная работа — это сделать так, чтобы текст, изображение, видео, аудио и контентный слой не жили как пять отдельных продуктов под одним доменом.
В Ranvik мы старались строить именно такой единый продуктовый слой. Не замаскированный каталог API, а сервис, где разные функции имеют общую логику доступа, хранения, публикации результата и поведения при ошибках.
Основной функционал: разные типы задач, а не разные вкладки одного и того же
Если смотреть на интерфейс, может показаться, что текст, изображение, видео и аудио — это просто разные вкладки одного и того же механизма. Но с инженерной точки зрения это вообще разные классы задач.
У текста важны интерактивность, стриминг ответа, скорость первого символа, удобная история чатов, повторное использование контекста и работа с вложениями. У изображений критичны работа с исходниками, превью, вариации, повторное открытие результата и подготовка файлов для дальнейших сценариев. У видео — длинный жизненный цикл, асинхронность, высокая цена ошибки, более тяжёлое хранение и большая чувствительность пользователя к задержкам. У аудио и голосовых сценариев есть свои особенности: длительность, работа с референсами, отдельная логика хранения и повторного использования.
Из-за этого нам было важно как можно раньше перестать думать о продукте как о “едином вызове генерации”. Технически текст, изображения, видео и аудио должны вести себя по-разному не потому, что так захотелось архитектурно, а потому что так устроена реальная нагрузка.
Примеры того, как это влияет на систему:
-
текстовые сценарии ближе к интерактивному режиму и сильнее завязаны на отзывчивость интерфейса;
-
изображения часто дают быстрый результат, но создают больше производных объектов: превью, версии для публикации, повторный просмотр;
-
видео почти всегда требует более длинного жизненного цикла и отдельного отношения к статусу выполнения;
-
аудио и голосовые функции сильнее завязаны на входные артефакты и повторное использование результата.
Именно поэтому у нас с самого начала была не одна “кнопка генерации” внутри системы, а несколько контуров поведения под разные классы задач. Для пользователя это не всегда заметно. Но именно это позволяет продукту вести себя цельно, а не ломаться на каждом тяжёлом сценарии.
Главная проблема AI-продукта под нагрузкой — не модели, а смешение быстрых и тяжёлых сценариев
Пока сервисом пользуются внутри команды, почти всё выглядит терпимо. Даже если тяжёлая генерация иногда занимает слишком много времени, а где-то интерфейс “подвисает”, это легко списать на рабочие нюансы. Но когда в продукт приходит реальная аудитория, очень быстро выясняется, что разные типы AI-задач нельзя пускать в один и тот же режим эксплуатации.
Есть сценарии, где пользователь ожидает почти мгновенного отклика. Для него чат — это разговор. Он не должен думать о внутренней сложности пайплайна, времени подготовки, особенностях провайдера и т.д. Если первый отклик затягивается, пользователь воспринимает это как тормозящий сервис.
Есть сценарии, где пользователь морально готов ждать, но взамен ожидает предсказуемого статуса. Особенно это касается тяжёлых медиа-задач. Когда речь идёт о видео или сложной генерации, человеку важно хотя бы понимать: задача реально принята, работа идёт, результат не потерян.
Если все эти сценарии вести одинаково, продукт очень быстро начинает деградировать:
-
быстрые пользовательские действия начинают страдать из-за тяжёлых задач;
-
тяжёлые задачи создают ощущение зависшего интерфейса;
-
система накапливает больше незавершённых операций;
-
сложнее управлять таймаутами и ретраями;
-
пользователь перестаёт понимать, что именно сейчас происходит.
Поэтому одно из наших ранних архитектурных решений было простым: мы не старались заставить все AI-сценарии выглядеть одинаково на исполнительном уровне.
Там, где можно, мы стараемся сохранить почти интерактивное ощущение. Там, где сценарий объективно тяжёлый, продукт сразу работает с ним как с длинной операцией: сохраняет статус, позволяет пережить более долгий цикл выполнения, не мешает остальной части системы.
Это важная вещь ещё и потому, что в AI-продуктах нагрузка распределяется очень неравномерно. Один пользователь может создать несколько сравнительно лёгких запросов. Другой в это же время запускает дорогие тяжёлые сценарии, которые по своему “весу” кратно отличаются от обычного использования. Значит, продукт должен быть спроектирован так, чтобы не ломать нормальное поведение ради поддержки крайних случаев, но и не терять эти крайние случаи по дороге.
Именно поэтому вопрос нагрузки для нас был не просто вопросом “ускорить запросы”. Это был вопрос правильного разделения жизненных циклов.
Хранение: в AI-сервисе оно почти сразу становится отдельной инженерной задачей
Во многих веб-продуктах хранение кажется достаточно прямолинейным: есть база, есть какие-то файлы, есть связки между ними. В AI-продукте эта схема довольно быстро перестаёт быть достаточной.
Почти каждое действие пользователя может порождать не один объект, а сразу цепочку:
-
исходный файл пользователя;
-
рабочую версию, которую удобнее или безопаснее использовать в модели;
-
превью;
-
служебные производные;
-
итоговый артефакт;
-
публичную версию для контентного контура;
-
набор метаданных, которые нужны не пользователю, а самой системе.
Если всё это складывать без разделения ролей, очень быстро начинаются типовые проблемы:
-
база обрастает тяжёлыми данными, которые ей не нужны;
-
прикладной backend начинает участвовать в задачах, которые не должен обслуживать напрямую;
-
любой экран с контентом начинает тянуть за собой слишком тяжёлую выдачу;
-
служебные данные смешиваются с пользовательскими;
-
усложняется повторное использование уже существующих артефактов.
Поэтому в Ranvik AI мы довольно рано разделили хранение по ролям. Тяжёлые медиа и производные файлы живут в отдельном файловом контуре. А прикладная часть системы в основном оперирует метаданными, ссылками, статусами, превью и облегченными представлениями.
Это решение даёт сразу несколько эффектов.
Во-первых, основной backend не превращается в монолит, который сам же должен обслуживать и бизнес-логику, и тяжёлую выдачу медиа.
Во-вторых, один и тот же результат можно использовать в разных слоях продукта без дублирования всей логики.
В-третьих, мы можем более спокойно масштабировать контентную часть, не таща за собой всю остальную систему.
Особенно важно это для сценариев, где результат должен жить дольше одного запроса. У нас один и тот же объект может участвовать сразу в нескольких контекстах:
-
как результат внутри чата;
-
как материал для повторного просмотра;
-
как вход для следующей генерации;
-
как опубликованный элемент в ленте;
-
как объект для фоновой обработки;
-
как источник аналитических и продуктовых сигналов.
Именно поэтому “куда складывать файлы” у нас быстро превратилось из технической мелочи в часть архитектуры жизненного цикла результата.
Почему в AI-продукте нельзя просто “считать баланс” и на этом успокоиться
Один из самых недооценённых вопросов в AI-сервисах — это экономика выполнения.
Когда люди снаружи видят AI-продукт, они часто представляют это так: пользователь запустил запрос, система посчитала стоимость, потом списала токены, баллы или лимит. На демо этого действительно может хватить. Но в реальном продукте почти сразу появляются нюансы:
-
можно ли безопасно запускать задачу ещё до фактического выполнения;
-
как не пустить пользователя в заведомо опасный по расходу сценарий;
-
как учитывать длинные задачи;
-
как сводить предварительную оценку и фактический расход;
-
что делать, если задача технически стартовала, но закончилась не так, как ожидалось.
Если подходить к этому грубо, продукт начинает вести себя либо слишком жёстко, либо слишком рискованно.
Слишком жёсткий вариант — это когда система перестраховывается и начинает блокировать нормальные пользовательские сценарии просто потому, что ей страшно ошибиться в оценке.
Слишком рискованный вариант — это когда система позволяет запускать всё подряд, а потом уже “по факту” пытается разобраться, что с этим делать. В AI-сервисе с тяжёлыми сценариями это плохая идея. Потому что одна неудачная операция может быть заметной не только в UX, но и в экономике продукта.
Поэтому вокруг генераций у нас появился отдельный контур контроля стоимости.
Без лишнего раскрытия внутренней кухни его можно описать так:
-
До старта система старается понять, безопасно ли вообще запускать сценарий в текущем контексте.
-
Во время выполнения фиксируются важные этапы и промежуточные значения.
-
После завершения происходит финальная сверка.
-
Если задача закончилась технически неудачно, это должно учитываться не только как ошибка исполнения, но и как ошибка расхода.
И вот здесь появляется один из самых важных для нас продуктовых принципов: технический сбой не должен автоматически становиться пользовательской проблемой.
Для пользователя “генерация не удалась” — это одна неприятность. Для продукта это на самом деле две разные неприятности: проблема выполнения и проблема биллинга. Если решить только первую, пользователь всё равно будет воспринимать продукт как несправедливый.
Именно поэтому в системе появилась компенсационная логика. Если тяжёлый сценарий дал пустой результат, был прерван, упал по таймауту или завершился аварийно, система должна корректно это отразить. Не просто показать ошибку, а ещё и закрыть экономическую сторону вопроса.
Это не маркетинговая “забота о пользователе”. Это один из фундаментальных механизмов доверия к AI-продукту.
Монетизация: когда подписка — это не витрина, а часть архитектуры
В AI-сервисе монетизация очень быстро перестаёт быть просто страницей с тарифами. Причина в том, что у разных сценариев очень разная себестоимость, очень разный профиль использования и очень разная цена неудачного запуска.
Если строить монетизацию слишком упрощённо, продукт начинает ломаться в обе стороны:
-
либо пользователь получает слишком жёсткие ограничения и начинает воспринимать сервис как “всё время что-то не даёт”;
-
либо продукт сам становится экономически неуправляемым, потому что тяжёлые сценарии расходуются слишком свободно.
Поэтому для нас подписка и токены — это не просто “как показать цену”. Это часть архитектуры поведения сервиса.
По сути, у нас есть несколько уровней управления доступом:
-
базовый доступ к продукту;
-
подписочный контур;
-
дополнительные расходные единицы для тяжёлых сценариев;
-
офферы и ценовые адаптации;
-
безопасное поведение в неудачных сценариях.
Нам было важно, чтобы монетизация не ощущалась как внешняя надстройка, которая начинает мешать продукту только после нажатия кнопки “оплатить”. Она должна быть встроена в саму логику сценариев.
Что это даёт:
-
тяжёлые задачи не стартуют “вслепую”;
-
пользователь понимает, где у него базовый доступ, а где дополнительный расход;
-
можно гибче управлять предложениями и офферами;
-
локальные платёжные сценарии становятся частью общей продуктовой логики, а не отдельным миром;
-
компенсация при неудачных сценариях остаётся технически и продуктово осмысленной.
Если говорить совсем по-простому, мы строили монетизацию не как “как списать деньги”, а как способ сделать дорогие AI-сценарии управляемыми, не убивая при этом пользовательский опыт.
Почему у нас вообще появилась лента и зачем она продукту
Если результат генерации живёт только внутри чата, его жизнь очень короткая. Человек получил ответ, посмотрел, скачал, закрыл экран и пошёл дальше. Для утилиты этого достаточно. Для продукта — не всегда.
Нам было важно, чтобы результат не заканчивался в точке, где модель отдала ответ. Поэтому в Ranvik AI появился отдельный контур публикации и потребления контента — лента.
Но здесь важно подчеркнуть: это не “социальный раздел для красоты” и не декоративная функция рядом с AI. Лента у нас выполняет вполне прагматичную роль:
-
продлевает жизнь результата;
-
даёт пользователю повод вернуться;
-
превращает разовую генерацию в более длинный пользовательский цикл;
-
создаёт контур повторного вовлечения;
-
позволяет результату существовать вне одного чата.
Как только в системе появляется лента, продукт перестаёт быть только инструментом моментального использования. Он начинает работать как среда, в которой результаты не только создаются, но и живут дальше.
А это уже означает отдельные инженерные требования:
-
нужно уметь отдавать контент дёшево и быстро;
-
нужно не тянуть лишние данные в каждый экран;
-
нужно правильно организовывать пагинацию;
-
нужно аккуратно считать просмотры, реакции и другие события;
-
нужно учитывать, что значительная часть потребления идёт с мобильных устройств и не всегда по идеальной сети.
По сути, лента стала для нас вторым продуктовым слоем над генерацией. Первый слой — это “создать”. Второй — это “дать результату жить”.
Как мы работаем с кэшированием и почему это напрямую связано с UX и нагрузкой
Когда в продукте одновременно есть AI-сценарии, контентный слой и публичные экраны, схема “всегда заново запрашивать всё с сервера” очень быстро становится дорогой и по времени, и по инфраструктуре.
Поэтому в Ranvik мы кэшируем там, где это действительно даёт эффект и не ломает актуальность данных.
На стороне клиента для ряда публичных и контентных экранов мы используем SSR-ориентированный подход и локальное кэширование состояния. Это даёт несколько практических плюсов.
Во-первых, интерфейс открывается быстрее и не выглядит пустым при первом заходе. Для продуктовых экранов это особенно важно: пользователь не должен видеть голый каркас там, где логично ожидать готовую выдачу.
Во-вторых, повторные переходы по одним и тем же срезам не превращаются в лишнюю серию сетевых запросов. Для ленты это особенно чувствительно, потому что человек может возвращаться к категориям, авторам, тегам и уже просмотренным выдачам.
В-третьих, кэширование помогает разгрузить backend на наиболее повторяемых сценариях чтения, не вмешиваясь в чувствительные персональные данные.
При этом мы не старались кэшировать всё подряд. В AI-продукте это опасная стратегия: можно легко закэшировать то, что должно оставаться живым и чувствительным к текущему состоянию пользователя. Поэтому подход у нас был скорее прикладной: публичные и контентные данные — да, персонально-чувствительные и часто меняющиеся вещи — аккуратно и избирательно.
Отдельная история — просмотры и служебные события в ленте. Такие события очень легко превращаются в “мелкий шум”, который по одному запросу кажется безобидным, а в сумме создаёт довольно неприятную нагрузку. Поэтому там, где возможно, мы собираем их пакетно и стараемся отправлять лёгким способом, не мешая основному UX. Это одновременно и про экономию запросов, и про ощущение “экран работает плавно”.
Если совсем коротко, то наша логика была такой: чтение нужно делать максимально дешёвым, а запись служебных событий — аккуратной и не навязчивой. Иначе контентный слой быстро начинает тянуть на себя непропорционально много ресурсов.
Лента как технический объект, а не просто как продуктовая идея
Как только лента перестаёт быть “одной общей страницей”, а начинает включать срезы по категориям, авторам, тегам, просмотрам, похожим материалам и персональным действиям, она превращается в отдельную техническую систему.
Нам важно было сделать так, чтобы этот слой:
-
не перегружал backend лишними запросами;
-
не дублировал одни и те же данные в нескольких местах;
-
не заставлял клиента каждый раз собирать состояние заново;
-
хорошо вёл себя на длинных сессиях;
-
не ломался от роста числа медиа-объектов.
Поэтому лента, назовем её “сообщество ИИ” у нас строилась не как “один длинный список постов”, а как набор управляемых выдач со своим состоянием, курсорами, кэшем, отдельными слоями чтения и аккуратной обработкой пользовательских действий.
Именно поэтому для нас лента стала не “дополнительной фичей”, а частью общей архитектуры продукта. Потому что там пересекаются и хранение, и кэширование, и работа с медиа, и продуктовые сигналы, и повторное вовлечение.
Поддержка и наблюдаемость: без них AI-сервис быстро превращается в черный ящик
Ещё одна вещь, которую снаружи почти не видно, но которая очень быстро становится критичной, — это поддержка и наблюдаемость.
AI-продукты редко ломаются в бинарной логике “работает / не работает”. Гораздо чаще возникают промежуточные ситуации:
-
результат технически завершился, но выглядит пустым или слабым;
-
тяжёлый сценарий шёл слишком долго;
-
пользователь не понял, что задача всё ещё выполняется;
-
расход выглядит неожиданным;
-
контент получился, но дальше неправильно встроился в продуктовый цикл.
Если система в таких случаях не оставляет достаточно сигналов, поддержка превращается в гадание. А это плохой путь: время реакции растёт, продуктовая команда не понимает, что именно пошло не так, а пользователь получает ощущение непрозрачного сервиса.
Поэтому мы старались проектировать сервис так, чтобы важные этапы были наблюдаемыми. Не в смысле раскрытия всей внутренней кухни наружу, а в смысле того, чтобы продукт сам мог отвечать на практические вопросы:
-
что именно запускалось;
-
в каком контексте это происходило;
-
до какого этапа дошёл сценарий;
-
чем он завершился;
-
что произошло с результатом;
-
как это повлияло на расход и пользовательский опыт.
Именно из таких приземлённых вещей и собирается надёжность. Не из одного большого “серебряного решения”, а из набора осмысленных практик: разделение жизненных циклов, аккуратное хранение, наблюдаемые стадии, корректный контур компенсации и отдельный слой поддержки.
Обычно эти вещи хуже всего видны в красивом демо. Но именно они и определяют, получится из AI-функции реальный продукт или нет.
Что оказалось самым сложным
Если убрать любые маркетинговые формулировки, самые сложные места были вполне практичными.
Во-первых, постоянный баланс между скоростью и устойчивостью. Пользователь всегда хочет, чтобы всё было быстрее. Система почти всегда хочет вести себя безопаснее. И почти никогда нельзя одновременно максимизировать обе стороны.
Во-вторых, необходимость проектировать сервис так, как будто каждая тяжёлая задача может завершиться неидеально. Не потому, что всё плохо, а потому что такова реальность AI-сценариев. Если строить архитектуру только вокруг счастливого пути, продукт очень быстро начнёт раздражать людей при реальном использовании.
В-третьих, оказалось, что хранение и жизненный цикл медиа нельзя считать второстепенной задачей. Для мультимедийного AI-продукта это один из несущих слоёв.
В-четвёртых, контентная часть оказалась важнее, чем может показаться на старте. Если результат нигде не живёт после генерации, продукт становится очень коротким по пользовательскому циклу.
И в-пятых, монетизация в AI-продукте — это архитектурная тема. Если её отнести “куда-нибудь потом в бизнес-слой”, продукт очень быстро начнёт конфликтовать сам с собой.
Что мы из этого вынесли
Главный вывод у нас довольно простой: хороший AI-сервис строится не вокруг количества моделей, а вокруг качества слоя между пользователем и генерацией.
Можно подключить сильные модели и всё равно получить слабый продукт, если:
-
быстрые и тяжёлые сценарии смешаны в одну исполнительную логику;
-
хранение медиа не продумано;
-
лента и контент не встроены в жизненный цикл результата;
-
кэширование сделано либо слишком агрессивно, либо вообще никак;
-
монетизация не связана с реальной экономикой сценариев;
-
а ошибки исполнения напрямую бьют по пользователю.
И наоборот, даже довольно сложный набор AI-функций можно собрать в цельный сервис, если думать не только о том, как что-то сгенерировать, но и о том, как эта генерация живёт дальше:
-
как она запускается;
-
как переживает тяжёлые сценарии;
-
где и как хранится;
-
как попадает в контентный слой;
-
как учитывается её стоимость;
-
как система ведёт себя, если что-то пошло не так.
Именно это мы и старались делать в Ranvik. Не строить “ещё один AI-чат”, а собрать сервис, который нормально ведёт себя под реальной нагрузкой, с тяжёлыми медиа, длинными сценариями, лентой, монетизацией, ограничениями и живой аудиторией.
Автор: Ranvik


