Эмбеддинги простыми словами: что это и как работают (с кодом)

Автор: Обновлено

Что такое эмбеддинг?

Эмбеддинг - это вектор (список чисел), в который модель превращает текст, изображение или другой объект так, что близкие по смыслу объекты получают близкие векторы. Поэтому смысл можно сравнивать арифметикой: расстоянием или косинусом угла между векторами.

TL;DR

  • -Эмбеддинг - вектор из сотен или тысяч чисел, который описывает смысл текста. Близкие по смыслу тексты получают близкие векторы, и их можно сравнивать косинусом.
  • -У OpenAI, Voyage, Gemini и Ollama векторы нормализованы до длины 1, поэтому косинус совпадает со скалярным произведением, а евклидово расстояние даёт тот же порядок результатов.
  • -Документы и запросы нужно векторизовать одной и той же моделью. Некоторым моделям нужны префиксы: nomic-embed-text ждёт search_query: и search_document:, multilingual-e5 ждёт query: и passage:.
  • -Вектор можно укоротить, если модель обучена по схеме Matryoshka (OpenAI v3, Voyage 4, Gemini, Qwen3-Embedding, nomic v1.5). После ручной обрезки вектор нормализуют заново.
  • -Для русского текста английская модель не подходит. В замере автора на 10 парах RU/EN nomic-embed-text нашла правильный перевод в 2 случаях из 10, многоязычная bge-m3 во всех 10.

Эмбеддинг - это вектор (список чисел), в который модель превращает текст, изображение или другой объект так, что близкие по смыслу объекты получают близкие векторы. Фразы «кот спит на диване» и «котёнок дремлет на кушетке» не делят ни одного слова, но у хорошей модели их векторы окажутся рядом, а вектор фразы про квартальную выручку уйдёт далеко. Поэтому смысл можно сравнивать арифметикой: расстоянием или косинусом угла между векторами.

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

Статья о том, как устроен эмбеддинг на уровне интуиции, как считать близость, как выбрать модель на ноябрь 2026 года и как укоротить вектор без потери качества. В примере на Python векторы считаются локально через Ollama, а цифры сравнения английской и многоязычной модели на русских и английских фразах взяты из замера автора 2026-11-08 на Ollama 0.40.14.

Что такое эмбеддинг

Эмбеддинг-модель принимает текст и возвращает вектор фиксированной длины: 768, 1024, 1536 или 3072 числа, в зависимости от модели. Длина не зависит от того, подали вы одно слово или страницу. Модель обучена так, чтобы тексты с похожим смыслом попадали в соседние точки этого многомерного пространства.

С чем эмбеддинг путают:

  • Не сжатие в архивном смысле. Из вектора нельзя восстановить исходный текст. Модель сохраняет то, что нужно для сравнения по смыслу, и теряет формулировки.
  • Не перевод. Вектор не текст на другом языке. Многоязычная модель кладёт фразу и её перевод рядом, но сам вектор ни на каком языке не написан.
  • Не словарь значений слова. Эмбеддинг не перечисляет значения. Он даёт точку в пространстве, и смысл виден только по тому, какие точки оказались рядом.

Формально эмбеддинг - это отображение: правило, которое ставит объекту в соответствие вектор. Отсюда и название, «вложение» объектов в векторное пространство.

Аналогия: координаты на карте смыслов

На обычной карте у города две координаты, и по ним видно, какие города рядом. Эмбеддинг делает то же для смысла, только координат сотни. Отдельная координата сама по себе ничего не значит: нет оси «про животных» или «про деньги». Значение имеет только взаимное расположение точек. Поэтому вектор одного текста бесполезен, полезно сравнение двух векторов.

От word2vec к эмбеддингам предложений

Массовой идея стала после word2vec: статья Миколова и соавторов Efficient Estimation of Word Representations in Vector Space вышла на arXiv в январе 2013 года. В ней приводится известный пример: вектор «King» минус «Man» плюс «Woman» ближе всего к вектору «Queen».

У word2vec один вектор на слово. Слово «ключ» получает одну точку, будь то ключ от двери или родник. Современные модели векторизуют предложение или абзац целиком и учитывают контекст: «ключ» в двух разных фразах влияет на вектор по-разному. Для поиска это и нужно, потому что люди ищут ответы на вопросы, а не отдельные слова.

Как это работает: вектор, расстояние, косинус

Размерность

Размерность - это количество чисел в векторе. У text-embedding-3-small от OpenAI по умолчанию 1536, у text-embedding-3-large 3072, у bge-m3 1024, у nomic-embed-text 768. Больше измерений обычно значит больше деталей смысла, но и больше места.

Место считается просто. Одно число float32 занимает 4 байта, вектор из 1536 чисел занимает 6 КБ, миллион таких векторов около 6 ГБ без индекса. Поэтому при выборе модели размерность считают вместе с качеством. Voyage, например, отдаёт векторы в int8 и бинарном виде, это по документации уменьшает объём в 4 и в 32 раза соответственно.

Косинус, скалярное произведение и евклидово расстояние

Близость двух векторов меряют тремя способами:

МераЧто считаетКогда использовать
Косинусная близостьУгол между векторами, от -1 до 1Значение по умолчанию для текста
Скалярное произведениеУгол и длину векторов вместеЕсли векторы нормализованы, это тот же косинус, но быстрее
Евклидово расстояниеПрямую дистанцию между концами векторовДля нормализованных векторов даёт тот же порядок, что и косинус

OpenAI, Voyage и Ollama возвращают векторы, нормализованные до длины 1. Это прямо сказано в документации OpenAI и в гайде по эмбеддингам у Anthropic (про Voyage), а Ollama пишет, что /api/embed отдаёт L2-нормализованные векторы. Для таких векторов косинус равен скалярному произведению, а косинус и евклидово расстояние ранжируют результаты одинаково. Выбор меры в этом случае влияет на скорость, а не на результат.

Нормализация теряется, если вектор обрезать вручную: после этого скалярное произведение уже не равно косинусу.

Где применяются эмбеддинги

  • Семантический поиск. Запрос и документы превращаются в векторы, ближайшие документы и есть результат. Работает, потому что близость векторов отражает близость смысла, а не совпадение слов.
  • RAG. Тот же поиск, только найденные фрагменты отдаются языковой модели как контекст для ответа.
  • Кластеризация. Отзывы, тикеты или статьи группируются по смыслу алгоритмом вроде k-means прямо по векторам.
  • Рекомендации. «Похожие статьи» и «похожие товары» ищутся по ближайшим векторам описаний.
  • Классификация. Поверх векторов обучают простой классификатор или сравнивают текст с векторами эталонных примеров каждого класса.
  • Дедупликация. Пары текстов с очень высоким косинусом чаще всего оказываются дублями или перефразами друг друга.

Пример: считаем близость руками

Пример ниже воспроизводит замер автора: 10 русских фраз и их английские переводы, две модели в Ollama. Для каждой модели считаются три числа: средний косинус фразы и её перевода, средний косинус с чужими переводами (90 несовпадающих пар) и top-1, то есть для скольких русских фраз ближайшим английским вектором оказался их собственный перевод.

Нужна запущенная Ollama и две модели: nomic-embed-text (английская, 274 MB) и bge-m3 (многоязычная, 1,2 GB).

ollama pull nomic-embed-text
ollama pull bge-m3

Код использует только стандартную библиотеку Python. Запрос идёт в нативный эндпоинт /api/embed: поле model и поле input со строкой или массивом строк, в ответе массив embeddings.

import json, math, urllib.request

def embed(model, texts):
    req = urllib.request.Request(
        "http://localhost:11434/api/embed",
        json.dumps({"model": model, "input": texts}).encode(),
        {"Content-Type": "application/json"})
    return json.load(urllib.request.urlopen(req, timeout=600))["embeddings"]

def cos(a, b):
    dot = sum(x * y for x, y in zip(a, b))
    return dot / math.sqrt(sum(x * x for x in a) * sum(y * y for y in b))

PAIRS = [("Кот спит на диване.", "The cat is sleeping on the sofa."),
         ("Поезд опоздал на сорок минут.", "The train was forty minutes late."),
         # остальные пары из таблицы ниже
        ]

Функция cos считает косинус полностью, хотя у Ollama векторы уже нормализованы и хватило бы скалярного произведения. Так код работает с любым источником векторов.

n = len(PAIRS)
for model, prefix in (("nomic-embed-text", "clustering: "), ("bge-m3", "")):
    ru = embed(model, [prefix + r for r, _ in PAIRS])
    en = embed(model, [prefix + e for _, e in PAIRS])
    match = [cos(ru[i], en[i]) for i in range(n)]
    other = [cos(ru[i], en[j]) for i in range(n) for j in range(n) if i != j]
    top1 = sum(max(range(n), key=lambda j: cos(ru[i], en[j])) == i
               for i in range(n))
    print(model, len(ru[0]), round(sum(match) / n, 3),
          round(sum(other) / len(other), 3), f"top-1 {top1}/{n}")

У nomic-embed-text префикс задачи обязателен по карточке модели. Для сравнения двух текстов между собой подходит clustering: . Для поиска нужна пара search_query: и search_document: . У bge-m3 префиксы не нужны, это прямо сказано в её карточке.

Результат и как его читать

Замер автора 2026-11-08, Ollama 0.40.14 на Mac с Apple Silicon, полный набор из 10 пар:

Модель (Ollama)РазмерностьКосинус перевода, среднее (мин.)Косинус с чужой фразой, среднее (макс.)top-1 RU→EN
nomic-embed-text v1.5 (английская), префикс clustering: 7680,587 (0,539)0,583 (0,622)2/10
bge-m3 (многоязычная)10240,921 (0,821)0,465 (0,630)10/10

У английской модели перевод и случайная фраза почти неотличимы: 0,587 против 0,583. Диапазоны пересекаются: худший перевод (0,539) получил меньший косинус, чем самая похожая чужая пара (0,622), и поиск нашёл правильный перевод только в 2 случаях из 10. У bge-m3 разрыв большой: 0,92 против 0,47, и даже самый слабый перевод (0,821) выше самой похожей чужой фразы (0,630), поэтому и результат 10 из 10.

Косинус по каждой паре:

Русская фразаАнглийский переводnomic-embed-textbge-m3
Кот спит на диване.The cat is sleeping on the sofa.0,6170,821
Завтра обещают сильный дождь.Heavy rain is forecast for tomorrow.0,5770,944
Сервер перестал отвечать после обновления.The server stopped responding after the update.0,5770,926
Я забыл ключи от машины дома.I left my car keys at home.0,6020,931
Цены на билеты выросли вдвое.Ticket prices have doubled.0,5540,971
Модель плохо справляется с длинными документами.The model struggles with long documents.0,5900,886
Врач посоветовал больше гулять.The doctor advised walking more.0,6180,948
Поезд опоздал на сорок минут.The train was forty minutes late.0,6060,920
Как вернуть деньги за неудачную покупку?How do I get a refund for a bad purchase?0,5390,900
Библиотека закрывается в восемь вечера.The library closes at eight in the evening.0,5890,963

Сравнивать надо разрыв внутри одной модели, а не абсолютные числа между моделями. Косинус 0,6 у nomic-embed-text попадает и в переводы, и в чужие пары, а у bge-m3 это уровень несвязанных фраз: все переводы у неё выше 0,82.

Ограничения замера: 10 коротких бытовых фраз, одна пара языков, одна метрика. Это иллюстрация того, что английская модель не годится для русского текста, а не бенчмарк. По ollama show у bge-m3 566,7M параметров и контекст 8192 токена, у nomic-embed-text 137M параметров и контекст 2048 в Ollama.

Как поставить Ollama и выбрать модель под память, разобрано в гайде по Ollama и в гайде по локальным LLM.

Какую модель выбрать (ноябрь 2026)

Цены и характеристики ниже сняты 8 ноября 2026 года с официальных страниц вендоров.

Облачные модели

МодельРазмерностьКонтекст, токеновЦена за 1M токеновЯзыки по документации
OpenAI text-embedding-3-small1536, меньше через dimensions8192$0,02на странице не указаны
OpenAI text-embedding-3-large3072, меньше через dimensions8192$0,13на странице не указаны
Voyage voyage-4-large1024 (256, 512, 2048)32 000$0,12многоязычная
Voyage voyage-41024 (256, 512, 2048)32 000$0,06многоязычная
Voyage voyage-4-lite1024 (256, 512, 2048)32 000$0,02не заявлено
Google gemini-embedding-23072 (от 128 до 3072)8192$0,20 текст, $0,10 в Batch100+
Cohere embed-v5.0-pro2048 (256, 512, 768, 1024, 1536)128 000на официальной странице не опубликована100+

OpenAI. В документации по-прежнему text-embedding-3-small и text-embedding-3-large, моделей новее v3 там нет. Размерность уменьшается параметром dimensions.

Voyage. Своей эмбеддинг-модели у Anthropic нет. В документации Claude рекомендуют Voyage AI (принадлежит MongoDB) с оговоркой, что стоит сравнить нескольких вендоров. Ключ теперь выдаёт MongoDB Atlas, эндпоинт https://ai.mongodb.com/v1/embeddings. Первые 200 млн токенов бесплатны по странице биллинга. Для поиска обязателен параметр input_type: к тексту добавляется служебный префикс для запроса или документа.

import numpy as np, voyageai

vo = voyageai.Client()  # ключ берётся из переменной окружения VOYAGE_API_KEY
docs = ["The cat is sleeping on the sofa", "Quarterly revenue grew by 12 percent"]
d = vo.embed(docs, model="voyage-4", input_type="document").embeddings
q = vo.embed(["where is the cat?"], model="voyage-4", input_type="query").embeddings[0]
print(np.dot(d, q))  # векторы нормализованы: dot == cosine

Для ключей Atlas нужна библиотека voyageai версии 0.3.7 или новее (pip install -U voyageai). Ключ держат в окружении, а не в коде.

Google. gemini-embedding-2 мультимодальная: текст, изображения, видео, аудио и PDF попадают в одно пространство. Параметра task_type у неё нет, задача пишется прямо в тексте: task: search result | query: ... для запроса, title: ... | text: ... для документа. Предыдущая gemini-embedding-001 работает только с текстом, принимает до 2048 токенов и стоит $0,15 за 1M токенов. Пространства двух моделей несовместимы: при переходе на gemini-embedding-2 всю базу векторизуют заново.

Cohere. Embed 5 есть в двух вариантах, embed-v5.0-pro и embed-v5.0-fast, с одинаковыми размерностями и контекстом 128K. Понимает текст, изображения и смешанный ввод вроде страниц PDF. На официальных страницах Cohere цены Embed 5 на 8 ноября 2026 года не нашлось, сторонние агрегаторы приводят свои цифры, их стоит сверить с прайсом перед расчётом бюджета.

Открытые модели для своего сервера

МодельРазмер в OllamaРазмерностьКонтекстЛицензияПрефиксыЯзыки
nomic-embed-text v1.5274 MB768 (Matryoshka до 64)8192 в модели, 2048 в OllamaApache 2.0обязательныанглийский
bge-m31,2 GB10248192MITне нужны100+
multilingual-e5-largeне проверялся1024512MITquery: / passage: обязательныоколо 100
Qwen3-Embedding 0.6B / 4B / 8B639 MB / 2,5 GB / 4,7 GBдо 4096, любая от 3232KApache 2.0инструкции поддерживаются100+ и код
EmbeddingGemma622 MB768 (512, 256, 128)2048Gemmaрекомендуются, task: / title:100+

BGE-M3 кроме обычного плотного вектора умеет разреженный и multi-vector режим. multilingual-e5-large обрезает текст до 512 токенов, а без префиксов query: и passage: по карточке модели работает хуже. Карточка Qwen3-Embedding-8B заявляет первое место в мультиязычном рейтинге MTEB со средним 70,58 на 5 июня 2025 года: это заявление авторов на дату публикации, а не текущее положение. EmbeddingGemma на 300M параметров используется в официальных примерах Ollama, запросы к ней оформляют так же, как к gemini-embedding-2.

Российские сервисы

Yandex AI Studio. Модели идут парами, для документов и для запросов: text-search-doc и text-search-query с вектором на 256 измерений и вторая версия text-embeddings-v2-doc / text-embeddings-v2-query с размерностью 128, 256, 512 или 768 по документации. Документы векторизуют doc-моделью, запросы query-моделью. По прайсу на 8 ноября 2026 года векторизация стоит 0,0101 ₽ за 1000 токенов с НДС.

GigaChat. Модель Embeddings с окном 512 токенов и вектором 1024 и EmbeddingsGigaR с окном 4096 токенов и вектором 2560. На странице EmbeddingsGigaR на 8 ноября 2026 года все эмбеддинг-модели GigaChat (Embeddings, Embeddings-2, EmbeddingsGigaR, GigaEmbeddings-3B-2025-09) стоят 14 ₽ за 1 млн токенов.

Как читать MTEB и ruMTEB

MTEB - общий лидерборд эмбеддинг-моделей по множеству задач: поиск, кластеризация, классификация, сравнение пар. Места в нём меняются часто, поэтому смотреть стоит не общий рейтинг, а колонку той задачи, которая нужна вам, обычно retrieval. Для русского языка есть ruMTEB (Snegirev et al., NAACL 2025), его задачи входят в тот же лидерборд.

Цифры из лидерборда служат для короткого списка, а не для финального выбора. Финальный выбор делают на своих данных: 50-100 реальных запросов с заранее известными правильными документами и доля случаев, когда нужный документ попал в top-5.

Matryoshka: как укоротить вектор и не потерять качество

Matryoshka Representation Learning (Kusupati et al., 2022) обучает модель так, что самая важная информация собирается в начале вектора. Первые 256 чисел из 768 уже работают как самостоятельный вектор, только чуть грубее, как меньшая матрёшка внутри большой.

Насколько грубее, видно по карточке nomic-embed-text-v1.5, средний балл MTEB при разной длине вектора:

Размерность76851225612864
MTEB62,2861,9661,0459,3456,10

Вектор в три раза короче теряет чуть больше балла, в двенадцать раз короче уже около шести.

Проще всего попросить нужную длину у API: dimensions у OpenAI и в Ollama /api/embed, output_dimension у Voyage, output_dimensionality у Gemini. Если векторы уже лежат в базе, их обрезают сами и нормализуют заново:

import numpy as np

def shorten(v, k):
    v = np.array(v)[:k]
    return v / np.linalg.norm(v)

Без второй строки обрезанный вектор теряет длину 1, и скалярное произведение перестаёт совпадать с косинусом. gemini-embedding-2 нормализует укороченные векторы сама, gemini-embedding-001 нет: для неё нормализация вручную обязательна.

Обрезать имеет смысл только модели, обученные по этой схеме: OpenAI v3, Voyage 4, Gemini, Cohere Embed 5, Qwen3-Embedding, EmbeddingGemma, nomic-embed-text v1.5. У bge-m3 и multilingual-e5 размерность фиксированная: обрезка их векторов в карточках моделей не предусмотрена, и качество после неё никто не гарантирует.

Типичные ошибки

Разные модели в индексе и в запросе. Векторы разных моделей лежат в разных пространствах, сравнивать их бессмысленно. Это касается и версий одного вендора: gemini-embedding-001 и gemini-embedding-2 несовместимы. Смена модели означает переиндексацию всей базы. Ollama в документации отдельно советует одну модель для индексации и поиска.

Забытые префиксы. nomic-embed-text ждёт search_query: для запроса и search_document: для документа, multilingual-e5 ждёт query: и passage: , EmbeddingGemma и gemini-embedding-2 ждут формат task: ... | query: .... Без префикса модель отдаёт вектор, ошибки нет, но поиск работает хуже. В облачных API ту же роль играет параметр: input_type у Voyage.

Слишком длинный текст. У multilingual-e5 лимит 512 токенов, у EmbeddingGemma 2048, у nomic-embed-text в Ollama 2048. Ollama по умолчанию обрезает вход молча (truncate: true), и хвост документа просто не попадает в вектор. С truncate: false вместо обрезки придёт ошибка. Длинные документы режут на фрагменты до векторизации.

Английская модель на русском тексте. Замер выше показывает, как это выглядит: nomic-embed-text дала переводу и чужой фразе одинаковый косинус, около 0,58. Модель не падает и не предупреждает, она просто перестаёт различать смысл.

Сравнение косинуса между моделями. Порог «похоже, если больше 0,8» привязан к конкретной модели. В замере выше у bge-m3 такой порог отделил бы все переводы от чужих фраз, а у nomic-embed-text не прошёл бы ни один перевод: её максимум 0,618. При смене модели порог подбирают заново на своих данных.

Когда эмбеддинги не нужны

Векторный поиск плохо находит точные строки: артикулы, коды ошибок, номера договоров, имена функций. Вектор передаёт смысл, и векторы «ERR_4012» и «ERR_4013», скорее всего, окажутся рядом. Для таких запросов работает обычный полнотекстовый поиск вроде BM25, а в продакшене оба поиска часто объединяют в гибридный.

Если весь корпус умещается в контекст модели, а запросов немного, документы можно передать в промпт целиком без векторного поиска. Когда хватает длинного контекста, а когда без RAG не обойтись, разобрано в статье Context Engineering vs RAG.

Векторная база тоже нужна не всегда. Тысяча векторов по 1024 числа занимает около 4 МБ, и поиск по ним сводится к одному умножению матрицы на вектор в numpy. База нужна, когда векторов миллионы, нужны фильтры по метаданным и обновления без пересборки. Если проект уже на Postgres, удобный вариант - расширение pgvector, оно есть в Supabase.

С чего начать

  1. Поставить Ollama и скачать многоязычную модель: ollama pull bge-m3 или ollama pull embeddinggemma.
  2. Прогнать пример из этой статьи на своих текстах и посмотреть, как разводятся похожие и непохожие пары.
  3. Собрать 50-100 реальных запросов с известными правильными документами и сравнить на них две-три модели из коротких списков выше.
  4. Выбрать размерность: начать с размерности по умолчанию и уменьшать через Matryoshka, если упираетесь в объём базы.
  5. Зафиксировать модель, версию и префиксы в коде индексации и поиска в одном месте, чтобы они не разошлись.

Как собрать из эмбеддингов полноценный поиск по документам с разбиением на фрагменты, гибридным поиском и reranking, описано в гайде по RAG-системам.

Часто задаваемые вопросы

Что такое эмбеддинг простыми словами?
Эмбеддинг - это набор чисел, которым модель описывает смысл текста, картинки или другого объекта. Тексты с похожим смыслом получают похожие наборы чисел, даже если в них нет общих слов. На этом построены семантический поиск, RAG, кластеризация и рекомендации.
Чем эмбеддинг отличается от токена?
Токен - это кусок текста (слово или часть слова), на которые модель режет вход. Эмбеддинг - это вектор чисел. Языковая модель сначала превращает каждый токен в свой вектор, а эмбеддинг-модель в итоге выдаёт один вектор на весь текст: фразу, абзац или документ.
Что такое векторные представления и как они связаны с эмбеддингами?
Это одно и то же. «Векторное представление» - русский перевод термина embedding. Встречается также «векторизация текста»: так называют сам процесс получения эмбеддинга, например в документации Yandex AI Studio.
Какую размерность выбрать: 256, 768 или 1536?
Размерность задаёт модель, а у моделей с Matryoshka её можно уменьшить параметром API. Меньший вектор дешевле хранить и быстрее сравнивать, но немного теряет в качестве: у nomic-embed-text-v1.5 по карточке модели MTEB падает с 62,28 при 768 измерениях до 61,04 при 256. Начинать стоит с размерности по умолчанию и уменьшать, если упираетесь в память или объём базы.
Чем косинусная близость отличается от скалярного произведения и евклидова расстояния?
Косинус меряет угол между векторами и не зависит от их длины. Скалярное произведение учитывает и угол, и длину. Евклидово расстояние меряет прямую дистанцию между концами векторов. Если векторы нормализованы до длины 1, как у OpenAI, Voyage и Ollama, косинус равен скалярному произведению, а все три меры дают одинаковый порядок результатов.
Можно ли использовать эмбеддинги на русском языке, и какую модель взять?
Можно, но модель должна быть многоязычной. Из открытых это BGE-M3, Qwen3-Embedding, EmbeddingGemma и multilingual-e5-large, из облачных Voyage 4, Gemini Embedding 2 и Cohere Embed 5. Английская nomic-embed-text на русском тексте почти не отличает перевод от случайной фразы. Для сравнения моделей на русском есть бенчмарк ruMTEB.
Нужна ли векторная база, чтобы работать с эмбеддингами?
Для сотен и тысяч документов нет: векторы помещаются в память, и поиск сводится к умножению матрицы на вектор в numpy. Векторная база или расширение pgvector нужны, когда векторов миллионы, нужен индекс для приближённого поиска, фильтры по метаданным и обновления без пересборки.