Автовекторизация (autoembedding)

Эмбеддер — модель, преобразующая данные в векторные представления.

Автовекторизация — это специальная функция Reindexer для преобразования данных из заданных полей в векторные представления. Представляет собой конвейер, автоматизирующий весь процесс от источника данных до индексирования и выборки. Автовекторизация позволяет с помощью подключенной пользовательской модели векторизации добавлять или обновлять данные из сырого представления в используемые векторные индексы, а также использовать входные данные пользователя для фильтрации данных в векторных индексах.

Сырыми данными в общем случае могут служить любые данные: текст, числа, аудиоданные, видеоданные, изображения. В Reindexer в качестве сырых данных для векторизации могут служить любые индексируемые данные. Автовекторизация допустима для объединения любых скалярных индексов и индексов-массивов, и не работает для композитных и sparse-индексов. А результат (эмбеддинги) может быть сохранён в любом подходящем типе векторных индексов.

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

Сервис векторизации (эмбеддер) должен выполнять роль прокси между Reindexer’ом и моделью, преобразующей исходные данные в эмбеддинги. Сервис может меняться независимо от Reindexer простой перенастройкой подключения. Сервисов может быть несколько, но не более двух на каждый векторный индекс в базе данных.

Требования к сервису векторизации

Сервис векторизации - это отдельный сервис с HTTP-интерфейсом, который должен быть реализован пользователем для возможности работы автовекторизации.

HTTPS в данный момент не поддерживается

Сервис должен предоставлять интерфейс, соответствующий описанной ниже спецификации OpenAPI. На текущий момент необходимым является только один обработчик для POST /api/v1/embedder/{name}/produce. Reindexer будет отправлять данные для векторизации в этот эндпоинт и ожидать эмбеддинг в ответе.

Текущий API сервиса векторизации находится в бета-версии и может измениться в будущих релизах

Для различных индексов возможно подключать собственные модели (сервисы векторизации) для получения различных векторных представлений (для семантического поиска, RAG, рекомендаций и т.д). Возможно использование одного сервиса для всех индексов, но следует учитывать нагрузку на базу и сервис.

Конфигурация автовекторизации

Настройка автовекторизации производится для каждого векторного индекса отдельно. Для этого в определение целевого индекса в поле config требуется добавить поле embedding. Поле embedding, в свою очередь, состоит из двух независимых полей: upsert_embedder и query_embedder. Где:

  • upsert_embedder — это описание векторизатора (сервиса), который будет использоваться для векторизации документов при их вставке и модификации. Также может быть использован для задании значений векторов в уже имеющихся документах;
  • query_embedder — это описание векторизатора (сервиса), который будет использоваться для векторизации пользовательских текстовых запросов для KNN-поиска в векторном индексе.

В общем случае оба векторизатора абсолютно независимы, настраиваются отдельно, могут существовать вместе или поодиночке, исходя из решаемой задачи. В то же время они могут описывать один и тот же сервис и использовать одну и ту же модель. Если ни один векторизатор на задан, автовекторизация для целевого индекса использоваться не будет.

Нужно понимать, что операции, выполняемые upsert_embedder, носят обязательный характер. Если сервис векторизации не ответил или вернул ошибку - операция отменяется. В случае сбоя сервиса с query_embedder для гибридных выборок значение меняется на пустое, когда от пользователя приходит несколько полнотекстовых и векторных запросов. Запрос в этом случае будет эквивалентен запросу без фильтрации по KNN-запросу со строкой.

Ниже представлена часть конфигурации для одного векторного индекса. Для примера, показан вариант конфигурации двух векторизаторов (значения в <> текстовые и должны быть заданы пользователем):

{
  "indexes": [
    {
      "name": "vec",
      "config": {
        "dimension": 1024,
        "metric": "inner_product",
        "embedding": {
          "upsert_embedder": {
            "name": "my-embedder",
            "URL": "http://127.0.0.1:8000",
            "cache_tag": "hnsw",
            "fields": ["name", "value"],
            "embedding_strategy": "always",
            "pool": {
              "connections": 3,
              "connect_timeout_ms": 500,
              "read_timeout_ms": 500,
              "write_timeout_ms": 500
            }
          }
        }
      }
    }
  ]
}

Параметры конфигурации подключения автовекторизатора

Имя параметра Описание Обязательный
name Имя сервиса векторизации. Если не указано, используется логика генерации по умолчанию, в нижнем регистре: <NS_NAME>_<INDEX_NAME>, для идентификации в логе Нет
URL URL сервиса векторизации. Адрес сервиса, на который будут отправляться запросы на создание эмбеддингов Да
cache_tag Имя (или идентификатор), используемое для доступа к кешу. Если этот параметр не указан или там пустая строка, то кеширование не используется. Имя может быть неуникальным, в этом случае разные векторизаторы могут помещать результат в один и тот же кеш. Однако необходимо учесть, что это работает корректно, если исходные данные для векторизаторов не пересекаются, или если векторизаторы возвращают абсолютно одинаковые значения для одного и того же набора входных данных. Нет
fields Список индексных полей в текущем пространстве имен базы данных, для которых необходимо вычислять эмбеддинг (не имеет смысла для query_embedder) Да
embedding_strategy Стратегия внедрения эмбеддингов в индекс (не имеет смысла для query_embedder) Нет
pool Конфигурация пула подключений к сервису векторизации Нет

Список возможных значений параметра embedding_strategy:

Имя параметра Описание
always Векторизация выполняется всегда. Если пользователь указал значение для поля (непустой вектор), оно будет перезаписано. Значение по умолчанию
empty_only Если пользователь указал значение для поля (непустой вектор), значение поля перезаписано не будет. Если поле пустое, то эмбеддинг выполняется автоматически
strict Если пользователь указал значение для поля (непустой вектор), операция отклоняется, возвращается ошибка. Если поле пустое, то эмбеддинг выполняется автоматически

При необходимости можно настроить пул подключений (pool). Иначе будут использоваться настройки по умолчанию:

Имя параметра Описание Обязательный Допустимые значения Значение по умолчанию
connections Количество подключений к сервису (единиц) Нет [1..1024] 10
connect_timeout_ms Тайм-аут подключения/переподключения к сервису векторизации (в миллисекундах) Нет Не менее 100 300
read_timeout_ms Тайм-аут получения данных от сервиса векторизации (в миллисекундах) Нет Не менее 500 5000
write_timeout_ms Тайм-аут отправки данных в сервис векторизации (в миллисекундах) Нет Не менее 500 5000

upsert_embedder и query_embedder

upsert_embedder — преобразует документы/фрагменты текста, которые необходимо сохранить в векторной базе данных (индексировать), в эмбеддинги. Используется в операциях: insert, update, upsert. А так же в команде create_embeddings. Извлечённые из документа данные отправляются в сервис векторизации в формате JSON (значение параметра format также будет json).

query_embedder — преобразует поисковый запрос пользователя в эмбеддинг, чтобы затем использовать этот эмбеддинг при KNN-поиске. Используется для запросов, содержащих KNN-условия со строкой (например, WhereKnnString в Go). При этом в сервис векторизации отправляется текст пользовательского запроса, а в параметре format — значение text.

Пример представления данных в запросе к сервису векторизации:

  • В момент вставки/обновления (для upsert_embedder). Передаются все указанные в конфигурации поля из векторизуемого документа (или нескольких):

    {"data":[{"field0":val0, "field1":[val10, val11], ...}, ..., {"field0":val0, "field1":[], ...}]}
    
  • В момент запроса на выборку, например, при использовании WhereKNNString (для query_embedder). Пользовательские запросы передаются в виде строк (несколько строк может быть передано только в случае, если в ядре будет собран батч из нескольких запросов):

    {"data":["WhereKNN input search text", ...]}
    

В ответ, в соответствии со спецификацией, ожидается:

[
  "_comment": "One array corresponds to one object/string that came to produce",
  [
    "_comment": "One such object corresponds to one chunk. At this stage, there should always be one chunk, and the data from the 'chunk' itself will be ignored - only the vector from the embedding field will be used",
    {
      "chunk": "some data",
      "embedding": [ 1.1, 0.7, ...]
    },
    {
      "chunk": "more data",
      "embedding": [ 0.1, -1.0, ...]
    },
    ...
  ],
  ...
]

Пример конфигурирования автоэмбедирования

connectConfig := &bindings.EmbedderConnectionPoolConfig{
	Connections:    3,
	ConnectTimeout: 500,
	ReadTimeout:    500,
	WriteTimeout:   500,
}
embedderConfig := &bindings.EmbedderConfig{
	Name:                 "test-embedder",
	URL:                  "http://127.0.0.1:8000",
	Fields:               []string{"name", "value"},
	CacheTag:             "HNSW",
	EmbeddingStrategy:    "always",
	ConnectionPoolConfig: connectConfig,
}
embeddingConfig := &bindings.EmbeddingConfig{
	UpsertEmbedder: embedderConfig,
}
hnswSTOpts := reindexer.FloatVectorIndexOpts{
	Metric:             "inner_product",
	Dimension:          1024,
	M:                  8,
	EfConstruction:     100,
	StartSize:          256,
	MultithreadingMode: 1,
	EmbeddingConfig:    embeddingConfig,
}
indexDef := reindexer.IndexDef{
	Name:      "vec",
	JSONPaths: []string{"vec"},
	IndexType: "hnsw",
	FieldType: "float_vector",
	Config:    hnswSTOpts,
}
err := DB.AddIndex("embedding_hnws", indexDef)
if err != nil {
	panic(err)
}
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/vectors_db/namespaces/test_ns/indexes' \
--header 'Content-Type: application/json' \
--data-raw '{
  "name": "vec",
  "json_paths": ["vec"],
  "field_type": "float_vector",
  "index_type": "hnsw",
  "config": {
    "dimension": 1024,
    "metric": "inner_product",
    "m": 8,
    "ef_construction": 100,
    "strat_size": 256,
    "multithreading": 1,
    "embedding": {
      "upsert_embedder": {
        "name": "test-embedder",
        "URL": "http://127.0.0.1:8000",
        "cache_tag": "HNSW",
        "fields": ["name", "value"],
        "embedding_strategy": "always",
        "pool": {
          "connections": 3,
          "connect_timeout_ms": 500,
          "read_timeout_ms": 500,
          "write_timeout_ms": 500
        }
      }
    }
  }
}'

Конфигурация кеша автовекторизатора

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

Кеш эмбеддиногов в Reindexer работает по принципу LRU и имеет гибридную структуру: ключи (векторизуемые значения) всегда хранятся in-memory, а значения соответствующих им векторов — на диске. Таких кешей может быть несколько. Все они существуют на уровне конкретной БД, и один кеш может использоваться одновременно в нескольких индексах и/или нескольких неймспейсах. Доступ к кешу осуществляется по идентификатору (cache_tag). По умолчанию кеширование отключено. Для того чтобы кеширование заработало, необходимо произвести соответствующую настройку.

Настройка кеша для автовекторизации производится в два шага:

  1. Необходимо задать параметр cache_tag, который был описан выше и является частью поля *_embedder из config в описании целевого векторного индекса;
  2. Включить кеширование для векторизаторов на уровне базы данных. Для этого существует специальная запись в системном неймспейсе #config с типом embedders. Эта запись также необязательна: если её нет, то кеширование не используется для всех автовекторизаторов, несмотря на установленный cache_tag.

Параметры для настройки двух кешей:

{
  "type":"embedders",
  "caches":[
    {
      "cache_tag":"*",
      "max_cache_items":1000000,
      "hit_to_cache":1
    },
    {
      "cache_tag":"the jungle book",
      "max_cache_items":2025,
      "hit_to_cache":3
    }
  ]
}
Имя параметра Описание Обязательный Допустимые значения Значение по умолчанию
cache_tag Имя идентификатора, используемое для доступа к кешу. Можно использовать специальный символ *, в этом случае настройки применяются ко всем настроенным векторизаторам, у которых задан непустой cache_tag при условии, что нет более конкретной специализации. Специализация определяется соответствием значений cache_tag в этой конфигурации и конфигурации векторизатора (например, the jungle book) Да Любой текст -
max_cache_items Максимальное количество элементов (уникальных ключей) в кеше эмбеддингов. Этот кеш будет активирован, только если свойство max_cache_items не установлено в значение 0. Он хранит результаты вычислений эмбеддинга Нет [0..INT_MAX] 1000000
hit_to_cache Это значение определяет, сколько запросов требуется для помещения результатов в кеш. Например, при значении 2 первый запрос будет выполнен без кеширования, второй запрос сгенерирует запись кеша и поместит результаты в кеш, а третий запрос уже получит результат из кеша. Значение 0 и 1 означают, что добавленное значение сразу помещается в кеш Нет [0..INT_MAX] 1
nsConfig := make([]reindexer.DBEmbeddersConfig, 2)
nsConfig[0].CacheTag = "*"
nsConfig[0].MaxCacheItems = 1000000
nsConfig[0].HitToCache = 1
nsConfig[1].CacheTag = "the jungle book"
nsConfig[1].MaxCacheItems = 2025
nsConfig[1].HitToCache = 3
item := reindexer.DBConfigItem{
	Type:      "caches",
	Embedders: &nsConfig,
}
err := DB.Upsert(reindexer.ConfigNamespaceName, item)
if err != nil {
	panic(err)
}
UPDATE #config
SET caches = [{"cache_tag":"*","max_cache_items":1000000,"hit_to_cache":1}, {"cache_tag":"the jungle book","max_cache_items":2025,"hit_to_cache":3}]
WHERE type = 'embedders'
curl -X 'PATCH' \
  'http://127.0.0.1:9088/api/v1/db/vectors_db/namespaces/%23config/items' \
  -H 'accept: application/json' \
  -H 'Content-Type: */*' \
  -d '
  {
    "type": "embedders",
    "caches": [
      {
        "cache_tag": "*",
        "max_cache_items": 1000000,
        "hit_to_cache": 1
      },
      {
        "cache_tag": "the jungle book",
        "max_cache_items": 2025,
        "hit_to_cache": 3
      }
    ]
  }'

Создание и обновление эмбеддингов для существующих документов

Для создания и обновления эмбеддингов существующих в неймспейсе документов предусмотрена отдельная action-команда: create_embeddings. Она применяется в случаях, когда необходимо добавить встроенные значения в существующие документы после настройки автоматического векторизатора, или для обновления существующих значений при смене векторизатора. Обновление происходит для всех векторизаторов в указанном пространстве имен.

Эта команда не выполняет блокировку целевого неймпейса. Вместо этого, она делает полную выборку и последовательный обход/векторизацию всех документов в соответствии с заданной стратегией векторизации. Это означает, что если в процессе выполнения команды какой-то из существующих документов будет обновлён, она может вновь восстановить его прежнее значение.

На текущий момент это действие не проксируется при использовании RAFT-кластера или шардированного кластера и выполняется только на том узле, на котором было инициировано. В случае с RAFT-кластером его требуется выполнять на узле-лидере.

Формат команды в JSON:

{
  "type": "action",
  "action": {
    "command": "create_embeddings",
    "namespace": "*",
    "batch_size": 100
  }
}
DB.Upsert(reindexer.ConfigNamespaceName, []byte("{\"type\":\"action\",\"action\":{\"command\":\"create_embeddings\", \"namespace\":\"*\", \"batch_size\":100}}"))
UPDATE #config
SET action = {"command": "create_embeddings", "namespace": "*", "batch_size": 100}
WHERE type = 'action'
reindexer_tool \
--dsn cproto://127.0.0.1:6534/vectors_db \
-c '\upsert #config {"type":"action","action":{"command":"create_embeddings", "namespace":"*", "batch_size":100}}'

Особое внимание необходимо уделить настройкам стратегии обновления эмбеддингов. Стратегия strict не рекомендуется на данном этапе.

  • Если документы содержат значения эмбеддингов, но есть также документы без заполненных векторов, то рекомендуется использовать стратегию empty_only.
  • Если необходимо заменить все значения в поле, для которого настроено автоматическая векторизация, то следует использовать стратегию always.

Стратегия задается в настройках векторизатора для индекса и может быть задана индивидуально в конфигурации: ("config": {"embedding": { "upsert_embedder": { "embedding_strategy": ...).

Параметры

Имя параметра Описание Обязательнный
namespace Целевой неймспейс (* – применяет команду ко всем неймспейсам в БД) Да
batch_size Число, определяющее, сколько документов будут обновляться в неймспейсе за один раз (размер пачки в транзакции) Нет

Параметром batch_size можно регулировать нагрузку на Reindexer и векторизирующий сервис

Команда удаления кеша для эмбеддингов

Эта команда применяется, когда необходимо сбросить содержимое кешей для всех или конкретных векторизаторов.

Формат команды в JSON:

{"type":"action","action":{"command":"clear_embedders_cache", "cache_tag":"*"}}
DB.Upsert("#config", []byte("{\"type\":\"action\",\"action\":{\"command\":\"clear_embedders_cache\", \"cache_tag\":\"*\"}}"))
UPDATE #config
SET action = {"command": "clear_embedders_cache", "cache_tag": "*"}
WHERE type = 'action'
reindexer_tool \
--dsn cproto://127.0.0.1:6534/vectors_db \
-c '\upsert #config {"type":"action","action":{"command":"clear_embedders_cache", "cache_tag":"*"}}'
Имя параметра Описание Обязательный
cache_tag Идентификатор целевого кеша (* — применить команду ко всем кешам) Да

Примеры KNN-запросов с автовекторизацией

Поиск KNN с автоматическим созданием эмбеддингов работает аналогично обычному поиску KNN, но ожидает строку вместо вектора. Эта строка передается в сервис векторизации, который возвращает соответствующий вектор значений (эмбеддинг). Полученный вектор используется при выполнении поиска в базе данных как фактическое значение для фильтрации. Перед использованием необходимо настроить поле query_embedder в конфигурации целевого индекса. Если по каким-то причинам векторизирующий сервис не отвечает или недоступен, для гибридных запросов (полнотекстовый + KNN) фильтр по данному условию будет проигнорирован, а запрос будет выполнен как полнотекстовый. В логе при этом появится соответствующая запись: Failed to get embedding for '<INDEX_NAME>'. Problem with client: ....

Все запросы обрабатываются по принципу KNN-поиска. Допускает комбинировать данный тип запросов как и любые другие запросы KNN.

Поиск KNN-векторов:

knnBaseSearchParams := reindexer.BaseKnnSearchParam{}.SetK(4291)
// brute force
it := db.Query("test_ns").WhereKnnString("vec_bf", "<text to embed calculate>", knnBaseSearchParams).Exec()
defer it.Close()

// hnsw
hnswSearchParams, err := reindexer.NewIndexHnswSearchParam(100000, knnBaseSearchParams)
if err != nil {
    panic(err)
}
it := db.Query("test_ns").WhereKnnString("vec_hnsw", "<text to embed calculate>", hnswSearchParams).Exec()
defer it.Close()

// ivf
ivfSearchParams, err := reindexer.NewIndexIvfSearchParam(10, knnBaseSearchParams)
if err != nil {
    panic(err)
}
it := db.Query("test_ns").WhereKnnString("vec_ivf", "<text to embed calculate>", ivfSearchParams).Exec()
defer it.Close()
SELECT * FROM test_ns WHERE KNN(vec_bf, '<text to embed calculate>', k=100)
SELECT * FROM test_ns WHERE KNN(vec_hnsw, '<text to embed calculate>', k=100, ef=200)
SELECT * FROM test_ns WHERE KNN(vec_ivf, '<text to embed calculate>', k=100, nprobe=10)
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/vectors_db/query' \
--header 'Content-Type: application/json' \
--data-raw '{
  "namespace": "test_ns",
  "type": "select",
  "filters": [
    {
      "op": "and",
      "cond": "knn",
      "field": "vec_bf",
      "value": "<text to embed calculate>",
      "params": {"k": 100}
    }
  ],
}'