# Qdrant: фильтрация поиска по метаданным и проверка выдачи

Источник: https://modelium.tech/zhurnal/qdrant-filtry-metadannyh/

Qdrant принимает условия на поля payload в `filter` запросов Query API: `must` объединяет условия по И, `should` — по ИЛИ, `must_not` исключает совпадения. Для повторяемого теста создайте отдельную коллекцию с синтетическими точками разных меток и проверьте как допустимую, так и запрещённую выдачу. Фильтр поиска не заменяет аутентификацию, авторизацию приложения и тест прав на документ.

**Проверено по документации 6 октября 2026 года.** Команды ниже — воспроизводимый синтетический протокол для читателя, а не результат запуска Modelium. Точная версия Qdrant в этом тексте не закреплена: API и документация сохранены с SHA-256; на вашей установленной версии проверьте совместимость перед использованием. [s1](#src-s1) [s3](#src-s3)

## Когда метаданные полезнее одного векторного сходства?

Предположим, локальный поиск по документам нашёл похожие фрагменты из разных подразделений. У точки Qdrant можно хранить `tenant_id`, `doc_type`, идентификатор документа и текст в payload, а Query API ограничит кандидатов до сравнения результата по условиям `filter`. Это полезно для маршрута «запрос → embedding → Qdrant → разрешённые кандидаты → ответ локальной модели»; сам факт похожести векторов не означает, что документ разрешено показывать пользователю. [s1](#src-s1) [s3](#src-s3) [s6](#src-s6) [s4](#src-s4)

`must` означает совпадение **всех** условий, `should` — хотя бы одного, `must_not` — исключение перечисленных совпадений. Для метки `tenant_id` и типа `doc_type` используйте `match.value` по заранее нормализованному keyword, а не полнотекстовое условие. В демонстрации `must` ограничивает поиск одновременно двумя метками. Одна перестановка цвета или текста в payload не меняет векторную семантику. [s1](#src-s1)

## Как подготовить безопасный синтетический набор?

Выполните [первый запуск Qdrant](/zhurnal/qdrant-lokalno/) на **изолированном локальном экземпляре** с портом 6333, доступным только на loopback; не используйте реальные документы. Self-hosted Qdrant по умолчанию доступен без аутентификации, если не настроить защиту: связывание порта с `127.0.0.1` в демонстрации не делает общий сервер безопасным. Перед рабочей эксплуатацией нужны сетевые ограничения, ключи и TLS, а также собственная политика доступа приложения. [s8](#src-s8) [s4](#src-s4)

Создайте **новую** коллекцию `demo_filter_docs`, чтобы не задеть прежнюю `demo_docs`: `curl -fsS -X PUT 'http://127.0.0.1:6333/collections/demo_filter_docs' -H 'Content-Type: application/json' -d '{"vectors":{"size":3,"distance":"Cosine"}}'`. Размерность 3 — только для искусственных координат ниже: не переносите её на embedding-модель. Сверьте `status` ответа прежде, чем писать точки. [s8](#src-s8)

До загрузки точек создайте два keyword payload-индекса: `curl -fsS -X PUT 'http://127.0.0.1:6333/collections/demo_filter_docs/index?wait=true' -H 'Content-Type: application/json' -d '{"field_name":"tenant_id","field_schema":"keyword"}'`; второй вызов с `{"field_name":"doc_type","field_schema":"keyword"}` для того же URL. Индекс ускоряет фильтр и помогает оценивать число кандидатов, но расходует ресурсы; на большом наборе индексируйте используемые поля, а не весь payload. Если коллекция уже заполнена, рекомендации Qdrant об HNSW после позднего создания индекса следует проверить отдельно. [s2](#src-s2) [s7](#src-s7)

Запишите три точки одной операцией: `curl -fsS -X PUT 'http://127.0.0.1:6333/collections/demo_filter_docs/points?wait=true' -H 'Content-Type: application/json' -d '{"points":[{"id":1,"vector":[1,0,0],"payload":{"tenant_id":"alpha","doc_type":"invoice","doc_id":"a1","text":"Тестовый счёт A"}},{"id":2,"vector":[1,0,0],"payload":{"tenant_id":"beta","doc_type":"invoice","doc_id":"b1","text":"Тестовый счёт B"}},{"id":3,"vector":[1,0,0],"payload":{"tenant_id":"alpha","doc_type":"memo","doc_id":"a2","text":"Тестовая заметка A"}}]}'`. Равные искусственные векторы намеренно исключают смысловую разницу; это проверка **фильтра**, не модели, ранжирования или качества RAG. Убедитесь, что upsert вернул успешный статус, затем запросите результат. [s6](#src-s6) [s3](#src-s3)

## Как проверить совпадение и отсутствие чужих точек?

Отправьте `POST /collections/demo_filter_docs/points/query` с телом `{"query":[1,0,0],"filter":{"must":[{"key":"tenant_id","match":{"value":"alpha"}},{"key":"doc_type","match":{"value":"invoice"}}]},"limit":3,"with_payload":true}`. Например, локальный вызов: `curl -fsS -X POST 'http://127.0.0.1:6333/collections/demo_filter_docs/points/query' -H 'Content-Type: application/json' -d '{"query":[1,0,0],"filter":{"must":[{"key":"tenant_id","match":{"value":"alpha"}},{"key":"doc_type","match":{"value":"invoice"}}]},"limit":3,"with_payload":true}'`. По логике сохранённых меток в **свежей** коллекции `result.points` должен содержать только `id=1`; `id=2` — чужой tenant, `id=3` — другой тип. Это ожидаемый вывод из fixture, а не измеренный результат Modelium. [s1](#src-s1) [s3](#src-s3) [s6](#src-s6)

Повторите тот же запрос с `tenant_id=beta`: должен появиться только `id=2`. С `tenant_id=gamma` ожидается пустой `result.points`; пустой ответ полезен как отрицательный контроль, но не доказывает, что злоумышленник не может отправить запрос **без фильтра**. Проверьте именно клиентское ограничение на маршрут и права по документу отдельно. Если `payload` отсутствует, убедитесь, что установлен `with_payload:true` — по умолчанию API его не возвращает. Не делайте вывод о порядке точек с равными векторами. [s1](#src-s1) [s3](#src-s3) [s4](#src-s4)

## Что меняется на реальных документах и больших коллекциях?

При реальной индексации нужен один выбранный способ получать embeddings как при записи, так и при запросе. Для self-hosted Qdrant документация рекомендует клиентский путь, например FastEmbed; это **не** универсальное утверждение «Qdrant не создаёт embeddings»: Cluster BM25 формирует sparse embeddings, Cloud Inference предлагает управляемые модели, а внешний провайдер — отдельный облачный путь. Для закрытого контура проверьте фактические сетевые обращения выбранного стека. Данные из демонстрации с трёхмерным вектором не подтверждают пригодность модели для русских документов. [s5](#src-s5) [s8](#src-s8)

Нельзя считать `tenant_id` в payload самодостаточным разграничением прав: если клиент может передать другой tenant или снять `filter`, он получит иной набор точек при доступном API. Вывод: сервер приложения должен устанавливать условия из проверенной личности, ограничить прямой доступ к Qdrant и проверять документные ACL при выдаче и после изменения прав. Qdrant описывает API-key, read-only и granular collection keys, но метка документа и права внутри общего tenant — отдельная задача приложения. Пройдите [проверку корпоративного поиска](/zhurnal/korporativnyy-poisk-prava-dostupa/) на двух ролях и после отзыва доступа. [s1](#src-s1) [s4](#src-s4)

Если поле — массив объектов, два независимых `key` с `[]` могут совпасть **на разных элементах** одного массива. Для условия, которое должно выполниться на одном объекте, используйте `nested` с вложенным `filter` — это отдельно иллюстрирует документация Qdrant. Для незнакомого поля или нулевой выдачи проверьте написание и тип payload, настройки keyword-индекса, статус upsert и исходную коллекцию; не подменяйте отсутствие результатов «плохими embeddings» до такой проверки. [s1](#src-s1) [s2](#src-s2) [s6](#src-s6)

Не удаляйте рабочие коллекции ради демонстрации. Когда тест закончился и имя сверено, удалите **только** созданную здесь `demo_filter_docs`: `curl -fsS -X DELETE 'http://127.0.0.1:6333/collections/demo_filter_docs'`. Оценить полезность фильтра на ваших данных поможет [протокол качества RAG](/zhurnal/ocenka-kachestva-rag/): фиксируйте версию модели, доступное железо, способ векторизации, набор разрешённых и запрещённых запросов и фактические ошибки выдачи. Это следующий шаг читателя, не отчёт об эксперименте Modelium. [s1](#src-s1) [s3](#src-s3) [s4](#src-s4)

## Источники

1. [Qdrant — Filtering: clauses, match и nested](https://qdrant.tech/documentation/search/filtering/) — получено 2026-10-06
2. [Qdrant — Indexing: payload index](https://qdrant.tech/documentation/manage-data/indexing/) — получено 2026-10-06
3. [Qdrant REST API — Query points](https://api.qdrant.tech/api-reference/search/query-points) — получено 2026-10-06
4. [Qdrant — Security & Access Control](https://qdrant.tech/documentation/security/) — получено 2026-10-06
5. [Qdrant — Inference: local, BM25, Cloud](https://qdrant.tech/documentation/inference/) — получено 2026-10-06
6. [Qdrant REST API — Upsert points](https://api.qdrant.tech/api-reference/points/upsert-points) — получено 2026-10-06
7. [Qdrant REST API — Create payload index](https://api.qdrant.tech/api-reference/indexes/create-field-index) — получено 2026-10-06
8. [Qdrant — Local Quickstart](https://qdrant.tech/documentation/quickstart/) — получено 2026-10-06
