Алексей Маркин рабочий журнал

ищет по заголовкам и тексту записей и заметок

запись 4 сентября 2026 г. · 14 мин
Все статьи

Модель выбирает, арифметика считает: как устроен geopilot

Разбор travel-каталога, где нейросеть подбирает места и порядок обхода, но ни одного числа не считает: почему роли разделены именно так, чем доказывается фотография места, как проверяется вид транспорта на отрезке и что из этого ломалось на проде.

У travel-каталога есть соблазн, перед которым трудно устоять: отдать модели всё. Пусть она подберёт места, разложит их по дням, посчитает дорогу и напишет тексты. Демонстрация выходит убедительной с первого захода — модель уверенно говорит «от Эрмитажа до Спаса на Крови десять минут пешком» и почти не ошибается, пока вы проверяете знакомый город.

Ошибаться она начинает там, где вы проверять не станете. «От Владивостока до Хабаровска два часа» — правдоподобно и неверно. «Пешком от Лиссабона до Нью-Йорка» — абсурдно, но модель это напишет, если попросить построить маршрут.

Ниже — разбор geopilot.info: каталога мест и планировщика маршрутов, где нейросеть используется в двух местах и не используется в третьем. Сейчас в нём 1481 место, 200 городов и 70 стран, и главное архитектурное решение всей системы формулируется одной строкой: модель выбирает, арифметика считает.

Разделение ролей

Планировщик собирает день вокруг выбранного места: вы открыли страницу музея, а система предлагает, что посмотреть рядом и в каком порядке.

Задача распадается надвое, и половины эти разной природы.

Первая — что поставить в день. Здесь нет правильного ответа, есть уместный: рядом с музеем лучше смотрится парк, а не второй музей; после собора не ведут в третий собор; ночлег в дневную программу не ставят. Это вкусовое знание, разлитое в текстах, и модель им владеет.

Вторая — сколько это займёт. Здесь правильный ответ ровно один, и он получается из данных: координаты, дорожный граф, скорость вида транспорта, время осмотра, часы работы. Модель это знание имитирует, и разница между имитацией и вычислением видна не сразу — что и делает её опасной.

Каталог10 кандидатов рядомМодельпорядок и одна фраза «почему»Оценщиквремя, расстояние, приходдвижок маршрутизации: настоящая дорогане больше 12 запросов на один пересчётмодель не видит ни одного числа из правой веткичисла не проходят через модель — и потому не портятся
рис. 1 Роли в советнике маршрута. Модель получает десять кандидатов и возвращает порядок из четырёх; всё, что можно посчитать, считается без неё.

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

Возвращает модель имена и порядок. Ни минут, ни километров, ни времени прихода в ответе нет — их неоткуда взять правильно, а неправильные числа выглядят так же уверенно, как правильные.

Второе следствие того же правила: модель работает на маленькой и быстрой конфигурации — задача структурная, объяснение короткое, платить за большую модель не за что. Тексты каталога пишет другая, крупная, потому что там решает язык, а не структура. Модель под задачу, а не одна на всё.

Подбор мест: где модель отвечает мимо

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

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

Механика понятна: модель усредняет. На запрос «достопримечательности города N» она возвращает то, что чаще всего написано про города вообще, а не то, чем этот город отличается от соседнего.

Способ второй: назвать места руками. Голубое озеро в Башкирии, Кунгурская ледяная пещера, Долина гейзеров, Гамсутль, Кезеной-Ам, Куршская коса. Список пишется человеком, а команда только раскладывает его по базе: заводит места, при необходимости — города и страны, ищет координаты в рамке вокруг города и помечает их как выверенные, чтобы последующие автоматические проверки их не трогали.

За два захода так добавилось 100 мест по России и 505 по миру — Колизей, Мачу-Пикчу, Ангкор-Ват, Терракотовая армия, Сигирия, Петра. Из этих 505 восемьдесят семь городов в каталоге отсутствовали вовсе: Долину гейзеров некуда положить, пока нет Камчатки.

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

Поэтому у текстов есть обязательный вход: статья Википедии об объекте. Материал сначала добывается, потом пишется, и в тексте не появляется ни одной даты, которой нет в источнике. Дороже — да. Но альтернатива не «дешевле», а «быстро и неправда».

Фотография: доказательство, а не иллюстрация

Самая недооценённая часть проекта. Найти картинку легко; доказать, что на ней именно это место, — нет.

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

  • по тегу opera для казанского театра прилетела Опера Осло;
  • по тегу circus — одна и та же цирковая палатка на два разных цирка;
  • по тегу monastery вместо армянского храма Сурб Хач — европейское аббатство;
  • по имени Musa Cälil — связка бананов, потому что Musa это ботанический род банана;
  • на набережную Туапсе встал скриншот системы мониторинга пожаров: файл честный, тема верная, но это не фотография.

Общее у всех промахов одно: тег доказывает тему, а не объект.

1. Статья Википедии об объектеснимок привязан к статье о самом объекте; у статьи есть координаты2. Файл, названный объектом и городомимя файла содержит все отличительные слова названия3. Снимок с координатами съёмкисделан в 300, 800 или 2000 метрах от точки места4. Фотосток по правилу двух сигналовтеги называют и отличительную часть имени, и город — иначе отказ
рис. 2 Источники снимка по убыванию доказательности. Каждый следующий берётся, только когда предыдущий промолчал, и каждый отвечает на свой вопрос о доказательстве.

Порядок на схеме — не произвол, а сортировка по силе доказательства, и опыт правил его дважды.

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

Второй раз правило правил случай из Улан-Удэ. Файл назывался «Расул Микаилов у памятника Ленину в Улан-Удэ» — имя честное, все слова на месте, и на странице самой большой головы Ленина в мире появился портрет незнакомого человека в костюме. Пришлось добавить признак «объект в имени стоит при человеке»: конструкции вроде «у памятника», «на фоне», «у входа» означают, что снимали не объект.

Лицензия решает, какие снимки вообще существуют для вас

Отдельный поворот, который стоит знать всем, кто берёт изображения из свободных источников.

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

Практический вывод: фильтр по лицензии стоит до скачивания. Викисклад отвечает на этот вопрос прямо — в метаданных файла есть поле AttributionRequired, — и файл, которому нужна подпись, отбрасывается раньше, чем к нему протянется рука.

Цена решения измерима: из 1099 снимков, уже стоявших на страницах, 982 были под лицензиями с обязательной атрибуцией. Замена нашлась примерно для половины; остальные страницы честно говорят, что фотографии нет. Это дороже, чем «оставить как было», и дешевле, чем разбирательство с правообладателем.

Маршруты: почему прямая линия — это ложь

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

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

Вокруг движка выстроено четыре ограничителя, и каждый появился из конкретной аварии.

Кэш отрезков. Дороги не перекладывают еженедельно, а точки мест не двигаются: посчитанный отрезок живёт 90 дней. Сейчас в кэше 2055 отрезков — это те запросы, которые не ушли к чужому серверу.

Бюджет вызовов. Не больше двенадцати запросов на один пересчёт маршрута. Дальше остаток дня считается оценкой: показать день сразу лучше, чем держать человека перед пустой страницей, пока отвечает чужой сервис.

Предохранитель. Три отказа подряд — и десять минут к движку не обращаются вовсе. Каждый запрос в такой момент стоит таймаута на глазах у пользователя.

Проверка ответа на осмысленность. Самое интересное.

Движок врёт вежливо

OSRM ставит точку на ближайшую дорогу — и делает это молча. Если места нет в графе, ближайшая дорога может оказаться за тысячи километров: Нью-Йорк на европейском графе прилипает к побережью Португалии, и маршрут «Лиссабон — Нью-Йорк на машине» возвращается бодрым ответом в сорок километров.

Отсюда два правила приёмки чужого ответа:

  • допуск на притягивание — пять километров. Дальше это не наша точка;
  • дорога не бывает короче прямой. Ответ, который короче гаверсинуса между теми же точками, читается как «пути нет», а не как хорошая новость.

Оба правила — про одно: чужой сервис отвечает всегда, и отличать ответ от отказа приходится самому.

Годность вида транспорта

Из тех же проверок вырос отдельный слой, которого в первой версии не было вовсе: можно ли этим видом транспорта пройти этот отрезок.

Проверок две, и обе нужны.

Расстояние. Пешком тридцать километров — ещё план на день, три тысячи — нет. Рейса на пятнадцать километров не бывает: у самолёта есть не только верхний предел, но и нижний.

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

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

Выбранный видпрофиль движкаДорога нашлась?да — земля под ней естьПроверка расстояниемпо дороге, а не по прямойнет — машинная проба:есть ли земля вообщеземли нет → самолётземля есть → машинаслишком далеко · слишком близконет пути этим видом · нет земличетыре причины отказа — четыре разных подсказки человеку
рис. 3 Проверка вида транспорта на отрезке. Ответ движка принимается только тогда, когда он о чём-то говорит: молчание графа — не доказательство.

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

Порядок обхода: где не надо звать движок

Оптимизатор перебирает порядок остановок. Наивная реализация зовёт движок на каждой паре точек — и получает квадрат от числа мест, помноженный на стоимость чужого запроса.

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

Тот же промах повторился на главной странице: она опрашивала движок веером — до каждого места города и до каждого кандидата на каждом шаге. Лечится одинаково: сначала выбрать оценками, потом измерить оставшееся.

Ворота провайдера: лимиты как часть архитектуры

Тексты каталога пишет облачная модель с квотой: пятичасовое окно и недельное. Когда квота кончается, поведение системы становится частью продукта, а не деталью инфраструктуры.

Первая версия делала растущую лестницу пауз: полчаса, два часа, пять, сутки. Это оказалось угадыванием. Снаружи не видно, сколько осталось от окна, и ошибка в большую сторону стоит ночи простоя.

Сейчас пауза одна — тридцать минут, — и после неё работа пробуется снова. Провайдера слушаются только тогда, когда он просит подождать меньше получаса. Пауза лежит в базе, а не в памяти процесса: её ставит одна команда, а видят все, включая расписание.

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

Что ломалось на продакшене

Раздел, без которого разбор был бы рекламой.

Пятисотка на всех страницах мест. Внутренняя перелинковка собирала из имён мест каталога одно регулярное выражение. На тысяче с лишним записей PCRE отказался его компилировать, и страницы городов и мест начали отдавать 500. Лечение — предел в 24 килобайта на выражение, а имена подставляются от длинных к коротким: обрезается хвост из самых общих. Урок не про PCRE, а про то, что структуры, растущие вместе с каталогом, надо ограничивать по размеру заранее.

Пермский собор в Томске. Геокодер отвечал первым совпадением по имени, и на карте Перми появлялась точка за тысячу семьсот километров. Теперь ответ обязан попадать в рамку вокруг города, а выверенные вручную координаты помечены и не переписываются автоматикой — Долина гейзеров и правда в трёхстах километрах от Петропавловска.

«Между странами» внутри России. Россия завелась в справочнике дважды — russia и rossiia, — и маршрут Москва — Анапа стал переездом между странами, потому что тип перехода считается по country_id. Дубли сведены командой, проигравшая запись гасится, а не удаляется: данные уже перевешены, и вернуть погашенное можно, удалённое — нет.

Вход, отвечающий JSON. Один адрес /login слушал три разных клиента — браузерную форму, админку на jQuery и мобильное приложение — и отвечал всем одинаково. Человек с формы получал на экран {"code":404,"message":"Email not found!"}. Это не про ИИ вовсе, но про то же самое: пока клиент один, разница между «ответить» и «ответить тому, кто спрашивает» незаметна.

Что из этого следует

Три вещи, применимые за пределами travel-каталога.

Первое. Разделите вопросы «что уместно» и «сколько это стоит». Первый отдайте модели, второй — коду. Смешанный ответ выглядит цельным и разваливается на проверке, причём проверять будет пользователь.

Второе. У каждого сгенерированного объекта должен быть источник, на который можно показать. У текста — статья, у фотографии — файл с лицензией и координатой. Не ради формальности: без источника вы не отличите работу от галлюцинации, и никакая температура этого не изменит.

Третье. Чужой сервис всегда отвечает. Движок маршрутизации притянет точку через океан, фотосток найдёт картинку по тегу, модель напишет уверенное расписание. Отличать ответ от отказа — ваша работа, и она стоит примерно столько же, сколько сама интеграция.