Поисковые движки и векторные хранилища (Retrieval Engines)
Поисковые движки отвечают за хранение индексов и выполнение векторного, полнотекстового или гибридного поиска. По умолчанию PostgreSQL с использованием образа ParadeDB предоставляет pgvector и BM25, которые могут совместно использоваться с данными приложения. При необходимости независимого масштабирования или повторного использования существующей инфраструктуры можно выбрать другие бэкенды.
При выборе бэкенда учитывайте зависимости развертывания, требования к производительности и изоляции данных:
| Сценарий | Рассмотреть |
|---|---|
| Одиночное или настольное развертывание с использованием встроенной базы данных | SQLite (встроенная, без зависимостей) |
| Необходимость независимого масштабирования сервиса векторного поиска | Qdrant, Milvus |
| Использование существующей инфраструктуры Elasticsearch / OpenSearch | Повторное использование существующего кластера |
| Необходимость раздельного хранения данных по базам знаний | Сохранить движок по умолчанию, зарегистрировать экземпляр в «Настройки → Векторные хранилища» и привязать к нужной базе знаний |
Смена движка требует перестроения индекса. Векторное хранилище, привязанное к базе знаний, не может быть изменено после создания, поэтому его следует определить до создания базы.
Мультимодальные эмбеддинги и поиск изображений
Векторные модели с поддержкой изображений на входе кодируют текст и изображения в одно пространство. В базе знаний векторы изображений по умолчанию отключены: необходимо одновременно настроить мультимодальный режим и «векторный поиск по изображениям»; уже загруженные документы нужно распарсить заново, а страницы сканированных PDF не создают векторов изображений. Галерея показывает только уже существующие изображения и от этого переключателя не зависит. Подробнее см. настройку изображений базы знаний.
Найденные изображения можно передать моделям переранжирования с поддержкой изображений на входе для оценки; визуальные диалоговые модели сразу видят сохранённое найденное оригинальное изображение, а чисто текстовые модели используют описание и другой текстовый материал. Векторы оригинальных изображений и текстовый индекс описаний/OCR управляются раздельно: выключение переключателя останавливает поиск по векторам изображений, но не удаляет уже созданные векторы автоматически.
Сводная таблица возможностей и сравнение выбора
| Движок | Значение RETRIEVE_DRIVER | Векторный поиск | Поиск по ключевым словам / полнотекстовый | Оценка ключевых слов | Разделение китайского текста | Управление размерностью | Передача порога фильтрации | Сложность развертывания | Сценарий использования |
|---|---|---|---|---|---|---|---|---|---|
| PostgreSQL | postgres | pgvector halfvec + HNSW индекс на основе выражений | ParadeDB BM25 (|||) | BM25 (paradedb.score) | Токенизатор ParadeDB | Единая таблица со смешанными размерностями, индекс на основе выражений по указанным размерностям | Порог расстояния в SQL | Низкая (встроено в образ по умолчанию) | Выбор по умолчанию; совместно с основной БД, транзакционная согласованность |
| SQLite | sqlite | sqlite-vec vec0 (cosine) | FTS5 contentless | FTS5 | Bigram (для биграмм) | Отдельная таблица vec0 для каждой размерности | Порог в приложении | Очень низкая (встроено) | Настольные ПК / разработка / микроразвертывания |
| Elasticsearch v8 | elasticsearch_v8 | script_score cosineSimilarity | match (BM25) | BM25 | Анализатор ES | dense_vector в одном индексе | Порог в приложении | Средняя | При наличии кластера ES 8 |
| Elasticsearch v7 | elasticsearch_v7 | Не поддерживается (Support только для keywords) | match (BM25) | BM25 | Анализатор ES | — | — | Средняя | Существующие кластеры ES 7, только как движок ключевых слов, требует комбинации с другим векторным движком |
| OpenSearch | opensearch | k-NN плагин knn (HNSW) | match (BM25) | BM25 | Анализатор OS | Явное определение mapping knn_vector + проверка подлинности | Нативный k-NN | Средняя | Среды ES, требующие аудита / алиасов / reindex; версии 2.11+ / 3.x |
| Qdrant | qdrant | Нативный HNSW Cosine | Полнотекстовый MatchText (токен ИЛИ) | Нет | Токенизатор для нескольких языков | Отдельный collection для каждой размерности | Нативный score_threshold | Средняя | Сценарии с преобладанием векторного поиска, требующие фильтрации по payload |
| Milvus | milvus | HNSW (IP/COSINE/L2) | Функция BM25 для разреженных векторов | BM25 | Анализатор Milvus | Отдельный collection для каждой размерности | Параметры приложения | Средняя/высокая | Масштабные векторные данные, требующие нативного гибридного поиска |
| Weaviate | weaviate | nearVector (certainty) | Нативный BM25 | BM25 | Анализатор Weaviate | Динамический Class | Нативный certainty | Средняя | Экосистема GraphQL, требующая настройки репликации / шардинга |
| Doris | doris | ANN HNSW inner_product/cosine | Инвертированный индекс MATCH_ANY | Инвертированный поиск | Китайский парсер при объявлении таблицы | Отдельная таблица для каждой размерности | Встроенный в SQL | Высокая | При наличии хранилища данных Doris, интеграция поиска и аналитики |
| VectorDB | tencent_vectordb | HNSW COSINE | Разреженный вектор BM25 (SPARSE_INVERTED) | BM25 | SDK SparseEncoder | Отдельный collection для каждой размерности | Параметры приложения | Низкая (облачный сервис) | Облачное решение, не требующее обслуживания |
Примечание: Независимо от наличия встроенной поддержки гибридного поиска у движка, aihelpme.ru всегда выполняет гибридный поиск на верхнем уровне путем RRF-объединения (
knowledgebase_search_fusion.go) — векторы и ключевые слова ищутся независимо, а затем объединяются с учетом веса (см. Оценка гибридного поиска и нормализация). Поэтому каждый движок должен предоставлять только один тип поиска.
Сводка методов конфигурации
Основные переключатели (см. раздел C1 в .env.example, docker-compose.yml):
| Переменная окружения | По умолчанию | Описание |
|---|---|---|
RETRIEVE_DRIVER | postgres | Разделенный запятыми список: postgres / sqlite / elasticsearch_v7 / elasticsearch_v8 / opensearch / qdrant / milvus / weaviate / doris / tencent_vectordb. При множественных драйверах операции записи транслируются на все, поиск направляется по типу. |
MULTI_STORE_RETRIEVE_TIMEOUT_SEC | 30 | Тайм-аут для параллельного поиска в нескольких хранилищах. |
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_TLS | localhost / 6334 / weknora_embeddings | Qdrant (gRPC порт). |
MILVUS_ADDRESS / _COLLECTION / _METRIC_TYPE / _USERNAME / _PASSWORD / _DB_NAME | localhost:19530 / weknora_embeddings / IP | metric должен быть изменен до создания collection. |
WEAVIATE_HOST / _GRPC_ADDRESS / _SCHEME / _AUTH_ENABLED / _API_KEY / _COLLECTION | weaviate:8080 / weaviate:50051 / http | Имена сервисов для контейнерной среды. |
DORIS_ADDR / _HTTP_PORT / _DATABASE / _USERNAME / _PASSWORD / _TABLE_PREFIX / _COMPAT_MODE | doris-fe:9030 / 8030 / weknora / root / — / weknora_embeddings / auto | Doris 4.1+; режим compat требует создания таблицы, после чего не может быть изменен. |
TENCENT_VECTORDB_ADDR / _USERNAME / _API_KEY / _DATABASE / _COLLECTION | — | Пропуск регистрации при отсутствии хотя бы одного из трех ключевых параметров. |
NEO4J_ENABLE / NEO4J_URI / _USERNAME / _PASSWORD | false / 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:
resolveStoreGroupsгруппирует KB по(VectorStoreID, владелец тенанта);- Для каждой группы вызывается
retriever.CreateRetrieveEngineForKB(factory.go):
- Если KB не привязана к хранилищу (
VectorStoreIDпуст, используется по умолчанию) → используетсяGetRetrieverEngines()тенанта; если не настроено, тоGetDefaultRetrieverEngines()на основеRETRIEVE_DRIVER(internal/types/tenant.go); - Если KB привязана к хранилищу → сначала проверяется принадлежность хранилища тенанту (
ownership.StoreOwnedBy) (отказ при попытке доступа к хранилищу другого тенанта, возвращаетсяErrVectorStoreForbidden), затем экземпляр берется изregistry.GetByStoreID(при отсутствии возвращаетсяErrVectorStoreNotFound), оборачивается в Composite с одним членом;
buildRetrievalParamsформирует параметры поиска для векторов/ключевых слов на основе поддержки (SupportRetriever) движком и типа KB (базы FAQ используют только векторный поиск FAQ, базы документов — стандартный векторный + ключевые слова);- При наличии нескольких групп
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_idIN (логика И),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); типCOSINESIMILk-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 Loadpartial 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 | расстояние cosine | 1 - distance |
| Qdrant / VectorDB / Doris | cosine (или скалярное произведение на единичных векторах) | Напрямую |
| Milvus (IP / COSINE) | внутреннее произведение / cosine | Напрямую |
| Milvus (L2) | квадрат евклидова расстояния, для единичных векторов = 2 - 2cos | оценка 1 - d/2, порог 2(1 - threshold) |
| Elasticsearch v8 | cosineSimilarity в script_score (Lucene требует неотрицательности) | Напрямую |
| OpenSearch | k-NN COSINESIMIL: (1+cosine)/2 | оценка 2*score-1, порог (1+threshold)/2 |
| Weaviate | certainty: (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. При наличии результатов от векторного и ключевого поиска:
// 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).
