Когда текст перестал быть главным
Ваш классический RAG умеет искать по тексту. Умеет работать с PDF, с документами. Но что делать с картинкой, которая стоит тысячу слов? Или с запросом, который описывает изображение? «Найди мне графики с ростом продаж за 2025 год» — и какого черта ваш векторный поиск по тексту ничего не находит, хотя в базе десятки таких графиков? (Потому что они в PNG, а не в описаниях alt-текста, которые кто-то забыл добавить).
Вот она, проблема. Мир мультимодален. Данные живут в разных измерениях — текст, изображения, аудио, видео. А ваш поисковый движок слеп на одно из них. Традиционные эмбеддинги (те самые, что из Harrier-OSS или zembed-1) работают в одной модальности. Они не понимают, что текст «рыжий кот на диване» и фотография этого кота — про одно и то же.
Пока вы читаете это, в вашем продакшене уже лежат тысячи неиндексированных изображений, скриншотов, схем и графиков. Они молчат. И поиск по ним — либо через костыльные теги, либо через дорогой и медленный анализ с помощью тяжелых мультимодальных LLM. Есть способ лучше.
Мультимодальные эмбеддинги — это не магия, это алгебра
Забудьте про «просто скормим картинку нейросети». Мультимодальные эмбеддинги — это модели, которые учатся представлять информацию из разных модальностей в одном векторном пространстве. Текст и изображение переводятся в векторы, которые можно сравнивать между собой. Косинусная близость между вектором текстового запроса «диаграмма роста пользователей» и вектором PNG-графика будет высокой, если модель обучена правильно.
И тут на сцену выходит Sentence Transformers. Да, та самая библиотека, которая раньше ассоциировалась только с текстом. К 2026 году она стала полноценным фреймворком для мультимодальности. Они не просто обернули CLIP — они создали целую экосистему моделей и реранкеров, которые делают кросс-модальный поиск практичным, а не академическим упражнением.
Зачем здесь реранкер? Он же для текста!
Самый частый вопрос. Вы получили топ-20 изображений по текстовому запросу через косинусную близость эмбеддингов. Первые пять — релевантны, остальные — так себе. Реранкер (модель для переранжирования) — это второй этап, который смотрит на пары «запрос-кандидат» и точнее оценивает релевантность. Он обучается на более сложных задачах и часто использует кросс-энкодеры.
В мультимодальном контексте появились реранкеры, которые работают с парами «текст-изображение». Они не генерируют эмбеддинги для всего корпуса (это дорого), а только для конкретной пары на этапе ранжирования. Результат — качество поиска взлетает на 15-30%, особенно для сложных запросов. А в RAG каждый процент на счету, потому что от этого зависит, какую информацию получит LLM.
1 Ставим всё, что нужно. Правильно.
Не делайте pip install sentence-transformers[all]. Вы установите тонну ненужного. Нам нужен точный набор. И да, Python 3.10+ обязателен, потому что в более старых версиях некоторые зависимости новых моделей ломаются.
# Создаем чистую среду. Всегда.
python -m venv venv_multimodal
source venv_multimodal/bin/activate # или .\\venv_multimodal\\Scripts\\activate на Windows
# Ядро
pip install sentence-transformers==3.0.0 # Актуально на 09.04.2026
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124 # Для CUDA 12.4, актуально в 2026
# Для работы с изображениями
pip install Pillow
pip install opencv-python-headless # Если нужна продвинутая предобработка
# Для векторной БД в примере - Qdrant, но подойдет любая
pip install qdrant-client
Не используйте torch из системного питона. Не используйте pip install sentence-transformers без указания версии. Через месяц вы обновите что-то случайно, и пайплайн сломается. Фиксируйте версии в requirements.txt. Прямо сейчас.
2 Выбираем модель. Не ту, что на хабре хвалят.
На 09.04.2026 лидеры — это семейства OpenCLIP и собственные разработки Sentence Transformers. Но не берите первую попавшуюся. Смотрите на задачи:
- Для общего поиска «текст-изображение»:
sentence-transformers/clip-ViT-B-32-multilingual-v1— хороший баланс скорости и качества, поддерживает множество языков. - Для максимальной точности:
sentence-transformers/clip-ViT-H-14— тяжелая, но если у вас GPU с памятью от 8 ГБ, то она того стоит. - Для специализированных данных (медицинские снимки, схемы): ищите fine-tuned версии на Hugging Face Model Hub. Например,
microsoft/BiomedCLIP-PubMedBERT_256-vit_base_patch16_224— для медицинских изображений.
Реранкеры — отдельная история. На момент апреля 2026 года модель cross-encoder/ms-marco-MiniLM-L-12-v2 устарела для текста, а для мультимодальности смотрите на Salesforce/blip-itm-rerank или специализированные sentence-transformers/clip-ViT-B-32-multilingual-v1-rerank.
3 Готовим данные. Не просто ресайз.
Ошибка номер один: скормить модели сырые изображения разных размеров и форматов. Модели ожидают определенного препроцессинга. И нет, это не просто resize((224,224)).
from PIL import Image
from sentence_transformers import SentenceTransformer, util
import torch
# Загружаем модель для эмбеддингов изображений и текста
# Актуальная мультимодальная модель на 2026 год
model = SentenceTransformer('sentence-transformers/clip-ViT-B-32-multilingual-v1', device='cuda' if torch.cuda.is_available() else 'cpu')
# Функция для загрузки и предобработки изображения
def load_and_preprocess_image(image_path):
image = Image.open(image_path).convert('RGB')
# Модель сама выполнит нужный препроцессинг (нормализацию, ресайз) при вызове encode
return image
# Для текста ничего особенного не нужно
text = "График роста доходов по кварталам 2025 года"
# Получаем эмбеддинги
image_emb = model.encode(load_and_preprocess_image("chart.png"))
text_emb = model.encode(text)
# Считаем схожесть - это и есть кросс-модальная близость
similarity = util.cos_sim(text_emb, image_emb)
print(f"Схожесть текста и изображения: {similarity.item():.4f}")
Важно: если у вас тысячи изображений, не делайте encode по одному. Используйте батчи. И кэшируйте результаты, чтобы не вычислять эмбеддинги каждый раз при перезапуске. Для этого идеально подходит модульный подход к RAG пайплайну, где эмбеддинг-модуль вынесен отдельно.
4 Индексируем в векторной БД. С умом.
Здесь ловушка: мультимодальные эмбеддинги обычно имеют размерность 512 или 768. Это не 1536, как у text-embedding-3-large от OpenAI, но и не 384. Проверьте, какая размерность у вашей модели (model.get_sentence_embedding_dimension()).
from qdrant_client import QdrantClient
from qdrant_client.http import models
import uuid
# Подключаемся к Qdrant (локально или облако)
client = QdrantClient(host="localhost", port=6333)
collection_name = "multimodal_docs"
# Создаем коллекцию с правильной размерностью
# Для clip-ViT-B-32 размерность 512
client.recreate_collection(
collection_name=collection_name,
vectors_config=models.VectorParams(
size=512, # Указываем точное значение!
distance=models.Distance.COSINE
)
)
# Предположим, у нас есть список словарей с данными
documents = [
{"id": 1, "image_path": "charts/sales_q1_2025.png", "description": "График продаж за первый квартал 2025"},
{"id": 2, "image_path": "screenshots/dashboard.png", "description": "Дашборд с метриками активности пользователей"},
# ...
]
points = []
for doc in documents:
# Генерируем эмбеддинг для изображения
img = load_and_preprocess_image(doc["image_path"])
img_emb = model.encode(img).tolist()
# Создаем точку для Qdrant
point = models.PointStruct(
id=doc["id"],
vector=img_emb,
payload={
"image_path": doc["image_path"],
"description": doc["description"],
"type": "image" # Метаданные для фильтрации
}
)
points.append(point)
# Загружаем батчем
client.upsert(collection_name=collection_name, points=points)
Почему не стоит хранить эмбеддинги текста и изображений в одной коллекции? Можно, но тогда нужно размечать в payload тип модальности. А если у вас гибридный документ (текст + картинка), то лучше создать отдельный эмбеддинг для каждого и связывать через общий ID. Или использовать продвинутые стратегии, как в мультимодальном RAG 2025.
5 Ищем. И потом переранжируем.
Первый этап — быстрый поиск по косинусной близости. Второй — точное ранжирование с реранкером. Не пропускайте второй, если хотите качество. Особенно когда в топе несколько очень похожих кандидатов.
from sentence_transformers import CrossEncoder
# Поиск
query = "Скриншот дашборда с DAU и MAU"
query_emb = model.encode(query)
search_result = client.search(
collection_name=collection_name,
query_vector=query_emb.tolist(),
limit=10 # Берем больше кандидатов, чем нужно, для реранкинга
)
# Теперь реранкинг
# Загружаем мультимодальный реранкер (актуальная модель на 2026 год)
reranker = CrossEncoder('Salesforce/blip-itm-rerank', max_length=512)
# Готовим пары [запрос, описание_кандидата] для реранкера
# Реранкер может работать с текстовыми описаниями кандидатов, а не с самими изображениями (это быстрее)
pairs = [[query, hit.payload["description"]] for hit in search_result]
# Получаем скоре релевантности для каждой пары
rerank_scores = reranker.predict(pairs)
# Сортируем результаты по скорам реранкера
reranked_results = [
{"hit": hit, "rerank_score": score}
for hit, score in zip(search_result, rerank_scores)
]
reranked_results.sort(key=lambda x: x["rerank_score"], reverse=True)
# Берем топ-3 после реранкинга
top_k = 3
final_results = reranked_results[:top_k]
for res in final_results:
print(f"Путь: {res['hit'].payload['image_path']}, Реранк-скор: {res['rerank_score']:.4f}")
Реранкер работает с парами «текст-текст» (запрос и описание изображения). Это быстрее, чем повторно кодировать изображение. Но если описаний нет, можно использовать реранкер, который принимает напрямую эмбеддинги — ищите такие модели, они появляются.
6 Встраиваем в RAG. Не забываем про контекст.
Теперь у вас есть релевантные изображения (или их описания). Как передать это LLM? Самый простой способ — добавить в промпт текстовые описания найденных изображений. Но если вам нужно, чтобы модель "видела" изображение, то нужно использовать мультимодальную LLM (например, GPT-4V, Claude 3.5 Sonnet или открытые аналоги).
Пайплайн выглядит так:
- Пользователь задает вопрос.
- Система выполняет кросс-модальный поиск (текст -> изображения) с реранкингом.
- Из найденных изображений извлекаются текстовые метаданные (путь, описание) и, при необходимости, сами изображения кодируются в base64.
- В промпт для мультимодальной LLM добавляются текстовые описания и, возможно, ссылки на изображения или base64.
- LLM дает ответ, основанный на мультимодальном контексте.
Это уже не просто RAG, а Agentic RAG с гибридным поиском, где агенту доступны разные типы данных.
Где всё ломается. Предупреждён — вооружён.
- Память GPU. Модели типа ViT-H-14 жрут память. Индексируйте на мощной машине, а для инференса используйте квантование (например, с помощью техник, описанных для pplx-embed). Или используйте CPU — будет медленнее, но стабильнее.
- Несоответствие распределений. Модель обучалась на одном наборе данных (например, Flickr30k), а ваши изображения — медицинские рентгеновские снимки. Качество упадет. Ищите дообученные модели или делайте fine-tuning на своих данных.
- Текстовые описания изображений. Если в вашей базе у изображений нет текстовых описаний, реранкеру нечего будет анализировать. Либо генерируйте описания с помощью image captioning модели (BLIP, OFA), либо используйте реранкеры, работающие напрямую с эмбеддингами.
- Скорость. Кросс-энкодеры (реранкеры) работают медленнее, чем би-энкодеры (для первичного поиска). Не применяйте их ко всему корпусу. Только к топ-N кандидатам от быстрого поиска.
Вопросы, которые вы хотели задать, но боялись
| Вопрос | Ответ |
|---|---|
| Можно ли искать по изображению, чтобы найти текст? | Да, это обратный кросс-модальный поиск. Закодируйте изображение-запрос, найдите ближайшие текстовые эмбеддинги в коллекции. Работает ровно так же. |
| Sentence Transformers или чистый CLIP от OpenAI? | Sentence Transformers дает удобный API, поддержку множества моделей и реранкеров. Чистый CLIP (через OpenAI API) — это черный ящик, дороже и без возможности fine-tuning. Для контроля и кастомизации — однозначно Sentence Transformers. |
| Как добавить аудио и видео? | Принцип тот же, но нужны модели, которые эмбеддят аудио и видео в то же пространство. Ищите мультимодальные модели «текст-аудио» (например, CLAP) или используйте отдельные энкодеры, а потом комбинируйте эмбеддинги (но это сложнее). |
| Какая векторная БД лучше? | Любая, которая поддерживает cosine similarity и фильтрацию по метаданным. Qdrant, Weaviate, Pinecone, pgvector. Выбор зависит от масштаба и инфраструктуры. Для начала — Qdrant локально. |
И последнее — зачем всё это?
Потому что будущее поиска — не в тексте. Оно в смысле. А смысл живет в диаграммах, в интерфейсах, в схемах процессов, которые кто-то заскринил и забыл. Ваша задача — заставить этот скрытый контекст работать. Мультимодальные эмбеддинги и реранкеры — это не панацея, а очень острый инструмент. Который, если его правильно настроить, разрежет проблему «у нас тут куча картинок, но мы не можем по ним искать» за пару дней работы.
Начните с малого: выберите одну папку со скриншотами, проиндексируйте, попробуйте найти по ним через текстовые запросы. Увидите, как это меняет представление о том, что можно искать. И потом уже масштабируйте на весь пайплайн, который должен укладываться в 400 мс.
Дерзайте. И не забудьте зафиксировать версии библиотек.