Skip to main content
Glama

Two-Tower Recsys MCP — нейросетевой поиск, доступный через MCP

Глубокая модель рекомендаций с двумя башнями, обученная на собственном корпусе отзывов Amazon 2023 года, развернутая как сервер инструментов MCP (Model Context Protocol), с чат-интерфейсом на Streamlit, который позволяет агенту Gemini вызывать эти инструменты от вашего имени.

Этот README описывает весь конвейер от начала до конца: что представляет собой модель, как она обучалась, насколько хорошо она реально работает (измерено, а не оценено), как MCP-сервер ее предоставляет и как запустить или развернуть фронтенд.


1. Что это такое

Модели с двумя башнями — стандартная архитектура для крупномасштабных промышленных рекомендательных систем (этот паттерн — отдельные «башни», которые встраивают пользователя и товар в одно и то же векторное пространство, обучаясь так, чтобы релевантные пары оказывались близко друг к другу, — используется в продакшене YouTube, Pinterest и собственных системах поиска Amazon). Этот проект реализует такую модель с нуля, обучает ее на реальных данных взаимодействий Amazon и оборачивает для использования агентами через MCP вместо типичного REST API.

Почему MCP вместо REST API? MCP — это протокол, который Anthropic представила для подключения LLM-агентов к инструментам и данным. Обертывание обученной модели в виде MCP-инструментов (а не, скажем, конечной точки Flask) означает, что любой MCP-совместимый агент — Claude Desktop, собственный фронтенд этого проекта на Streamlit+Gemini или любой другой MCP-клиент — может напрямую вызывать recommend_for_user, similar_items и т.д., при этом LLM сама решает, когда и как их вызывать, на основе естественного языка.

Related MCP server: Modular RAG MCP Server

2. Архитектура

  • Башня пользователя: изученное встраивание ID пользователя (64-мерное) → 2-слойный MLP → 64-мерный выход.

  • Башня товара: изученное встраивание ID товара (64-мерное), объединенное с замороженным встраиванием предложения all-MiniLM-L6-v2 из названия товара (384-мерное, проецируемое в 64-мерное) → 2-слойный MLP → 64-мерный выход. Замороженное текстовое встраивание дает модели возможность холодного старта — она может разумно разместить товар в векторном пространстве даже без истории взаимодействий, только на основе его названия.

  • Обе башни выдают L2-нормализованные векторы; сходство — это скалярное произведение (эквивалентно косинусному сходству).

  • Функция потерь при обучении: внутрипакетный семплированный softmax — для пакета из B положительных пар (пользователь, товар) каждый другой товар в пакете выступает отрицательным для каждого пользователя, и применяется кросс-энтропия к полученной матрице сходства B×B. Это стандартный, вычислительно эффективный способ обучения башен поиска без явного отрицательного сэмплирования.

  • Сервинг: встраивания товаров предвычисляются один раз и индексируются в FAISS (IndexFlatIP) для быстрого поиска ближайших соседей. Второй индекс FAISS, построенный на сырых (необученных) встраиваниях MiniLM из названий, обеспечивает текстовый поиск с холодным стартом, который работает независимо от обученного коллаборативного сигнала.

        ┌────────────┐                          ┌────────────┐
        │  User ID   │                          │  Item ID   │
        └─────┬──────┘                          └─────┬──────┘
              │ embed(64)                              │ embed(64)
              ▼                                         ▼
        ┌────────────┐                    ┌──────────────────────────┐
        │  MLP (128) │                    │  Item title → MiniLM(384) │
        └─────┬──────┘                    └─────────────┬─────────────┘
              │                                          │ project(64)
              │                                          ▼
              │                                   concat(128) → MLP(128)
              ▼                                          ▼
        user vector (64, L2-norm)          item vector (64, L2-norm)
              └──────────────┬───────────────────────────┘
                              ▼
                    dot product = relevance score

3. Набор данных

McAuley-Lab/Amazon-Reviews-2023 (лаборатория McAuley, UCSD), категория Video_Games — сырые отзывы + метаданные товаров, загруженные напрямую с HuggingFace.

Шаг

Количество

Сырые отзывы

4 624 615

Сырые пользователи / товары

2 766 656 / 137 249

После 5-core фильтрации (пользователи и товары с ≥5 взаимодействиями)

857 505 взаимодействий

Пользователи / товары (после фильтрации)

98 906 / 26 354

Взаимодействия Train / Valid / Test

659 693 / 98 906 / 98 906

Протокол разбиения — leave-last-two-out для каждого пользователя, отсортировано по времени: самое последнее взаимодействие каждого пользователя → тест, второе с конца → валидация, остальные → обучение. Это временное разбиение, поэтому модель оценивается на предсказании действительно будущего поведения относительно того, на чем она обучалась, а не на случайно удержанных взаимодействиях (что привело бы к утечке будущей информации в обучение и завышению показателей).

4. Оценка (реальные, измеренные числа)

Оценка использует ранжирование по полному каталогу — каждый кандидат оценивается по всем 26 354 товарам, а не по небольшой выборке отрицательных примеров. Оценка с семплированными отрицательными примерами (распространенная в старых статьях по RecSys, например, ранжирование только по 99 случайным отрицательным) известна тем, что существенно завышает офлайн-метрики, поэтому здесь используется более сложный и честный протокол. Уже просмотренные пользователем товары исключаются из его собственного ранжирования кандидатов.

Тестовый набор — 98 906 пользователей, у каждого удержанное финальное взаимодействие:

Метрика

Значение

Recall@10

1.40%

NDCG@10

0.70%

HitRate@10

1.40% (идентично Recall@10 при leave-one-out: ровно один релевантный товар на пользователя)

Для контекста: случайный шанс в каталоге из 26 354 товаров при k=10 составляет 10/26 354 = 0.038%. Обученная модель примерно в 37 раз лучше случайной при ранжировании по полному каталогу.

Валидационный Recall@10 достиг пика 2.43% (эпоха 142/150) во время обучения — тестовое число ниже, потому что тестовое взаимодействие — это самое дальнее взаимодействие каждого пользователя в будущее относительно его обучающей истории, что по своей сути является более сложным предсказанием. Этот разрыв — ожидаемое поведение для временного разбиения, а не ошибка. Тестовое число (1.40%) — это то, которое следует цитировать где угодно — валидация использовалась только для выбора лучшей контрольной точки во время обучения, поэтому сообщение о ней как о финальном результате было бы формой подбора результатов.

Полная кривая обучения: models/train_history.csv. Сырые результаты: models/test_results.json.

5. MCP-инструменты (mcp_server.py)

Инструмент

Описание

recommend_for_user(user_id, k)

Топ-k персонализированных рекомендаций, исключает товары, с которыми пользователь уже взаимодействовал

similar_items(item_id, k)

Сходство товар-товар через обученные встраивания башни товаров

search_items(query_text, k)

Семантический поиск с холодным стартом по названиям товаров (только MiniLM — работает для товаров, по которым у коллаборативной модели слабый сигнал)

explain_recommendation(user_id, item_id)

Оценка сходства плюс прошлые товары пользователя, наиболее похожие на целевой, для интерпретируемости

6. Фронтенд (streamlit_app.py)

Чат-интерфейс в том же стиле, что и weather-mcp-server: он запускает MCP-сервер как подпроцесс через stdio, получает схемы его инструментов, преобразует их в объявления вызова функций Gemini и выполняет агентный цикл — Gemini решает, какой из 4 инструментов вызвать (если вообще вызывать), на основе вашего сообщения, инструмент выполняется на реальной обученной модели, и результат возвращается для финального ответа на естественном языке. На боковой панели показано описание каждого инструмента плюс пример в один клик с реальными ID из обученного каталога, а также раскрывающийся блок со статистикой оценки модели.

7. Локальный запуск

uv venv --python 3.11 .venv
uv pip install -p .venv/bin/python -r requirements.txt

# one-time: reproduce the trained model from scratch
.venv/bin/python src/data_prep.py                  # downloads + filters the dataset
.venv/bin/python src/precompute_text_embeddings.py
.venv/bin/python src/train.py                       # ~150 epochs, ~40s/epoch on an M2 CPU
.venv/bin/python src/evaluate.py                    # writes models/test_results.json
.venv/bin/python src/build_index.py                 # builds FAISS indices for serving

# run the MCP server standalone (stdio transport)
.venv/bin/python mcp_server.py

# or run the chat frontend (spawns the MCP server itself)
cp .streamlit/secrets.toml.example .streamlit/secrets.toml   # then fill in your key
.venv/bin/streamlit run streamlit_app.py

Если .streamlit/secrets.toml (или переменная окружения GEMINI_API_KEY) не задан, приложение переключается на запрос ключа на боковой панели во время выполнения.

Примечание для macOS

faiss и torch конфликтуют из-за инициализации OpenMP runtime на macOS, что вызывает segfault при вызовах поиска FAISS, если torch/numpy не импортированы до faiss, с установленными KMP_DUPLICATE_LIB_OK=TRUE и OMP_NUM_THREADS=1. Оба уже обработаны внутри mcp_server.py и src/build_index.py.

8. Развертывание на Streamlit Community Cloud

  1. Запушьте этот репозиторий на GitHub (публичный или приватный — Community Cloud может развернуть оба для личного аккаунта).

  2. Перейдите на share.streamlit.io, нажмите New app и укажите этот репозиторий с streamlit_app.py в качестве точки входа.

  3. В Settings → Secrets приложения добавьте:

    GEMINI_API_KEY = "your_gemini_api_key_here"

    Это тот же механизм, что и .streamlit/secrets.toml локально — ключ хранится только в хранилище секретов Streamlit, никогда в репозитории или истории git, и приложение читает его автоматически, так что посетителям никогда не нужно вводить ключ.

  4. Разверните. Первый запуск будет медленным (~1-2 мин), пока загружается модель MiniLM и индексы FAISS; последующие загрузки быстрые.

Примечание о размере репозитория: models/ (~110 МБ: обученная контрольная точка + индексы FAISS) включен в коммит, чтобы развернутому приложению не нужно было переобучаться при каждом холодном старте. data/raw/ (~2.9 ГБ сырых загрузок с HuggingFace) находится в .gitignore и нужен только если вы хотите воспроизвести обучение с нуля.

9. Технологический стек

Python, PyTorch, FAISS, Sentence-Transformers (MiniLM), FastMCP, MCP Python SDK, Google Gemini API, Streamlit, pandas, HuggingFace datasets/huggingface_hub.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    Provides RAG-based knowledge retrieval and document management as MCP tools, supporting hybrid search, reranking, and retrieval process visualization.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    A modular RAG framework exposing knowledge retrieval tools via MCP, enabling AI assistants to perform hybrid search, reranking, and multimodal document queries with full observability and evaluation.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables agents to turn documents into vectors, perform approximate nearest neighbor search with HNSW, and filter by metadata via MCP tools for RAG workflows.
    MIT