Возьмите три фразы:
- «Завтра еду в горы кататься на лыжах».
- «Планирую зимний отпуск в Альпах».
- «Завтра еду в горы, но кататься не буду — лечу спину».
Человек мгновенно видит: первая и вторая близки по смыслу, хотя не делят ни одного значимого слова. Первая и третья делят почти все слова, но говорят о разном. Поисковая система, которая работает с текстом как с набором букв, увидит ровно наоборот. Эмбеддинги придуманы, чтобы починить первую половину этой картины — и они честно ломаются на второй.
#Что такое вектор смысла
Эмбеддер — это отдельная модель, которая принимает кусок текста и выдаёт массив чисел фиксированной длины. Не текст, не сжатый текст, не пересказ — просто числа. Длина массива задаётся моделью: например, nomic-embed-text выдаёт 768 чисел, bge-m3 — 1024. Это число называют размерностью.
Смысл отдельного числа в массиве человеку недоступен, и пытаться его интерпретировать бесполезно. Значение имеет только одно: насколько два массива близки друг к другу. Формально близость считают через косинус угла между векторами — подробный разбор самой меры есть в справке про косинусную близость.
Полезная картинка: представьте карту, где у каждой фразы есть координаты. Фразы про зимний отдых оказываются в одном районе, фразы про покупку молока — в другом. Векторный поиск — это «покажи всё, что ближе полукилометра к этой точке».
#Как это выглядит на наших трёх фразах
Проверить утверждение «близость по смыслу превращается в близость координат» можно самому, за пять минут, без библиотек машинного обучения. Метод такой:
- Поднимите Ollama и скачайте модель-эмбеддер:
ollama pull nomic-embed-text. - Для каждой из трёх фраз получите вектор запросом к
/api/embeddings. - Посчитайте косинус между парами: сумма произведений соответствующих чисел, делённая на произведение длин векторов.
Что вы увидите — предсказуемо по направлению и непредсказуемо по величине: пара «лыжи — Альпы» окажется ближе, чем пара «лыжи — молоко», а пара «еду в горы кататься» и «еду в горы, но кататься не буду» окажется близкой, хотя смысл противоположный. Конкретные числа приводить бессмысленно: они зависят от модели, от версии и от того, добавили ли вы служебные префиксы. Важна не величина, а порядок.
Про префиксы стоит сказать отдельно, потому что на этом теряют качество молча. Модели семейства nomic обучены с пометками роли текста: хранимый документ подаётся как search_document: …, поисковый запрос — как search_query: …. Ollama сама их не добавляет. Persona добавляет их в app/memory_vec.py перед вызовом эмбеддера — и там же в комментарии зафиксировано следствие: при смене схемы префиксов индекс нужно строить заново, иначе запросы и документы окажутся в разных системах координат.
#Главная ловушка: близко не значит релевантно
Векторный поиск возвращает самое похожее, а не самое правильное. Это не баг реализации, это определение операции. Отсюда три устойчивых класса промахов.
| Класс | Пример запроса | Что вернётся вместо ответа |
|---|---|---|
| Отрицание | «встреча не состоялась» | записи про состоявшиеся встречи |
| Близкие сущности | «ошибка E-4021» | абзацы про другие коды ошибок |
| Смена роли | «что я должен Олегу» | «что Олег должен мне» |
Отрицание — самый коварный. Частица «не» почти не сдвигает вектор, потому что модель кодирует тему, а не логику высказывания. Для памяти это критично: «я больше не работаю в этой компании» и «я работаю в этой компании» лежат рядом.
Второй класс объясняет, почему поиск по идентификаторам, артикулам и кодам ошибок нужно вести не векторами. Эмбеддер видит в E-4021 не уникальный ключ, а строку, похожую на E-4022. Разбор того, какие типы запросов кому отдавать, — в сравнении семантического поиска и ключевых слов.
Третий класс — потеря направления связи. Векторное представление плохо удерживает, кто субъект, а кто объект, потому что оба слова в предложении присутствуют.
#Что из этого следует при выборе модели
Эмбеддер — отдельная модель, и её берут по остаточному принципу чаще всего. Три практических правила:
Размерность нужно решить один раз. Она попадает в схему хранилища. В Persona векторная таблица объявлена как FLOAT[768] в миграции 186_vec_memory.sql — под nomic-embed-text. Модель с другой размерностью потребует новой таблицы и полной переиндексации, это не настройка, а миграция.
Многоязычность не бесплатна. Модель, обученная преимущественно на английском, на русском тексте даёт заметно менее осмысленные соседства. Проверять это надо на своём корпусе — сравнение моделей и их поведения на русском есть в обзоре embed-моделей.
Смена модели = переиндексация всего. Векторы, посчитанные разными моделями, несопоставимы в принципе. Нельзя «дообавить» новые записи новой моделью к старому индексу: получится хранилище, где половина точек лежит в чужой системе координат, и поиск будет тихо возвращать мусор.
#Нужна ли под это отдельная база
Не обязательно, и это стоит проверить до того, как поднимать инфраструктуру. На объёмах личного архива вектор — это просто ещё одна колонка рядом с текстом, а поиск ближайших соседей выполняется расширением к обычной базе. Когда отдельное хранилище действительно окупается и с какого порога — разобрано в материале про векторные базы данных.
Полезно помнить и место эмбеддингов в общей схеме: они отвечают за один шаг конвейера — превращение текста в сравнимую форму. Всё остальное — нарезка, отбор, вклейка в промпт — живёт вокруг, и описано в статье о том, как работает память у ИИ и в разборе устройства RAG.
#Частые вопросы
#Можно ли по эмбеддингу восстановить исходный текст?
Точно — нет, это не обратимое сжатие. Но и считать вектор анонимным нельзя: по нему восстановима тема и во многих случаях достаточно деталей, чтобы понять, о чём был фрагмент. Вектор личной переписки — такие же личные данные, как и сама переписка.
#Почему два разных эмбеддера нельзя сравнивать между собой?
Потому что каждая модель строит собственную систему координат в процессе обучения. Одинаковая размерность ничего не гарантирует: 768 чисел от одной модели и 768 от другой описывают разные пространства, и косинус между ними — случайное число.
#Сколько места занимают векторы?
Один вектор из 768 чисел одинарной точности — это около трёх килобайт. На десять тысяч записей выходят десятки мегабайт: заметно больше текста, но не критично для персонального архива. Считать надо от числа записей, а не от их длины.
#Нужен ли GPU, чтобы считать эмбеддинги?
Нет. Небольшие модели-эмбеддеры считаются на процессоре с приемлемой скоростью. Узкое место обычно другое: первый запрос после простоя грузит модель в память, и это может занять десятки секунд — поэтому в Persona для запроса к эмбеддеру выставлен жёсткий потолок ожидания, после которого поиск деградирует на ключевые слова, а не подвешивает чат.
#Что делать, если поиск по смыслу находит не то?
Сначала проверьте, не относится ли запрос к одному из трёх классов из таблицы выше — тогда лечится не настройкой, а добавлением поиска по ключевым словам рядом. Если запрос обычный, а результаты всё равно мимо, вероятнее всего проблема в модели-эмбеддере или в том, что индекс построен другой моделью.
#Вывод
Эмбеддинг — это координаты фразы, а не её понимание. Он отлично решает задачу «найди похожее по теме» и систематически проваливает задачи «найди ровно это» и «учти отрицание». Если ваши запросы — это темы и формулировки своими словами, векторы окупятся. Если это имена, коды, номера и точные цитаты — начинайте с ключевых слов и добавляйте векторы только как второй источник кандидатов.