Сдерживаем полет фантазии LLM в киносервисе

от автора

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

В сервисе ИИ есть в нескольких проявлениях:
— рекомендательная модель;
— ИИ‑помощник;
— поиск (Шазам);
— распознавание скриншотов с названием/афишей фильма (Vision‑модель);
— MCP;
— голосовой ввод.

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

Общий принцип простой: модель понимает запрос и предлагает гипотезы, каталог API киносервисов решает, существует ли фильм на самом деле. Текстовые модели ходят через OpenRouter, конкретно Шазам, про который поговорим ниже, работает наLing 2.6 Flash — MoE‑модель (104B total / 7.4B active), заточена под короткие agent‑задачи. Речь распознаёт Groq Whisper large‑v3, vision запросы для скриншотов тоже идут через OpenRouter, с разными «уровнями» качества для обычных пользователей и PRO: gemini-2.5-flash-lite → gpt-4o-mini для тех, кто порадовал монеткой, и gemma/nemotron для базовых пользователей.

И всё это — в реальном времени. Ни ночного пересчёта индекса, ни ответа за минуты: пользователь жмёт «найти» — и пайплайн крутится прямо сейчас — обычно 5–7 секунд end‑to‑end. С голосом добавляется Whisper, в сумме чаще до 10 секунд на весь путь от аудио до результата. Vision‑модели чуть потяжелее, там в районе 10–15 секунд уходит на распознание, но тоже очень бодро.

Как раз про опыт «приземления» полета фантазий нейросети на реальный API TMDB с реальными фильмами и хочется рассказать: кажется занимательным, как в режиме онлайн устроен процесс поиска и подбора реальных фильмов нейросеткой, и возврат точных активных ссылок пользователю.

Как устроен любой AI‑сценарий у нас

Пользователь приносит неструктурированный ввод: текст, голосовое сообщение или скриншот из рилзов/соцсетей. Дальше идёт предобработка — распознавание речи, подготовка картинки. Затем LLM или vision‑модель разбирает смысл и выдаёт структурированный черновик.

Хорошие результаты подкрепляем API и зазерняемся в катарсисе

Хорошие результаты подкрепляем API и зазерняемся в катарсисе

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

Дальше постараюсь коротко и интересно рассказать о том, как что в сервисе реализовано, начнем с Шазама, надеюсь будет интересно, погнали!

Кино‑Шазам: когда помнишь сюжет, но не название

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

Помните такого дядю? Всегда вспоминаю его, когда  тестирую Шазам

Помните такого дядю? Всегда вспоминаю его, когда тестирую Шазам

Типичная ситуация: единственное, что помнишь про фильм — что его снял Нолан в 2000-м, или что это фильм про лиминальные пространства (очень актуально), и в таком духе. Или, тоже допустимый кейс, когда пользователь голосом набросал минуту воспоминаний о фильме своими словами. Обычный поиск по названиям/актерам в других ресурсах на таких запросах разваливается: слишком мало ключевых слов, слишком много лишнего контекста, пользователь может напутать имена актеров или написать их неправильно, и тогда базовый поиск точно не справится.

Шазам заточен именно под естественный язык, из которого извлекается конкретный фильм, а не под общие запросы типа «дай топ-10 комедий».

Как было раньше: векторная база и почему от неё ушли

Первую версию гордо могу назвать любопытной но неудачной попыткой. Мы делали классически: embeddings + FAISS. Описание пользователя превращалось в вектор (модель bge‑large), искалось ближайшее среди заранее проиндексированных фильмов. Звучит красиво, но на практике оказалось очень больно.

В голову приходит только Сизифов труд и Авгеевы конюшни

В голову приходит только Сизифов труд и Авгеевы конюшни

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

Каждый поиск — ощутимая нагрузка на сервер. Embedding‑модель на CPU, FAISS query, иногда перевод через nllb-200 (запросы на русском). С ограниченными стартовыми ресурсами это ощущалось каждым запросом: холодный старт, высокий летенси, расходы по памяти.

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

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

Мы выкинули это всё и полностью отказались от формата. От изначального концепта оставили только Whisper — он решает другую задачу (STT), и влияет только на размер контекста, который тоже лучше обрабатывается именно нейронкой. 

Как устроено сейчас: предложение вариантов от LLM, проверка на реальном каталоге

В Shazam мы используем Ling 2.6 Flash через OpenRouter — не потому что это самая умная модель в мире, а потому что Шазам — это structured retrieval (структурированный поиск): два быстрых вызова (разбор intent + список названий), строгий JSON, русский запрос → английские title для каталога. Ling — instant/agent‑модель с низкой latency и копеечной ценой на короткий output.

Финальный фильм пользователю не «придумывает нейросеть» — каждое название проходит grounding в TMDB; выдумки отсекаются. Поэтому primary может быть быстрым и дешёвым, а качество держится гибридом: LLM + фильмография + синк с Кинопоиском + пересечения (в случае с несколькими актёрами).

Разберём пошагово весь процесс Шазама.

Так пошагово выглядит любой процесс Шазама на платформе

Так пошагово выглядит любой процесс Шазама на платформе

Шаг 0 — голос. Если пользователь говорит, а не печатает: Groq Whisper large‑v3 переводит в текст. Длинное голосовое «ну там типа чувак в костюме мочит бандосов…» даёт модели больше контекста, чем одна строка в поиске.

Шаг 1 — intent parsing. Отдельный короткий вызов LLM разбирает запрос в JSON: актёры и режиссёры (на английском), жанры, keywords сюжета, годы, фильм или сериал. Плюс rule‑based дополнения: русские алиасы актёров («кавилл» → Henry Cavill), маркеры «новый фильм / премьера» → year_from ≥ 2024, слова «сериал / сезон» → is_series.

вот кстати и лиминальные пространства подъехали. Наверху предлагаем точное совпадение, дальше идут весьма похожие под запрос фильмы

вот кстати и лиминальные пространства подъехали. Наверху предлагаем точное совпадение, дальше идут весьма похожие под запрос фильмы

Шаг 2 — LLM возвращает несколько фильмов. Но зачем?! Это осознанный дизайн‑выбор, (не баг а фича):

— Модель не уверена на 100% — если запрос относительно простой, типа того же «фильм про сны Нолан» — тут всё понятно, какой будет ответ, но другие формулировки могут быть менее очевидные, например, что‑то из мемов или жизненного опыта пользователя. Несколько кандидатов = несколько гипотез.

— Запрос намеренно размытый — «страшный фильм про маньяка» не имеет единственного ответа; пользователю нужна короткая выдача на выбор.

Порядок в списке = уверенность: первое название — best guess, дальше — близкие по смыслу или жанру.

— Каждое название потом независимо валидируется в TMDB. Выдумка модели («Dark Dream 2») просто не найдётся и отсеется — без этого мультикандидатный пайплайн был бы опасен.

Давно что-то не было мемов. Вот актуальный мем всем дочитавшим!

Давно что‑то не было мемов. Вот актуальный мем всем дочитавшим!

Шаг 3 — приземляем в TMDB. Для каждого английского названия — поиск по фильму или сериалу, выбор лучших кандидатов по полному/частичному совпадению названия и ранжирование по популярности. Параллельно подтягиваем imdb_id (без него строка не попадает в выдачу, так как мы не сможем распарсить русские названия картинки и тд.), русское наименование, год, жанры.

Шаг 4 — гибридные пути, не только LLM. Чистый «LLM → название → TMDB» недостаточен для части запросов. Поэтому параллельно происходят:
Прямой поиск в TMDB по сырому тексту запроса (ru/en).
РФ API для актуализации по премьерам — для свежих российских премьер и RU‑названий, которых LLM ещё «не знает» или TMDB матчит хуже.

Рассмотрим несколько частных примеров шазама по запросам с актерами и ключевыми словами.

Фильмография актёра из TMDB

Кейс с уточнением актера и с ключевыми словами по фильму, например, Стетхем + shark → «Мэг: Монстр из Глубины» всплывает (ха‑ха) за счёт совпадения ключевых слов в общем обзоре фильмографии, а не потому что LLM вспомнила.

Про кейс со Стетхемом кстати давайте разберём подробнее, потому что в целом есть интересные особенности, как проходит процесс с актером, как частный случай.

Параллельно формулируются подходящие кандидаты и выстраивается ранжирование для конкретного запроса. В конечном итоге результатов будет несколько

Параллельно формулируются подходящие кандидаты и выстраивается ранжирование для конкретного запроса. В конечном итоге результатов будет несколько

На деле это выглядит вот так:

примерно 10 секунд загрузки, и в конечном итоге пользователь получает список фильмов, где есть и точное совпадение, и какие-то похожие фильмы, которые пользователю могут быть интересны под запрос.

примерно 10 секунд загрузки, и в конечном итоге пользователь получает список фильмов, где есть и точное совпадение, и какие‑то похожие фильмы, которые пользователю могут быть интересны под запрос.

Пересечение двух актёров

Например, мой личный кейс на который наткнулся — Кавилл ∩ Джилленхолл (фильм «Грязные деньги», тоже недавно был в прокате) — это формат сценария поиска для запросов «фильм актеров X и Y» с наслоением поиска по новинкам.
Тоже интересный кейс, не самый простой на самом деле, который тоже удаётся обработать: мы не полагаемся только на генерацию названий — включается отдельная TMDB‑ветка.

Шаг 1 — intent. Парсер кладёт в JSON обоих актёров на английском (Henry Cavill, Jake Gyllenhaal), rule‑based слой подхватывает русские алиасы («кавилл», «джилленхол»), маркер «новый фильм» — нижнюю границу года.

Шаг 2 — пересечение в TMDB. Для каждого актёра: search/personperson/{id}/movie_credits → множество tmdb_id (с отсечением совсем малоизвестных). Пересечение двух множеств — кандидаты, где они снимались вместе. В логах это буквально actor intersection TMDB ids: N (A ∩ B).

Шаг 3 — ранний Kinopoisk для недавних фильмов. Если в запросе два и более актёров, или «новый фильм», или распознали русские фамилии — до основного merge идёт отдельный запрос в API РФ поиска по сырому тексту запроса. Кинопоиск хорошо знает свежие RU‑премьеры и кириллические формулировки; TMDB и LLM могут отставать на несколько дней. Без imdb_id hit из KP карточка всё равно не попадёт в выдачу — тоже отрезается на этапе приземления на API.

Шаг 4 — две ветки фильмографии + буст. Параллельно тянем фильмографию каждого актёра через поиск по людям. Фильмы из пересечения получают жёсткий бонус к score (бонус за пересечение — большой плюс к внутреннему рангу). Это понятный сигнал: этот id есть в фильмографии всех актеров из запроса. Дальше — буст по ключевым словам по соответствию с описанием/названием, если в запросе есть сюжетные слова; для «нового фильма» без каких‑либо ярких ключевых слов — фильтр от 2024 года и новее поднимает свежие релизы только если они уже матчятся по смыслу, чтобы случайная новинка 2026 не залезла в топ.

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

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

Шаг 5 — merge с LLM. Поиск от названия всё равно работает — модель может назвать правильный фильм первым. Но тут мы не выбираем в итоге одно из двух, а сначала сохраняем порядок LLM по релевантности названия, дополняем хитами из KP, direct TMDB search и actor‑prefetch. Финальный _rank_search_results сортирует так:

1. В пересечении двух актёров — выше всего.
2. Год ≥ 2024, если запрос про новинку.
3. Есть kp_id (RU‑якорь).
4. Score популярности TMDB.

Вот примерно все основные кейсы, на двух остановились поподробнее, всё это мерджится с результатами LLM: приоритет у пересечения актёров, свежих годов (2024+), наличия id, и общий скор соответствия. Если нет единого источника правды — ловим несколько слабых сигналов, которые сходятся на одних и тех же id.

С чем столкнулись на проде

ЛЛМ не врёт. Она просто галлюцинирует!

ЛЛМ не врёт. Она просто галлюцинирует!

LLM выдумывает названия. Лечится обязательным TMDB hit + imdb_id. Галлюцинация умирает на этапе заземления.

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

Русский ввод, английский каталог. Разбор запроса переводит актёров на английский; список названий от модели — тоже на английском, как в TMDB. Русское название догружаем отдельным запросом к TMDB с русской локалью и через РФ API кино.

Один актёр vs два. «Фильм с ДиКаприо» — просто поиск по фильмографии. «Кавилл и Джилленхолл, новый фильм» — пересечение фильмографий в TMDB плюс ранний запрос в API.

Сериал vs фильм. Явно помечаем, что нужен сериал, и в TMDB ищем в первую очередь среди сериалов — иначе «сериал про итальянскую мафию» находил бы фильмы с похожими словами.

Ключевые слова типа «Настольный теннис». Это про недавнюю премьеру «Марти Великолепный»: короткие английские слова дают ложные совпадения в описаниях фильмов. Отталкиваясь от этого, начали собирать группы синонимов (для конкретного фильма условно «пинг‑понг», «настольный теннис») — иначе в топ лезло бы всё, где встречается «table».

Летенси. До 15 названий × TMDB поиск + параллельный imdb_id fetch в thread pool. Компромисс: качество и актуальность важнее sub‑second ответа; пользователь видит прогресс, не пустой экран.

Чем Shazam отличается от «просто спросить ChatGPT»

Наверное, главный вопрос: почему просто не закинуть вопрос про фильм в GPT или в гугл, какой смысл вообще использовать такой сервис внутри Movie Planner.

ChatGPT назовёт 3–5 фильмов (скорее всего, наиболее точно подходящие вашему запросу, в этом плане он — топ), но фильм будет без конкретики: нейросети редко дают прямые урлы на фильмы. А зачем? Их задача ответить на ваш вопрос. Также нейронки редко дают постеры или сразу подробное описание/актерский состав, я такого не встречал, а также нет какого‑либо функционала добавления в базу, планирования просмотра, да даже просто добавить в какой‑либо список нельзя, а также нельзя например сохранить билеты и чтобы агент вам их скинул перед входом в кинотеатр. Можно в отдельном чате вести базу кино, но кажется это натягивание совы на глобус.

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

Итоги

Такая логика, как в кино-Шазаме, используется и внутри рекомендательного сервиса с подбором рекомендаций «по эмоции», и в AI‑помощнике (особенно по рекомендации по новым фильмам) — один поиск фильма на всё, одни и те же правила заземления запроса.

Приземлять то, что нашла нейросеть по описаниям, на реальные id картин — очень интересное занятие! Думаю, все так или иначе сталкиваются с тем, что результаты нейросетей нужно переводить в понятные JSON и другие форматы. Они иногда ломаются, тут я постарался рассказать про такой формат, который строго подвязан на реальные названия, имена и id.

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

Про остальное буду рад рассказать дальше, напишите, про что было бы интереснее почитать: про рекомендации «по эмоции», ИИ‑помощника или распознавание фильмов со скриншотов?

ссылка на оригинал статьи https://habr.com/ru/articles/1053204/