Каждый, кто запускал рекомендательную систему, знает это чувство бессилия. Новый пользователь заходит, видит пустую ленту и уходит навсегда. Коллаборативная фильтрация бесполезна — история взаимодействий равна нулю. Контентная фильтрация по жанрам? Покажите мне человека, который хочет смотреть только "боевики", и я покажу вам того, кто засыпает на пятой перестрелке. Традиционные методы ломаются о cold start.
Выход — перестать гадать на кофейной гуще и заставить пользователя рассказать о своем вкусе. Но не через скучные анкеты, а через диалог. А потом превратить этот диалог в математику. GPT и эмбеддинги дают ключ к вектору вкуса — числовому отпечатку предпочтений, который работает даже для абсолютно нового юзера.
В этой статье мы соберем прототип рекомендательного сервиса (назовем его NextFilm) на Python, OpenAI API и косинусной близости. Без сложных моделей, без тонн данных, но с работающим решением, которое можно запустить за вечер.
Важно: всё, что вы прочитаете, базируется на опыте реальных проектов. Если хотите системно подойти к архитектуре рекоммендательных систем, рекомендую прочитать мой разбор MLSD: как не сгореть, проектируя рекомендательные системы — там описан универсальный план и главные подводные камни.
Проклятие белого листа
Проблема cold start убивает не только новые платформы. Даже зрелые сервисы вроде Netflix сталкиваются с ней, когда у пользователя нет истории — он залогинился через соцсеть, но не поставил ни одного лайка. Классические подходы:
- Популярное — показываем топ по рейтингу. Скучно, не персонализировано, конверсия в просмотр — пшик.
- Жанровый фильтр — пользователь выбирает жанры. Результат: куча однотипного треша.
- Анкета при регистрации — 10 вопросов, которые все ненавидят заполнять. Брошенные корзины.
Ни один из вариантов не учитывает нюансы: атмосферу, темп повествования, визуальный стиль, неожиданные твисты. То, что делает фильм "нашим", не помещается в три тега.
Выход — использовать естественный язык. Пусть пользователь опишет, что он любит, своими словами. А GPT превратит этот поток сознания в структурированный профиль. Мы уже разбирали похожий подход для литературы в статье Твои литературные вкусы — это код. Давай его декомпилируем через ChatGPT — там та же идея, но для книг.
Вектор вкуса: ДНК вашего пользователя
Магия в эмбеддингах. Любой текст (описание фильма, отзыв, диалог с пользователем) можно превратить в вектор фиксированной размерности. Если два вектора близки в многомерном пространстве — их смыслы похожи.
Наша идея:
- Получить эмбеддинги всех фильмов из базы (на основе описаний, жанров, ключевых слов).
- Через диалог с LLM выяснить вкусы нового пользователя и сгенерировать текстовый портрет его идеального фильма.
- Преобразовать этот портрет в эмбеддинг — это и есть вектор вкуса.
- Найти k ближайших соседей среди фильмов — готово, рекомендации.
Звучит просто, но дьявол в деталях. Давайте разберем реализацию на Python.
1Шаг 1. База фильмов и эмбеддинги
Возьмем небольшой датасет из 5000 фильмов (для прототипа хватит). Для каждого нужен текстовый дескриптор. Лучше всего — описание сюжета из Wikipedia или TMDB. Склеим с жанрами, ключевыми словами, именами режиссеров и актеров. Пример:
import openai
openai.api_key = "sk-..." # ваш ключ
# Функция для эмбеддинга текста (используем text-embedding-3-small)
def get_embedding(text: str, model="text-embedding-3-small") -> list[float]:
text = text.replace("\n", " ")
return openai.embeddings.create(input=[text], model=model).data[0].embedding
# Собираем дескриптор фильма
desc = f"{title}. Жанры: {genres}. Режиссер: {director}. Сюжет: {plot}."
film_vector = get_embedding(desc)
# Сохраняем в numpy array или Faiss indexДля больших баз (миллионы фильмов) используйте Faiss от Facebook — он умеет искать ближайших соседей за миллисекунды. Мы же для MVP обойдемся простым сканированием с numpy.
2Шаг 2. GPT-опросник для пользователя
Теперь начинается магия. Вместо скучной формы мы запускаем диалог. Бот задает 5-7 открытых вопросов, а пользователь отвечает как хочет. Пример промпта:
system_prompt = """Ты — дружелюбный киноэксперт. Твоя задача — выяснить вкусы пользователя.
Задай ему 5 вопросов о любимых фильмах, режиссерах, атмосфере, темпе, визуале.
После получения ответов сгенерируй краткий, но ёмкий портрет его идеального фильма (3-5 предложений).
Портрет НЕ должен включать названия реальных фильмов — только описание характеристик.
"""
messages = [{"role": "system", "content": system_prompt}]
for _ in range(5):
user_answer = input("Ваш ответ: ") # или из чата
messages.append({"role": "user", "content": user_answer})
# Получаем следующий вопрос (или финальный портрет)
response = openai.chat.completions.create(model="gpt-4o", messages=messages)
assistant_msg = response.choices[0].message.content
messages.append({"role": "assistant", "content": assistant_msg})
if len(messages) >= 12: # после 5 ответов получаем портрет
break
# Извлекаем портрет — он будет последним сообщением ассистента
profile_text = assistant_msg
print("Ваш профиль вкуса:", profile_text)Важно: GPT-4o (актуальная на апрель 2026) отлично справляется с извлечением неявных предпочтений. Например, если пользователь скажет "люблю Тарантино за диалоги", модель поймет: важны остроумные разговоры, нелинейный сюжет, напряжение. И включит это в портрет.
3Шаг 3. Вектор вкуса и поиск
Превращаем портрет в эмбеддинг той же моделью, что и фильмы, и ищем ближайших соседей:
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
user_vector = np.array(get_embedding(profile_text))
# film_vectors — матрица (N, 1536), где N — число фильмов
similarities = cosine_similarity([user_vector], film_vectors)[0]
top_indices = similarities.argsort()[-10:][::-1]
recommendations = [films[i].title for i in top_indices]
print("Рекомендуем:", recommendations)Всё. За 10 строк кода мы решили cold start. Пользователю не нужно ничего оценивать — только поболтать с ботом. При этом рекомендации будут точнее, чем жанровая фильтрация, потому что мы учли нюансы.
Ошибка, которую я совершил в первый раз: я использовал разные модели эмбеддингов для пользователя и фильмов. Например, text-embedding-3-small для фильмов и ada-002 для профиля. Векторы лежат в разных пространствах, косинусная близость не работает. Всегда используйте одну и ту же модель.
Как улучшить точность: асессмент и обратная связь
Наш прототип уже работает, но он статичен. Вкусы меняются, да и профиль может быть неидеальным. Добавим механизм уточнения. Пользователь видит рекомендации и ставит лайки/дизлайки. Мы берем эмбеддинги лайкнутых фильмов и усредняем их с текущим вектором вкуса (с весами). Новый вектор смещается в сторону того, что зашло. Проходит несколько итераций — и система запоминает пользователя так же хорошо, как коллаборативная фильтрация.
Кстати, эта техника отлично ложится на идею персонализированных фильтров, о которой я писал в статье Ваш личный AI-фильтр: как перестать читать мусор и не пропустить важное — там похожая концепция, но для текстов.
Продвинутые техники: мультимодальность и кластеризация
Хотите еще круче? Добавьте постеры фильмов. С помощью мультимодальных эмбеддингов (например, Gemini Embedding 2, который мы разбирали в мультимодальном RAG туториале) можно учесть визуальный стиль. Пользователь говорит "люблю нуар с контрастным светом" — и система ищет фильмы с похожей цветовой палитрой.
Другой уровень — кластеризация эмбеддингов фильмов. Вместо прямого поиска можно построить дендрограмму и рекомендовать из того же кластера. Это снижает шум. Подробнее о таком подходе читайте в материале Data-driven анализ вкусов: как с помощью эмбеддингов LLM кластеризовать фильмы, книги и музыку.
Главные грабли (и как их обойти)
- Размер эмбеддингов. У text-embedding-3-small размерность 1536. Для 100 000 фильмов матрица займет ~600 МБ в памяти — отлично. Но если миллионы, используйте Faiss с компрессией (PQ), иначе ОЗУ уплывет.
- Длина портрета. GPT может сгенерировать портрет слишком обобщенный ("люблю хорошее кино"). Задайте в промпте ограничение: "Опиши идеальный фильм максимально конкретно: жанр, темп, настроение, визуальный стиль, необычные элементы".
- Холодный старт для самого фильма. Новый фильм без оценок — та же проблема. Но наш метод работает: просто добавьте описание в базу, и его найдут по эмбеддингу. Контентная близость не требует рейтингов.
- Язык. Модели эмбеддингов лучше всего работают на английском. Если база на русском — используйте multilingual-модели (например, text-embedding-3-small поддерживает много языков, но проверьте). Или переводите описания на английский через GPT перед эмбеддингом — это дополнительный расход токенов, но качество вырастает.
Кстати, один из самых коварных багов — это случай, когда пользователь отвечает односложно ("да", "нет"). GPT может не вытянуть нормальный портрет. Решение: добавьте валидацию. Если ответ пользователя короче 10 символов, попросите развернуть. Или задавайте закрытые вопросы с последующим уточнением.
Что дальше? Вместо заключения
Мы построили рекомендательную систему, которая не боится пустого профиля. Её можно развернуть за один вечер, а точность будет выше, чем у многих стартапов, собирающих лайки годами.
Теперь представьте, что вы не просто ищете похожие фильмы, а делаете персонального кино-агента, который объясняет, почему именно этот фильм вам подойдет. В статье От шаблонных рекомендаций к умному собеседнику мы как раз это и реализовали на Amazon Bedrock AgentCore — рекомендую глянуть для вдохновения.
Единственное, о чем стоит помнить: GPT — не панацея. Он дорогой на этапе генерации портрета (но это разово для пользователя), и он может галлюцинировать. Всегда проверяйте финальный портрет на осмысленность. А лучше — дайте пользователю его отредактировать перед поиском. И тогда ваш NextFilm станет тем сервисом, с которого не хочется уходить.