Skip to content

Поисковые движки и векторные хранилища (Retrieval Engines) ​

Поисковые движки отвечают за хранение индексов и выполнение векторного, полнотекстового или гибридного поиска. По умолчанию PostgreSQL с использованием образа ParadeDB предоставляет pgvector и BM25, которые могут совместно использоваться с данными приложения. При необходимости независимого масштабирования или повторного использования существующей инфраструктуры можно выбрать другие бэкенды.

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

СценарийРассмотреть
Одиночное или настольное развертывание с использованием встроенной базы данныхSQLite (встроенная, без зависимостей)
Необходимость независимого масштабирования сервиса векторного поискаQdrant, Milvus
Использование существующей инфраструктуры Elasticsearch / OpenSearchПовторное использование существующего кластера
Необходимость раздельного хранения данных по базам знанийСохранить движок по умолчанию, зарегистрировать экземпляр в «Настройки → Векторные хранилища» и привязать к нужной базе знаний

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

Мультимодальные эмбеддинги и поиск изображений ​

Векторные модели с поддержкой изображений на входе кодируют текст и изображения в одно пространство. В базе знаний векторы изображений по умолчанию отключены: необходимо одновременно настроить мультимодальный режим и «векторный поиск по изображениям»; уже загруженные документы нужно распарсить заново, а страницы сканированных PDF не создают векторов изображений. Галерея показывает только уже существующие изображения и от этого переключателя не зависит. Подробнее см. настройку изображений базы знаний.

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

Сводная таблица возможностей и сравнение выбора ​

ДвижокЗначение RETRIEVE_DRIVERВекторный поискПоиск по ключевым словам / полнотекстовыйОценка ключевых словРазделение китайского текстаУправление размерностьюПередача порога фильтрацииСложность развертыванияСценарий использования
PostgreSQLpostgrespgvector halfvec + HNSW индекс на основе выраженийParadeDB BM25 (|||)BM25 (paradedb.score)Токенизатор ParadeDBЕдиная таблица со смешанными размерностями, индекс на основе выражений по указанным размерностямПорог расстояния в SQLНизкая (встроено в образ по умолчанию)Выбор по умолчанию; совместно с основной БД, транзакционная согласованность
SQLitesqlitesqlite-vec vec0 (cosine)FTS5 contentlessFTS5Bigram (для биграмм)Отдельная таблица vec0 для каждой размерностиПорог в приложенииОчень низкая (встроено)Настольные ПК / разработка / микроразвертывания
Elasticsearch v8elasticsearch_v8script_score cosineSimilaritymatch (BM25)BM25Анализатор ESdense_vector в одном индексеПорог в приложенииСредняяПри наличии кластера ES 8
Elasticsearch v7elasticsearch_v7Не поддерживается (Support только для keywords)match (BM25)BM25Анализатор ES——СредняяСуществующие кластеры ES 7, только как движок ключевых слов, требует комбинации с другим векторным движком
OpenSearchopensearchk-NN плагин knn (HNSW)match (BM25)BM25Анализатор OSЯвное определение mapping knn_vector + проверка подлинностиНативный k-NNСредняяСреды ES, требующие аудита / алиасов / reindex; версии 2.11+ / 3.x
QdrantqdrantНативный HNSW CosineПолнотекстовый MatchText (токен ИЛИ)НетТокенизатор для нескольких языковОтдельный collection для каждой размерностиНативный score_thresholdСредняяСценарии с преобладанием векторного поиска, требующие фильтрации по payload
MilvusmilvusHNSW (IP/COSINE/L2)Функция BM25 для разреженных векторовBM25Анализатор MilvusОтдельный collection для каждой размерностиПараметры приложенияСредняя/высокаяМасштабные векторные данные, требующие нативного гибридного поиска
WeaviateweaviatenearVector (certainty)Нативный BM25BM25Анализатор WeaviateДинамический ClassНативный certaintyСредняяЭкосистема GraphQL, требующая настройки репликации / шардинга
DorisdorisANN HNSW inner_product/cosineИнвертированный индекс MATCH_ANYИнвертированный поискКитайский парсер при объявлении таблицыОтдельная таблица для каждой размерностиВстроенный в SQLВысокаяПри наличии хранилища данных Doris, интеграция поиска и аналитики
VectorDBtencent_vectordbHNSW COSINEРазреженный вектор BM25 (SPARSE_INVERTED)BM25SDK SparseEncoderОтдельный collection для каждой размерностиПараметры приложенияНизкая (облачный сервис)Облачное решение, не требующее обслуживания

Примечание: Независимо от наличия встроенной поддержки гибридного поиска у движка, aihelpme.ru всегда выполняет гибридный поиск на верхнем уровне путем RRF-объединения ( knowledgebase_search_fusion.go ) — векторы и ключевые слова ищутся независимо, а затем объединяются с учетом веса (см. Оценка гибридного поиска и нормализация). Поэтому каждый движок должен предоставлять только один тип поиска.

Сводка методов конфигурации ​

Основные переключатели (см. раздел C1 в .env.example, docker-compose.yml):

Переменная окруженияПо умолчаниюОписание
RETRIEVE_DRIVERpostgresРазделенный запятыми список: postgres / sqlite / elasticsearch_v7 / elasticsearch_v8 / opensearch / qdrant / milvus / weaviate / doris / tencent_vectordb. При множественных драйверах операции записи транслируются на все, поиск направляется по типу.
MULTI_STORE_RETRIEVE_TIMEOUT_SEC30Тайм-аут для параллельного поиска в нескольких хранилищах.
ELASTICSEARCH_ADDR / _USERNAME / _PASSWORD / _INDEX— / WeKnoraОбщие для ES v7/v8.
OPENSEARCH_ADDR / _USERNAME / _PASSWORD / _INDEX / _INSECURE_SKIP_VERIFY—OpenSearch.
QDRANT_HOST / _PORT / _COLLECTION / _API_KEY / _USE_TLSlocalhost / 6334 / weknora_embeddingsQdrant (gRPC порт).
MILVUS_ADDRESS / _COLLECTION / _METRIC_TYPE / _USERNAME / _PASSWORD / _DB_NAMElocalhost:19530 / weknora_embeddings / IPmetric должен быть изменен до создания collection.
WEAVIATE_HOST / _GRPC_ADDRESS / _SCHEME / _AUTH_ENABLED / _API_KEY / _COLLECTIONweaviate:8080 / weaviate:50051 / httpИмена сервисов для контейнерной среды.
DORIS_ADDR / _HTTP_PORT / _DATABASE / _USERNAME / _PASSWORD / _TABLE_PREFIX / _COMPAT_MODEdoris-fe:9030 / 8030 / weknora / root / — / weknora_embeddings / autoDoris 4.1+; режим compat требует создания таблицы, после чего не может быть изменен.
TENCENT_VECTORDB_ADDR / _USERNAME / _API_KEY / _DATABASE / _COLLECTION—Пропуск регистрации при отсутствии хотя бы одного из трех ключевых параметров.
NEO4J_ENABLE / NEO4J_URI / _USERNAME / _PASSWORDfalse / bolt://neo4j:7687Графовый поиск (независим от векторных движков).

Помимо переменных окружения (хранилище env, глобальное для процесса), можно создавать записи VectorStore в административном интерфейсе для тенанта и привязывать их к конкретным базам знаний (тот же тип движка может использовать несколько экземпляров кластера, поиск автоматически выбирает нужный экземпляр согласно привязке KB и проверяет принадлежность тенанту (см. Выбор движка при поиске).

Справочник реализаций движков ​

Архитектура уровней: Repository → KVHybridRetrieveEngine → Composite → Registry ​

Каждый бэкенд реализует interfaces.RetrieveEngineRepository (EngineType() / Support() / Save / BatchSave / Retrieve / DeleteBy* / CopyIndices / BatchUpdateChunkEnabledStatus / BatchUpdateChunkTagID / EstimateStorageSize). Над ним расположены:

  • KVHybridRetrieveEngine (retriever/keywords_vector_hybrid_indexer.go): Оборачивает Repository в RetrieveEngineService, отвечает за запись эмбеддингов и ключевых слов при индексации;
  • CompositeRetrieveEngine (retriever/composite.go): Композитный паттерн. Метод Retrieve направляет запрос в первый поддерживающий нужный тип (vector / keywords из RetrieveParams.RetrieverType) движок; операции записи (Index / Delete / CopyIndices и т.д.) транслируются на всех членов.
  • RetrieveEngineRegistry (retriever/registry.go): Двойной реестр — byEngineType (на основе RETRIEVE_DRIVER, только один экземпляр на тип) и byStoreID (на основе таблицы VectorStore в БД, позволяет регистрировать несколько экземпляров одного типа движка, например, два кластера ES).
Динамическое воссоздание (rehydrate) ​

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

  • При отсутствии в GetOrLoadByStoreID создается экземпляр «на лету» с использованием VectorStoreRepository и EngineFactory; если репозиторий или фабрика не предоставлены, выполняется обычный поиск.
  • Одиночное создание ограничено тайм-аутом EngineBuildTimeout (10 с), singleflight объединяет одновременные запросы в один вызов;
  • При сбое создания запускается период rebuildCooldown (30 с) для предотвращения повторных неудачных попыток при постоянной недоступности бэкенда.
  • Используется счетчик поколений storeGen для предотвращения гонки состояний: результаты публикуются только если поколение совпало с начальным, предотвращая перезапись новыми данными при создании или удалении во время сборки.

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

Регистрация движков: initRetrieveEngineRegistry ​

Инициализация происходит в internal/container/container.go. При запуске парсится RETRIEVE_DRIVER (список через запятую), для каждого драйвера создается клиент и регистрируется registry.Register(retriever.NewKVHybridRetrieveEngine(repo, engineType)); ошибки инициализации одного драйвера только логируются, не препятствуя запуску. Затем loadDBStoresIntoRegistry загружает экземпляры векторных хранилищ, созданные пользователем, из таблицы vector_stores, создает для них движки через createEngineServiceFromStore (engine_factory.go) и регистрирует с помощью RegisterWithStoreID.

Выбор движка при поиске ​

Точка входа поиска HybridSearch (knowledgebase_search.go) выбирает движок на основе привязки KB:

  1. resolveStoreGroups группирует KB по (VectorStoreID, владелец тенанта);
  2. Для каждой группы вызывается retriever.CreateRetrieveEngineForKB (factory.go):
  • Если KB не привязана к хранилищу (VectorStoreID пуст, используется по умолчанию) → используется GetRetrieverEngines() тенанта; если не настроено, то GetDefaultRetrieverEngines() на основе RETRIEVE_DRIVER (internal/types/tenant.go);
  • Если KB привязана к хранилищу → сначала проверяется принадлежность хранилища тенанту (ownership.StoreOwnedBy) (отказ при попытке доступа к хранилищу другого тенанта, возвращается ErrVectorStoreForbidden), затем экземпляр берется из registry.GetByStoreID (при отсутствии возвращается ErrVectorStoreNotFound), оборачивается в Composite с одним членом;
  1. buildRetrievalParams формирует параметры поиска для векторов/ключевых слов на основе поддержки (SupportRetriever) движком и типа KB (базы FAQ используют только векторный поиск FAQ, базы документов — стандартный векторный + ключевые слова);
  2. При наличии нескольких групп retrieveFromStores запускает параллельный поиск (верхний предел 4 группы, тайм-аут каждой группы MULTI_STORE_RETRIEVE_TIMEOUT_SEC, по умолчанию 30 с), результаты нормализуются при наличии разных типов движков.

Подробное описание движков ​

Константы типов движков см. в internal/types/retriever.go: postgres, elasticsearch, opensearch, qdrant, milvus, weaviate, doris, sqlite, tencent_vectordb (также есть устаревшие infinity, elasticfaiss, которые не развертываются). За исключением особых случаев, все движки заявляют Support() как [keywords, vector].

PostgreSQL (pgvector + ParadeDB) — движок по умолчанию ​

internal/application/repository/retriever/postgres/repository.go. Использует ту же БД, что и приложение (embeddings таблица, управляется GORM).

  • Векторный поиск: pgvector halfvec (половинная точность, 2 байта/измерение). Колонка embedding имеет переменную размерность, индекс HNSW строится на выражении (embedding::halfvec(dim)) halfvec_cosine_ops — выражение в ORDER BY должно точно совпадать с выражением индекса (явное приведение типов с обеих сторон), иначе произойдет последовательное сканирование (комментарии в коде ссылаются на pgvector issue #702/#835). Запрос использует подзапрос для получения expandedTopK (TopK*2, в диапазоне [100,200], чтобы избежать замедления HNSW из-за большого LIMIT) кандидатов с вычислением distance = embedding <=> query, затем фильтрация по distance <= 1-threshold, score = 1 - distance. В транзакции SET LOCAL hnsw.ef_search (≥40) и SET LOCAL hnsw.iterative_scan = strict_order (pgvector ≥ 0.8, выборочная фильтрация с дополнением результатов), при отсутствии GUC в старых версиях автоматически выполняется повторный запрос.
  • Поиск по ключевым словам: BM25 от ParadeDB (pg_search — content ||| query для соответствия любому токену) + paradedb.score(id) as score.
  • Фильтрация: knowledge_base_id / knowledge_id / tag_id IN (логика И), is_enabled равно NULL или true.
  • Индексация: BatchSave + ON CONFLICT DO NOTHING; удаление выполняется физически по ID блока/источника/знания.

SQLite (FTS5 + sqlite-vec) — легковесный одиночный вариант ​

internal/application/repository/retriever/sqlite/repository.go. Решение без внешних зависимостей, полностью встраиваемое.

  • Векторный поиск: расширение sqlite-vec (cgo bindings), каждая размерность имеет отдельную виртуальную таблицу vec0: CREATE VIRTUAL TABLE ... USING vec0(embedding float[dim] distance_metric=cosine); запрос WHERE v.embedding MATCH ? (сериализованный вектор запроса) ORDER BY v.distance, score = 1 - distance. При запуске ensureExistingVecTables создает недостающие таблицы vec0 на основе существующих данных.
  • Поиск по ключевым словам: FTS5 таблица lite_embeddings_fts без контента, с ручным биграммным токенизатором (подходит для китайского), запрос после sanitizeFTS5Query также использует биграммы MATCH.
  • Фильтрация: knowledge_base_id, knowledge_id, tag_id и статус включения применяются к основной таблице lite_embeddings. Векторные пути используют WHERE v.rowid IN (SELECT ... FROM lite_embeddings filtered WHERE ...), фильтрация происходит до выбора TopK. Если сначала выбрать глобальные TopK, а затем фильтровать, то валидные совпадения могут не попасть в выборку;
  • Распространение ошибок: любой сбой пути поиска возвращает ошибку, предотвращая ошибочное принятие сбоя за успешный поиск без совпадений;
  • Порог: значение 0 для порога вектора означает отсутствие фильтрации, а не отбрасывание всех результатов;
  • Подходит для настольных ПК / разработки / очень малых развертываний.

Elasticsearch v8 ​

internal/application/repository/retriever/elasticsearch/v8/repository.go. Типизированный клиент, один индекс (ELASTICSEARCH_INDEX, по умолчанию WeKnora), документы содержат поле dense_vector для эмбеддингов.

  • Векторный поиск: script_score запрос, скрипт Math.max(cosineSimilarity(params.query_vector, 'embedding'), 0.0), порог фильтрации на стороне приложения. Lucene не допускает отрицательных итоговых оценок; при отсутствии обрезки, если хотя бы один вектор имеет противоположное направление запросу, весь запрос на поиск вернет 400; при установке порога 0 значения будут в диапазоне [0,1], такие документы окажутся в конце списка и будут отфильтрованы любым положительным порогом.
  • Поиск по ключевым словам: match запрос к полю content (BM25).
  • Фильтрация: bool filter (термы для ID KB/знания/тега; is_enabled использует must_not для инвертированного поиска, отсутствие поля в исторических данных означает активацию); при запуске проверяется mapping для определения, требуется ли суффикс .keyword для полей ID.
  • Индексация: пакетная запись через Bulk API, пустые векторы отклоняются.

Elasticsearch v7 — только ключевые слова ​

internal/application/repository/retriever/elasticsearch/v7/repository.go. Важно: Support() возвращает только [keywords] — драйвер v7 в aihelpme.ru регистрируется только как движок BM25 ключевых слов (в коде сохранены конструкции запросов script_score cosineSimilarity, но декларация возможностей не включает vector; Composite не направляет запросы векторов этому драйверу). Для векторного поиска требуется комбинация с другим драйвером (например, RETRIEVE_DRIVER=postgres,elasticsearch_v7) или обновление до v8.

OpenSearch ​

internal/application/repository/retriever/opensearch/ (разделено на несколько файлов: repository.go, retrieve.go, query.go, mapping.go, crud.go и др.).

  • Проверка версии: отклоняет версии ES, несовместимые с Lucene HNSW (предварительная версия); версии 2.4-2.10 принимаются с предупреждением; 2.11+ / 3.x принимаются без замечаний (основное тестирование на 3.3.2). probeKNNPlugin требует наличия плагина opensearch-knn на всех узлах.
  • Векторный поиск: плагин knn ( query.go buildKNNQuery); тип COSINESIMIL k-NN возвращает (1+cosine)/2. Драйвер преобразует порог в min_score = (1+threshold)/2 для передачи на уровень движка, а при возврате преобразует обратно в косинусное сходство (2*score-1), приводя к единой шкале с другими движками.
  • Поиск по ключевым словам: match для content (BM25). Гибридный поиск не использует нативный конвейер OS, а унифицируется на верхнем уровне через RRF ( query.go явно указывает на это).
  • Индексация: декларативное определение mapping (mapping.go) (поле knn_vector с параметрами method/engine); проверка fingerprints mapping при запуске, отклонение при несоответствии (ErrConfigInvalid); управление алиасами + copy.go для поддержки reindex; события создания/перестроения индекса записываются в журнал аудита через AuditSink.
  • Конфигурация включает OPENSEARCH_INSECURE_SKIP_VERIFY и слой безопасной транспортной передачи ( transport.go ).

Qdrant ​

internal/application/repository/retriever/qdrant/repository.go. gRPC клиент (порт по умолчанию 6334).

  • Управление коллекциями: отдельный collection для каждой размерности: {QDRANT_COLLECTION|weknora_embeddings}_{dim}; payload (kb_id/knowledge_id/chunk_id/tag_id и т.д.) индексируется как ключевые слова, content использует многоязычный токенизатор для полнотекстового индекса.
  • Векторный поиск: API Query, оценка score — нормализованное скалярное произведение (≈cosine, [0,1] для IR embedding), порог передается как score_threshold.
  • Поиск по ключевым словам: локальная токенизация tokenizeQuery с последующей фильтрацией MatchText(content, token) для каждого токена в режиме SHOULD (ИЛИ); используется Scroll для обхода всех collection соответствующих размерностей; без оценки BM25 (результаты возвращаются сразу после совпадения, оценка определяется RRF на верхнем уровне).
  • Фильтрация: getBaseFilter формирует булевы выражения для точной фильтрации по KB/knowledge/tag/is_enabled.
  • Запись и удаление: невалидные UTF-8 и NUL символы в payload очищаются перед записью; при удалении, если collection для данной размерности еще не существует (например, до первой записи или после очистки Qdrant), это обрабатывается как «нечего удалять», не препятствуя последующей записи; пакетные обновления статуса включения и тегов выполняются для каждого collection, любая ошибка возвращается, а не только логируется.
  • Конфигурация: QDRANT_HOST / QDRANT_PORT / QDRANT_API_KEY / QDRANT_USE_TLS.

Milvus ​

internal/application/repository/retriever/milvus/repository.go.

  • Управление коллекциями: отдельный collection для каждой размерности ({MILVUS_COLLECTION|weknora_embeddings}_{dim}). Схема включает плотный вектор embedding (индекс HNSW, M=16 efConstruction=128, метрика определяется MILVUS_METRIC_TYPE: IP по умолчанию / COSINE / L2) и разреженный вектор content_sparse — автоматически генерируется из content через встроенную функцию BM25 (entity.FunctionTypeBM25) с AutoIndex(BM25). Для content в новых Collection используется многоязычный анализатор Milvus: english для английского, chinese (с Jieba) для китайского, default (ICU) для неизвестных языков, с указанием анализатора в поле language.
  • Векторный поиск: Search + WithANNSField(embedding). IP / COSINE возвращают косинусное сходство напрямую; L2 возвращает квадрат евклидова расстояния, который драйвер преобразует в 1 - d/2, а порог — в радиус поиска 2(1 - threshold).
  • Поиск по ключевым словам: поиск по разреженным векторам BM25 для content_sparse (полнотекстовый поиск в Milvus 2.5+), выполняется с указанием analyzer_name в зависимости от языка запроса. Возвращает фактические оценки BM25 от Milvus; при поиске по нескольким размерностям результаты сначала объединяются и сортируются, затем выбирается TopK.
  • Фильтрация: filter.go строит булевы выражения для фильтрации по KB/знанию/тегу/статусу включения.
  • Синхронизация включения/отключения: BatchUpdateChunkEnabledStatus выполняется для каждого collection отдельно, ошибки агрегируются через errors.Join и возвращаются, а не просто логируются — отключенные в основной БД части не должны быть доступны для поиска из-за молчаливого сбоя обновления индекса.
  • Конфигурация: MILVUS_ADDRESS / MILVUS_USERNAME / MILVUS_PASSWORD / MILVUS_DB_NAME / MILVUS_METRIC_TYPE (при изменении метрики требуется пересоздание collection). Старые схемы Collection нельзя напрямую преобразовать в многоязычные анализаторы; для этого можно использовать go run ./cmd/milvus-migrate --source <старый префикс> --target <новый префикс>, чтобы перенести плотные векторы и сохранить метрику исходного Collection, затем после подтверждения успешного поиска изменить MILVUS_COLLECTION на новый префикс.

Weaviate ​

internal/application/repository/retriever/weaviate/repository.go. Двойной интерфейс: HTTP + gRPC.

  • Управление классами: динамическое создание Class (WEAVIATE_COLLECTION парсится), поддержка ReplicationConfig / ShardingConfig.
  • Векторный поиск: GraphQL nearVector + порог certainty; certainty = (2-distance)/2 = (1+cosine)/2. Драйвер передает порог (1+threshold)/2, а при возврате преобразует обратно в косинусное сходство (2*certainty-1).
  • Поиск по ключевым словам: GraphQL BM25 запрос (Bm25ArgBuilder).
  • Фильтрация: GraphQL where для фильтрации по KB/знанию/тегу/статусу включения.
  • Конфигурация: WEAVIATE_HOST / WEAVIATE_GRPC_ADDRESS / WEAVIATE_SCHEME / WEAVIATE_AUTH_ENABLED + WEAVIATE_API_KEY.

Apache Doris (4.1+) ​

internal/application/repository/retriever/doris/ (699 строк в repository.go + schema.go + structs.go). Подключение через MySQL протокол к FE (9030), HTTP (8030) через Stream Load (безопасный клиент SSRF).

  • Создание таблиц: отдельная таблица для каждой размерности (префикс DORIS_TABLE_PREFIX|weknora_embeddings), schema.go генерирует DDL: ANN индекс HNSW + inner_product (единичные векторы при записи/запросе эквивалентны cosine); колонка content использует инвертированный индекс с объявленным chinese parser (не требует токенизации на стороне приложения). После генерации DDL выполняется опрос готовности ANN индекса.
  • Режим совместимости DORIS_COMPAT_MODE: auto (автоопределение) / inner_product_duplicate (таблица DUPLICATE KEY + inner_product_approximate) / legacy ( 1 - cosine_distance_approximate); после создания таблицы режим нельзя изменить.
  • Векторный поиск: inner_product_approximate(embedding, query) (cosine после нормализации) или формула legacy, LIMIT TopK в SQL.
  • Поиск по ключевым словам: content MATCH_ANY ? через инвертированный индекс.
  • Запись: для таблиц DUPLICATE KEY используется явное удаление + вставка для сохранения семантики замены; обновления enabled/tag выполняются через Stream Load partial update.
  • Конфигурация: DORIS_ADDR / DORIS_HTTP_PORT / DORIS_DATABASE / DORIS_USERNAME / DORIS_PASSWORD / DORIS_TABLE_PREFIX / DORIS_COMPAT_MODE.

VectorDB ​

internal/application/repository/retriever/tencentvectordb/repository.go. RpcClient, EventualConsistency, тайм-аут 10с.

  • Управление коллекциями: отдельный collection для каждой размерности ({TENCENT_VECTORDB_COLLECTION|weknora_embeddings}_{dim}), тройной индекс: плотный вектор HNSW+COSINE (M=16, efConstruction=200), разреженный вектор SPARSE_INVERTED+IP (BM25 на стороне сервера), скалярный фильтр (первичный ключ id + фильтры по content/source/chunk/knowledge/kb/tag).
  • Векторный поиск: Search COSINE, диапазон SDK [-1,1] (фактический диапазон IR embedding [0,1]).
  • Поиск по ключевым словам: локальный encoder.SparseEncoder (BM25) кодирует запрос в разреженный вектор, выполняет поиск по полю sparse_vector и обходит все collection соответствующих размерностей.
  • Конфигурация: TENCENT_VECTORDB_ADDR / TENCENT_VECTORDB_USERNAME / TENCENT_VECTORDB_API_KEY / TENCENT_VECTORDB_DATABASE / TENCENT_VECTORDB_COLLECTION. Пропуск регистрации при отсутствии хотя бы одного из трех ключевых параметров.

Neo4j — поиск по графу (вне системы Registry) ​

internal/application/repository/retriever/neo4j/repository.go реализует RetrieveGraphRepository (SearchNode(ctx, NameSpace, entities)), а не движок векторного/ключевого поиска: ищет узлы и связи сущностей по NameSpace (KnowledgeBase, Knowledge), используется на этапе ENTITY_SEARCH в конвейере chat (GraphRAG). Активируется через NEO4J_ENABLE=true + NEO4J_URI/NEO4J_USERNAME/NEO4J_PASSWORD. Поиск ограничен 200 начальными сущностями и 2000 строками результата, подробнее см. в Графы знаний.

Управление размерностью эмбеддингов ​

aihelpme.ru позволяет использовать разные модели эмбеддингов (с разной размерностью) для разных KB. Стратегия изоляции размерности по движкам:

ДвижокСтратегия
PostgreSQLСмешанное хранение в одной таблице embeddings с колонкой dimension; HNSW индекс строится на выражении embedding::halfvec(dim), при поиске WHERE dimension = ? + соответствующее приведение типа для попадания в индекс
SQLiteОтдельная виртуальная таблица vec0 для каждой размерности (создается автоматически при запуске на основе данных)
Qdrant / Milvus / VectorDBОтдельный collection для каждой размерности: {base}_{dim}, ensureCollection создает лениво (memoized map sync.Map для отслеживания созданных размерностей)
DorisОтдельная таблица для каждой размерности: {prefix}_{dim}, schema.go генерирует DDL и опрашивает готовность ANN индекса
ElasticsearchЕдиное mapping dense_vector в одном индексе (ELASTICSEARCH_INDEX), размерность фиксирована в mapping
OpenSearchСоздание псевдонима {OPENSEARCH_INDEX}_{dim} и физического индекса для каждой размерности; отдельный индекс для ключевых слов без размерности

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

Согласованность на стороне поиска обеспечивается функцией validateSameEmbeddingModel (knowledgebase_search_shared.go): все KB в одном поисковом запросе должны иметь одинаковую модель эмбеддингов (по имени + BaseURL, кросс-тенанты считаются эквивалентными); в противном случае запрос отклоняется — это предотвращает сравнение оценок из разных векторных пространств. Вектор запроса вычисляется один раз для каждого идентификатора модели эмбеддингов (ResolveEmbeddingModelKeys + GetQueryEmbedding) и передается как params.QueryEmbedding всем группам хранилищ, исключая повторные вызовы API эмбеддингов.

Объединение гибридного поиска ​

Нормализация векторных оценок между движками (EngineAwareNormalizer) ​

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

ДвижокИсходная оценкаПреобразование драйвера
Postgres / SQLiteрасстояние cosine1 - distance
Qdrant / VectorDB / Doriscosine (или скалярное произведение на единичных векторах)Напрямую
Milvus (IP / COSINE)внутреннее произведение / cosineНапрямую
Milvus (L2)квадрат евклидова расстояния, для единичных векторов = 2 - 2cosоценка 1 - d/2, порог 2(1 - threshold)
Elasticsearch v8cosineSimilarity в script_score (Lucene требует неотрицательности)Напрямую
OpenSearchk-NN COSINESIMIL: (1+cosine)/2оценка 2*score-1, порог (1+threshold)/2
Weaviatecertainty: (1+cosine)/2оценка 2*certainty-1, порог (1+threshold)/2

Функция internal/application/service/retriever/normalizer.go дополнительно выполняет clamp01 для векторных оценок при наличии смешанных типов движков (hasMixedEngineTypes) в результате fan-out; неизвестные движки также clamp01 с выводом WARN для каждого запроса.

Оценки ключевых слов (BM25) на этом этапе не нормализуются — их диапазон не ограничен сверху, сжатие приведет к потере информации о длинном хвосте; они обрабатываются на этапе объединения согласно собственным спискам (см. ниже).

clamp01 также обрабатывает NaN/Inf, гарантируя строгий слабый порядок для последующей сортировки.

Взвешенное объединение RRF ​

knowledgebase_search_fusion.go. При наличии результатов от векторного и ключевого поиска:

go
// fuseWithRRF
rrf   = vectorWeight/(rrfK + vectorRank) + keywordWeight/(rrfK + keywordRank)
score = rrf / ((vectorWeight + keywordWeight) / (rrfK + 1))   // Нормализация к [0,1]
  • Каждый список результатов (RetrieveResult, представляющий результаты от отдельного движка, хранилища или группы параметров) обрабатывается независимо, затем объединяется; лучший ранг для каждого chunk берется из его позиции в каждом списке.
  • Оценки делятся на теоретический максимум, в результате чего chunk, занявший первое место в обоих списках, получает 1, а занявший первое место только в одном — получает вес этой ветки (по умолчанию 0.7 / 0.3). Ненормализованный RRF в лучшем случае составляет около 0.016, что ниже обычных косинусных сходств (0.5–0.9), поэтому взвешенное объединение теряет смысл; соответствующие пункты базовой оценки и релевантности MMR также становятся неэффективными;
  • rrfK, vectorWeight, keywordWeight берутся из RetrievalConfig тенанта (GetEffectiveRRFK / GetEffectiveRRFWeights предоставляют значения по умолчанию);
  • При наличии результатов только от одного типа поиска RRF не применяется, deduplicateByScore сохраняет лучший результат для каждого chunk. При наличии только векторных результатов сохраняется косинусное сходство; при наличии только ключевых слов, каждый список нормализуется к [0,1] относительно своего максимума (BM25 оценки от разных движков несопоставимы), сохраняя относительный порядок, чтобы избежать насыщения комбинированной оценки; исходные оценки BM25 все еще записываются в trace поиска.

Все три пути выдают результат в диапазоне [0,1], поэтому результаты разных поисковых запросов (по target документов/тегов, группировка по моделям эмбеддингов, вызовы для FAQ/документов) могут быть объединены и отсортированы.

Комбинированная оценка (оценка rerank модели 0.6 + базовая оценка поиска 0.3 + вес источника 0.1, MMR, взвешивание FAQ/Wiki) выполняется на этапе CHUNK_RERANK в конвейере поиска и ответа, см. Этап переранжирования в конвейере поиска и ответа (RAG pipeline).

Поток выполнения поиска ​

Версия v0.8.3 · MIT License