Как устроен AI-сервис маршрутов: очереди без брокера, токеномика и production-компромиссы инди-стартапа

В первой главе я рассказывала, как обычное планирование отпуска превратилось в мини-магистратуру по промпт-инжинирингу. Я хотела просто маршрут по Японии с детьми, а в итоге два дня добывала его из LLM клещами, потом еще отдельно причесывала, форматировала и превращала в документ, который не стыдно распечатать.

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

Во второй главе - не история про “смотрите, мы прикрутили AI к кнопке”. Это история про то, как выглядит AI-продукт, когда вы действительно пытаетесь довести его до продакшена, а не просто записать красивый рилс под музыку из Interstellar.

Где AI начинает кусаться

На бумаге все звучит очень романтично. Пользователь приходит, выбирает город, бюджет, интересы, состав группы, нажимает кнопку — и через пару минут получает красивый маршрут в PDF.

Как устроен AI-сервис маршрутов: очереди без брокера, токеномика и production-компромиссы инди-стартапа

В реальности это не “один запрос к модели”, а длинная цепочка действий: собрать вводные, подобрать правильный сценарий генерации, найти релевантные места, выстроить логистику, не забыть про жилье, еду, сувениры, бюджет, примечания и в конце все это еще и красиво сверстать.

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

Нужно было сделать так, чтобы генерации не терялись, чтобы баланс не списывался дважды, чтобы пользователь мог зайти через разные способы входа, а маршрут все равно оставался его маршрутом, и чтобы фронт не умирал в позе “загрузка…” навсегда.

Именно поэтому в продукте появились вещи, которые редко обсуждают в восторженных постах про AI. Очередь генерации без отдельного брокера. Атомарная работа с токенами. Multi-OAuth как один логический аккаунт. Наблюдаемость без тяжелого APM. И, конечно, отдельная система тестирования, потому что если просто верить LLM на слово, однажды она уверенно отправит вас пить кофе туда, где уже три года шаурма и пункт выдачи.

Почему наш маршрут делает не “одна умная модель”, а целая банда агентов

Как устроен AI-сервис маршрутов: очереди без брокера, токеномика и production-компромиссы инди-стартапа

Самый частый вопрос звучит примерно так: “А что там сложного, один хороший промпт написали и готово?” Очень хочется ответить “да, конечно”, а потом тихо заплакать.

Один огромный промпт - это красиво только в теории. На практике он плохо управляется, плохо дебажится и плохо масштабируется. Если модель ошиблась в одном месте, вы потом не понимаете, где именно она свернула не туда: при подборе жилья, в логистике, в бюджете или уже в самом форматировании.

Поэтому перед релизом мы протестировали больше 20 LLM-вариантов. Причем не только очевидные западные модели, но и российские, и совсем свежие американские, и китайские. Нам было важно не “выбрать самую хайповую”, а понять, кто лучше работает именно на нашей задаче. Оказалось, что универсального чемпиона не существует. Одна модель быстрее, другая точнее, третья лучше ищет и собирает фактуру. В итоге мы используем не одну, а три разные модели под разные типы задач — там, где нам важны скорость, точность или поиск.

Дальше стало еще интереснее. Вместо одного “царь-промпта” мы собрали пайплайн из шести агентов. Это не шесть чатиков ради красоты, а шесть ролей с понятной зоной ответственности. Условно говоря, один агент думает как риелтор и подбирает проживание, другой как экономист считает бюджет, третий как тревел-планер строит логистику, четвертый отвечает за локальные и нишевые места, пятый собирает полезные примечания, а шестой уже приводит результат в аккуратный, читаемый и пригодный для PDF вид.

И тут важный момент: они не просто “по очереди что-то пишут”. У нас есть ветвление сценариев. Автомобильное путешествие и городская поездка - это вообще разные миры. Для автопутешествий нужны одни промпты, одни документы для поиска и одна логика маршрута. Для городских маршрутов — совсем другие. В одном случае важно, где парковка и как не ехать три часа ради одного музея. В другом - где удобнее пересадка, как не загнать пользователя с коляской в ад из лестниц и как уместить в день несколько точек без ощущения, что вы проходите квест “успей умереть до обеда”.

Поэтому у нас нет единого шаблона “сделай красиво”. Для разных типов поездок написаны разные наборы инструкций. Причем это не короткие магические фразы которые можно легко найти на просторах интернета. Минимальная длина промпта у нас — 75 строк, максимальная — 116. Да, буквально текстовый роман. Просто потому что иначе модель начинает делать то, что модели умеют лучше всего: звучать уверенно и врать с хорошей интонацией.

Как устроен AI-сервис маршрутов: очереди без брокера, токеномика и production-компромиссы инди-стартапа

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

В среднем на одного агента у нас выходит примерно:

- 5275 prompt tokens

- 3316 completion tokens

- 8591 total tokens

И это, кстати, хорошо отрезвляет, когда кто-то говорит: “Да это же просто один запрос к нейронке”.

Самое приятное в такой схеме - она лучше объяснима. Если что-то пошло не так, мы хотя бы понимаем, кто именно накосячил. Не “нейросеть сломалась”, а, например, агент по логистике слишком агрессивно уплотнил день, или агент по локальным рекомендациям ушел в слишком туристические места, или форматирующий агент переборщил с украшательством. Для команды из двух человек это очень важно. Когда у тебя нет отдельного отдела AI research, нельзя позволить себе роскошь разбираться с магией. Нужна система, где каждое решение можно хотя бы как-то потрогать руками.

Очереди, токены и login-зоопарк: всё то, о чем не мечтают на демо

Когда пользователь нажимает кнопку “сгенерировать маршрут”, он ожидает простую вещь: что процесс начнется один раз, дойдет до конца и завершится предсказуемо. В реальной жизни пользователь может нажать кнопку пять раз, обновить страницу, потерять интернет, зайти позже с другого устройства и параллельно логиниться через разные провайдеры. И продукт все равно должен вести себя прилично.

Поэтому одна из самых важных инженерных частей у нас — это не сама генерация, а контур вокруг нее. Мы довольно рано поняли, что для нас гораздо важнее надежно переживать долгие операции, чем строить “идеально академическую” архитектуру. Отсюда и решение с очередью генерации без отдельного брокера. Нам нужно было ограничивать число параллельных маршрутов, не терять задачи и иметь возможность поднимать зависшие процессы без поднятия отдельного зоопарка инфраструктуры. Это решение не самое глянцевое, зато прагматичное: для инди-команды важнее, чтобы оно стабильно работало и не съедало бюджет быстрее, чем наши пользователи доедают хинкали.

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

Как мы тестируем то, что по определению любит импровизировать

Самое веселое в AI-продукте — то, что он одновременно должен быть умным и предсказуемым. А нейросети, как известно, обожают творчество в самый неподходящий момент.

Поэтому наше тестирование состоит из трех уровней. И нет, вариант “запустили пару раз, вроде красиво” мы не рассматриваем. Это путь к тому, чтобы однажды маршрут в Париже предложил купить сувениры в Берлине, а вы узнали об этом из разгневанного комментария.

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

Второй уровень — параметризованные программные тесты. Вот тут уже начинается более взрослая история. Мы проверяем длину текста, наличие и отсутствие обязательных сущностей, форматирование, полноту, наличие нужных разделов, корректность структуры, общую дисциплину ответа. По сути это попытка обложить “шумный” LLM-результат детерминированным контрактом. Мы не спрашиваем у теста: “Это вдохновляющий маршрут?” Мы спрашиваем: “Тут вообще есть бюджет? Тут есть жилье? Тут нет запрещенных артефактов? Тут внятная структура? Тут нет обрыва на полуслове?” После этого ставим оценки по шкале от 1 до 10. Это очень приземленный, но полезный слой. Он не делает маршрут великим, зато не дает ему стать бессвязной простыней с красивым заголовком.

И третий уровень — мой любимый. На итоговый маршрут мы запускаем модель-судью. Внутри мы ее между собой называем Судья Дредд, потому что она никого не жалеет и умеет смотреть на результат глазами туриста, а не разработчика, который уже полюбил свой pipeline и простил ему всё. Судья оценивает маршрут по шкале от 1 до 100: насколько рекомендации реально полезны, насколько логичны локации, удобно ли перемещаться, нет ли откровенно слабых мест по доступности, не слишком ли это туристическая жвачка и не отправили ли мы человека в маршрут, который выглядит красиво только на бумаге.

Как устроен AI-сервис маршрутов: очереди без брокера, токеномика и production-компромиссы инди-стартапа

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

Сейчас все наши маршруты получают не менее 92 баллов из 100. И да, это та часть, где можно сделать паузу и довольно кивнуть. Особенно потому, что некоторые модели, которые на витрине выглядят очень бодро, у нас проваливались совершенно драматически. Например, модель от Yandex набирает всего 35 баллов. Упс. Скажем так: если бы это был школьный дневник, мы бы уже вызывали родителей.

Но тут важная оговорка. Судья - тоже модель. А значит, она не бог, не ГОСТ и не последняя инстанция. Это не абсолютная истина, а хороший сигнальный инструмент. Она помогает ловить деградацию и сравнивать версии, но не заменяет человеческую голову. Если смотреть на LLM-judge как на оракул, можно однажды получить очень уверенного оракула, который просто встал не с той ноги.

Поэтому только комбинация трех уровней дает что-то похожее на взрослую систему контроля качества. Сценарные прогоны ловят грубые сбои. Программные тесты держат формат и инварианты. Судья оценивает человеческую полезность. И только вместе это начинает быть похоже не на “мы что-то сгенерировали”, а на продукт.

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

Как устроен AI-сервис маршрутов: очереди без брокера, токеномика и production-компромиссы инди-стартапа

Если коротко, главный урок очень простой: AI-продукт нельзя строить как красивую обертку вокруг одного вызова модели. Как только в уравнении появляются деньги, длинные задачи, разные типы пользователей и ожидание стабильного результата, вам приходится проектировать не “чатик”, а систему.

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

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

И да, ровно в этом месте обычно становится интересно не только разработчикам, но и обычным людям: “Окей, а что у вас в итоге получается на выходе?” Отличный вопрос.

В третьей главе покажу уже сам результат: реальные примеры маршрутов, как выглядит финальный PDF, какие кейсы сервис вытягивает лучше всего и что сказали первые живые пользователи. Там будет меньше разговоров про пайплайны и больше про то, ради чего весь этот зоопарк вообще затевался.

2
Начать дискуссию